Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/lib/components/icons/Icon.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,13 @@
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"/>
<line x1="7" y1="7" x2="7.01" y2="7"/>
</svg>
{:else if name === 'stage'}
<!-- Arrow pointing down into a horizontal line (apply/stage changes) -->
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="7 10 12 15 17 10"/>
<line x1="12" y1="15" x2="12" y2="3"/>
<line x1="4" y1="21" x2="20" y2="21"/>
</svg>
{:else if name === 'font-size-increase'}
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" stroke="none">
<text x="2" y="18" font-size="16" font-weight="700" font-family="system-ui, sans-serif">A</text>
Expand Down
41 changes: 40 additions & 1 deletion src/lib/pyodide/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import {
// Re-export for use in other modules
export { execDuringStreaming };

// Import mutation queue
import { initMappings, flushQueue, clearQueue } from './mutationQueue';

// Re-export replState as pyodideState for backwards compatibility
export { replState as pyodideState };

Expand Down Expand Up @@ -298,14 +301,23 @@ async function runStreamingLoop(
export async function runStreamingSimulation(
code: string,
duration: string,
onUpdate?: (result: SimulationResult) => void
onUpdate?: (result: SimulationResult) => void,
nodeVars?: Map<string, string>,
connVars?: Map<string, string>
): Promise<SimulationResult | null> {
// Ensure initialized
const state = get(replState);
if (!state.initialized) {
await initRepl();
}

// Initialize mutation queue mappings for this run
if (nodeVars && connVars) {
initMappings(nodeVars, connVars);
} else {
clearQueue();
}

streamingActive = true;

// Update simulation state - preserve result structure for smooth transition
Expand Down Expand Up @@ -421,6 +433,13 @@ if 'sim' not in dir() or sim is None:
raise RuntimeError("No simulation to continue. Run a simulation first.")
`);

// Apply any pending graph mutations before continuing
const mutationCode = flushQueue();
if (mutationCode) {
await exec(mutationCode);
consoleStore.info('Applied graph mutations');
}

// Start streaming generator with reset=False and optimized tickrate
await exec(generateStreamingStartCode(durationExpr, STREAMING_TICKRATE, false));

Expand Down Expand Up @@ -476,6 +495,26 @@ if 'sim' not in dir() or sim is None:
}
}

/**
* Stage pending graph mutations into the simulation.
* If streaming: injects via execDuringStreaming (applied between generator steps).
* If paused: executes directly via exec.
* Returns true if mutations were applied, false if nothing to stage.
*/
export async function stageMutations(): Promise<boolean> {
const code = flushQueue();
if (!code) return false;

if (streamingActive) {
execDuringStreaming(code);
consoleStore.info('Staged changes (applied during streaming)');
} else {
await exec(code);
consoleStore.info('Staged changes applied');
}
return true;
}

/**
* Reset simulation state completely.
* Use when loading a new model or creating a new graph.
Expand Down
36 changes: 35 additions & 1 deletion src/lib/pyodide/codeBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function groupConnectionsBySource(
}

/**
* Generate connection lines from grouped connections
* Generate connection lines from grouped connections (anonymous, for inline use)
*
* @param connections - Array of connections
* @param nodeVars - Map of nodeId to variable name
Expand All @@ -107,6 +107,40 @@ export function generateConnectionLines(
return lines;
}

/**
* Generate named connection variable definitions.
* Each edge gets its own named variable for individual mutation support.
*
* @param connections - Array of connections
* @param nodeVars - Map of nodeId to variable name
* @param prefix - Variable name prefix (default 'conn')
* @returns Object with definition lines, variable names list, and id-to-varname map
*/
export function generateNamedConnections(
connections: Connection[],
nodeVars: Map<string, string>,
prefix: string = 'conn'
): { lines: string[]; varNames: string[]; connVars: Map<string, string> } {
const lines: string[] = [];
const varNames: string[] = [];
const connVars = new Map<string, string>();

let idx = 0;
for (const conn of connections) {
const sourceVar = nodeVars.get(conn.sourceNodeId);
const targetVar = nodeVars.get(conn.targetNodeId);
if (!sourceVar || !targetVar) continue;

const varName = `${prefix}_${idx}`;
lines.push(`${varName} = Connection(${sourceVar}[${conn.sourcePortIndex}], ${targetVar}[${conn.targetPortIndex}])`);
varNames.push(varName);
connVars.set(conn.id, varName);
idx++;
}

return { lines, varNames, connVars };
}

/**
* Generate a Python list definition
*
Expand Down
17 changes: 17 additions & 0 deletions src/lib/pyodide/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export {
initPyodide,
runStreamingSimulation,
continueStreamingSimulation,
stageMutations,
resetSimulation,
validateGraph,
stopSimulation,
Expand All @@ -23,6 +24,7 @@ export {
// Code generation
export {
generatePythonCode,
type CodeGenResult,
runGraphStreamingSimulation,
exportToPython,
validateGraphSimulation,
Expand Down Expand Up @@ -51,3 +53,18 @@ export {
type Backend,
type BackendState
} from './backend';

// Mutation queue for runtime graph changes
export {
queueAddBlock,
queueRemoveBlock,
queueAddConnection,
queueRemoveConnection,
queueUpdateParam,
queueUpdateSetting,
hasPendingMutations,
isActive as isMutationQueueActive,
pendingMutationCount,
getNodeVar,
getConnVar
} from './mutationQueue';
Loading