From 45e897299d2ee36d86ab42dc38fad47ba3842ee8 Mon Sep 17 00:00:00 2001 From: Milan Rother Date: Tue, 2 Jun 2026 15:15:44 +0200 Subject: [PATCH] Codegen: add engine setup seam (no-op default) and use _block_key consistently in the node-id map --- src/lib/pyodide/engineCodegen.ts | 32 ++++++++++++++++++++++++++++++++ src/lib/pyodide/pathsimRunner.ts | 25 ++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 src/lib/pyodide/engineCodegen.ts diff --git a/src/lib/pyodide/engineCodegen.ts b/src/lib/pyodide/engineCodegen.ts new file mode 100644 index 00000000..b9fbff75 --- /dev/null +++ b/src/lib/pyodide/engineCodegen.ts @@ -0,0 +1,32 @@ +/** + * Engine-specific codegen seam. + * + * Lets an alternate-engine build inject setup code into the generated Python + * *after* the imports and *before* the blocks, without touching the shared + * code generation in pathsimRunner.ts. The default engine (pathsim) needs none, + * so this is a no-op that a re-engined build swaps out (like the worker-side + * engineInstall seam). + * + * Example (fastsim): emit class-level `port()` wraps for toolbox block classes, + * because fastsim's `Connection` only accepts fastsim blocks, so a pathsim + * toolbox block must be ported at the class level before any instance is built. + */ + +/** A named block of setup lines emitted after imports, before block creation. */ +export interface EngineSetup { + /** Section header, rendered as a `#
` comment (with a banner on export). */ + header: string; + /** Python source lines for the section body. */ + lines: string[]; +} + +/** + * Engine-specific setup code, given the block import groups (import path → class + * names) collected from the graph. Returns null when the engine needs no setup + * (the default), in which case no section is emitted. + */ +export function generateEngineSetup( + _importGroups: Map> +): EngineSetup | null { + return null; +} diff --git a/src/lib/pyodide/pathsimRunner.ts b/src/lib/pyodide/pathsimRunner.ts index c13ea372..338b95a6 100644 --- a/src/lib/pyodide/pathsimRunner.ts +++ b/src/lib/pyodide/pathsimRunner.ts @@ -13,6 +13,7 @@ import { BLOCK_CATEGORY_ORDER } from '$lib/constants/python'; import { isSubsystem, isInterface } from '$lib/nodes/shapes'; import { blockImportPaths } from '$lib/nodes/generated/blocks'; import { ENGINE_MODULE, enginePath } from '$lib/constants/engine'; +import { generateEngineSetup } from './engineCodegen'; import { graphStore, findParentSubsystem } from '$lib/stores/graph'; import { runStreamingSimulation, @@ -421,6 +422,14 @@ export function generatePythonCode( } lines.push(''); + // 1b. Engine-specific setup (e.g. fastsim port() wraps); no-op by default. + const engineSetup = generateEngineSetup(importGroups); + if (engineSetup) { + lines.push(`# ${engineSetup.header}`); + lines.push(...engineSetup.lines); + lines.push(''); + } + // 2. Code context (user-defined variables/functions) if (codeContext.trim()) { lines.push('# CODE CONTEXT'); @@ -475,7 +484,10 @@ export function generatePythonCode( lines.push('# NODE ID MAPPING (for data extraction)'); lines.push('_node_id_map = {'); for (const [nodeId, varName] of nodeVars) { - lines.push(` id(${varName}): "${nodeId}",`); + // _block_key (REPL setup) uses the engine's stable block_id when + // present and falls back to id(), so static entries here stay + // consistent with the mutation-added ones in _apply_mutations. + lines.push(` _block_key(${varName}): "${nodeId}",`); } lines.push('}'); lines.push(''); @@ -599,6 +611,17 @@ function generateFormattedPythonCode( } lines.push(''); + // Engine-specific setup (e.g. fastsim port() wraps); no-op by default. + const engineSetup = generateEngineSetup(importGroups); + if (engineSetup) { + lines.push(divider); + lines.push(`# ${engineSetup.header}`); + lines.push(divider); + lines.push(''); + lines.push(...engineSetup.lines); + lines.push(''); + } + // Code context (user-defined variables/functions) if (codeContext.trim()) { lines.push(divider);