Skip to content

Commit 5db1ce8

Browse files
authored
Merge pull request #263 from pathsim/feature/runtime-mutation
runtime mutation
2 parents fcaccda + 7b9dff1 commit 5db1ce8

12 files changed

Lines changed: 510 additions & 52 deletions

File tree

src/lib/components/icons/Icon.svelte

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,13 @@
420420
<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"/>
421421
<line x1="7" y1="7" x2="7.01" y2="7"/>
422422
</svg>
423+
{:else if name === 'stage'}
424+
<!-- Arrow pointing down into a horizontal line (apply/stage changes) -->
425+
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
426+
<polyline points="7 10 12 15 17 10"/>
427+
<line x1="12" y1="15" x2="12" y2="3"/>
428+
<line x1="4" y1="21" x2="20" y2="21"/>
429+
</svg>
423430
{:else if name === 'font-size-increase'}
424431
<svg width={size} height={size} viewBox="0 0 24 24" fill="currentColor" stroke="none">
425432
<text x="2" y="18" font-size="16" font-weight="700" font-family="system-ui, sans-serif">A</text>

src/lib/pyodide/bridge.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ import {
2727
// Re-export for use in other modules
2828
export { execDuringStreaming };
2929

30+
// Import mutation queue
31+
import { initMappings, flushQueue, clearQueue } from './mutationQueue';
32+
3033
// Re-export replState as pyodideState for backwards compatibility
3134
export { replState as pyodideState };
3235

@@ -298,14 +301,23 @@ async function runStreamingLoop(
298301
export async function runStreamingSimulation(
299302
code: string,
300303
duration: string,
301-
onUpdate?: (result: SimulationResult) => void
304+
onUpdate?: (result: SimulationResult) => void,
305+
nodeVars?: Map<string, string>,
306+
connVars?: Map<string, string>
302307
): Promise<SimulationResult | null> {
303308
// Ensure initialized
304309
const state = get(replState);
305310
if (!state.initialized) {
306311
await initRepl();
307312
}
308313

314+
// Initialize mutation queue mappings for this run
315+
if (nodeVars && connVars) {
316+
initMappings(nodeVars, connVars);
317+
} else {
318+
clearQueue();
319+
}
320+
309321
streamingActive = true;
310322

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

436+
// Apply any pending graph mutations before continuing
437+
const mutationCode = flushQueue();
438+
if (mutationCode) {
439+
await exec(mutationCode);
440+
consoleStore.info('Applied graph mutations');
441+
}
442+
424443
// Start streaming generator with reset=False and optimized tickrate
425444
await exec(generateStreamingStartCode(durationExpr, STREAMING_TICKRATE, false));
426445

@@ -476,6 +495,26 @@ if 'sim' not in dir() or sim is None:
476495
}
477496
}
478497

498+
/**
499+
* Stage pending graph mutations into the simulation.
500+
* If streaming: injects via execDuringStreaming (applied between generator steps).
501+
* If paused: executes directly via exec.
502+
* Returns true if mutations were applied, false if nothing to stage.
503+
*/
504+
export async function stageMutations(): Promise<boolean> {
505+
const code = flushQueue();
506+
if (!code) return false;
507+
508+
if (streamingActive) {
509+
execDuringStreaming(code);
510+
consoleStore.info('Staged changes (applied during streaming)');
511+
} else {
512+
await exec(code);
513+
consoleStore.info('Staged changes applied');
514+
}
515+
return true;
516+
}
517+
479518
/**
480519
* Reset simulation state completely.
481520
* Use when loading a new model or creating a new graph.

src/lib/pyodide/codeBuilder.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function groupConnectionsBySource(
8484
}
8585

8686
/**
87-
* Generate connection lines from grouped connections
87+
* Generate connection lines from grouped connections (anonymous, for inline use)
8888
*
8989
* @param connections - Array of connections
9090
* @param nodeVars - Map of nodeId to variable name
@@ -107,6 +107,40 @@ export function generateConnectionLines(
107107
return lines;
108108
}
109109

110+
/**
111+
* Generate named connection variable definitions.
112+
* Each edge gets its own named variable for individual mutation support.
113+
*
114+
* @param connections - Array of connections
115+
* @param nodeVars - Map of nodeId to variable name
116+
* @param prefix - Variable name prefix (default 'conn')
117+
* @returns Object with definition lines, variable names list, and id-to-varname map
118+
*/
119+
export function generateNamedConnections(
120+
connections: Connection[],
121+
nodeVars: Map<string, string>,
122+
prefix: string = 'conn'
123+
): { lines: string[]; varNames: string[]; connVars: Map<string, string> } {
124+
const lines: string[] = [];
125+
const varNames: string[] = [];
126+
const connVars = new Map<string, string>();
127+
128+
let idx = 0;
129+
for (const conn of connections) {
130+
const sourceVar = nodeVars.get(conn.sourceNodeId);
131+
const targetVar = nodeVars.get(conn.targetNodeId);
132+
if (!sourceVar || !targetVar) continue;
133+
134+
const varName = `${prefix}_${idx}`;
135+
lines.push(`${varName} = Connection(${sourceVar}[${conn.sourcePortIndex}], ${targetVar}[${conn.targetPortIndex}])`);
136+
varNames.push(varName);
137+
connVars.set(conn.id, varName);
138+
idx++;
139+
}
140+
141+
return { lines, varNames, connVars };
142+
}
143+
110144
/**
111145
* Generate a Python list definition
112146
*

src/lib/pyodide/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export {
88
initPyodide,
99
runStreamingSimulation,
1010
continueStreamingSimulation,
11+
stageMutations,
1112
resetSimulation,
1213
validateGraph,
1314
stopSimulation,
@@ -23,6 +24,7 @@ export {
2324
// Code generation
2425
export {
2526
generatePythonCode,
27+
type CodeGenResult,
2628
runGraphStreamingSimulation,
2729
exportToPython,
2830
validateGraphSimulation,
@@ -51,3 +53,18 @@ export {
5153
type Backend,
5254
type BackendState
5355
} from './backend';
56+
57+
// Mutation queue for runtime graph changes
58+
export {
59+
queueAddBlock,
60+
queueRemoveBlock,
61+
queueAddConnection,
62+
queueRemoveConnection,
63+
queueUpdateParam,
64+
queueUpdateSetting,
65+
hasPendingMutations,
66+
isActive as isMutationQueueActive,
67+
pendingMutationCount,
68+
getNodeVar,
69+
getConnVar
70+
} from './mutationQueue';

0 commit comments

Comments
 (0)