feat(wasm): unify editor builds — footprint/symbol editors via runtime --frame
The footprint and symbol editors are no longer separate WASM bundles: the frontend loads the parent pcbnew/eeschema bundle and passes --frame=fpedit / --frame=symedit (TOOL_BUNDLE + TOOL_FRAME -> Module.arguments in boot). Drops the two duplicate build+deploy targets and their wrapper scripts + vestigial embind; adds low-level harnesses (footprint_editor.html, symbol_editor.html) and a runtime-frame spec. Bumps the kicad submodule to the runtime --frame launcher. The frame-runtime spec is listed in PCBNEW_FAMILY_SPECS so CI routes it to the chromium-ci (V8) project — its footprint case boots the pcbnew module, which OOMs SpiderMonkey on x86 CI. Includes the editor-unification dossier (research docs 01-04 + the as-built implementation record 05). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c005e1ba73
commit
c6716e6b60
22 changed files with 1066 additions and 103 deletions
|
|
@ -36,7 +36,7 @@ import {
|
|||
MODELS_LOADING_EVENT,
|
||||
type ModelsLoadingDetail,
|
||||
} from "@/wasm/libs/models-bridge";
|
||||
import { memfsFilePath, memfsProjectDir } from "@/wasm/constants";
|
||||
import { memfsFilePath, memfsProjectDir, TOOL_FRAME } from "@/wasm/constants";
|
||||
import { driveProjectIntoTool, type ToolFile } from "@/wasm/kicad-runner";
|
||||
import { registerSaveHook, type SaveBytes } from "@/wasm/save-flow";
|
||||
import type {
|
||||
|
|
@ -779,6 +779,9 @@ export function WasmTool({
|
|||
// 3D models: lazy per-board source (null unless the CDN manifest is
|
||||
// configured) — feeds the board prescan + the viewer's ensure fallback.
|
||||
modelsSource: modelsSourceConfig(),
|
||||
// footprint_editor/symbol_editor load the pcbnew/eeschema bundle; the
|
||||
// frame token tells its single_top launcher which editor frame to open.
|
||||
frame: TOOL_FRAME[tool],
|
||||
});
|
||||
// Register the save sink before the file opens: from here on, every
|
||||
// editor File→Save (MEMFS write) is routed onward through saveBytes.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
MODELS_3D_ROOT,
|
||||
RESOURCE_PATH,
|
||||
TOOL_ARGV0,
|
||||
TOOL_BUNDLE,
|
||||
TOOL_LIB_KIND,
|
||||
TOOL_NEEDS_CONFIG_SEED,
|
||||
} from "./constants";
|
||||
|
|
@ -67,6 +68,11 @@ export interface BootOptions {
|
|||
/** 3D model source (lazy, per-board). Null/omitted ⇒ the viewer renders the
|
||||
* bare board only, exactly as before models existed. */
|
||||
modelsSource?: Model3dSource | null;
|
||||
/** Editor frame to open when the bundle serves more than one (e.g. `"fpedit"`
|
||||
* so the pcbnew bundle opens the Footprint Editor). Passed through as
|
||||
* `--frame=<token>` in `Module.arguments`; parsed in single_top.cpp. Omitted
|
||||
* ⇒ the bundle's build-time default frame. See `TOOL_FRAME` in constants.ts. */
|
||||
frame?: string;
|
||||
}
|
||||
|
||||
let booted: { tool: Tool; promise: Promise<void> } | null = null;
|
||||
|
|
@ -195,6 +201,10 @@ async function doBoot(opts: BootOptions): Promise<void> {
|
|||
libsSource,
|
||||
modelsSource,
|
||||
} = opts;
|
||||
// The deployed bundle backing this tool. footprint_editor/symbol_editor share
|
||||
// the pcbnew/eeschema engine, so their `.wasm`/`.js`/pthread-worker files are the
|
||||
// parent's; `tool` still drives identity (thisProgram), config-seed and lib-kind.
|
||||
const bundle = TOOL_BUNDLE[tool] ?? tool;
|
||||
const w = window as ToolWindow;
|
||||
|
||||
// The wasm reads the top-level frame geometry from a GLOBAL `mainWindow`
|
||||
|
|
@ -380,6 +390,11 @@ async function doBoot(opts: BootOptions): Promise<void> {
|
|||
w.Module = {
|
||||
thisProgram: TOOL_ARGV0[tool], // argv[0] for KiCad's DEBUG check
|
||||
...(traceMask ? { ENV: { KICAD_TRACE: traceMask } } : {}),
|
||||
// Runtime frame selection: emscripten feeds these to main() as argv[1..], which
|
||||
// single_top.cpp parses ("--frame=<token>") to open the requested editor frame
|
||||
// from a shared bundle. Set in the Module literal so it's present before the
|
||||
// glue's run()/callMain fires. Empty ⇒ the bundle's build-time default frame.
|
||||
arguments: opts.frame ? [`--frame=${opts.frame}`] : [],
|
||||
preRun,
|
||||
postRun: [],
|
||||
print: (...args: unknown[]) => {
|
||||
|
|
@ -415,7 +430,7 @@ async function doBoot(opts: BootOptions): Promise<void> {
|
|||
locateFile: (path: string) => `${base}/${path}`,
|
||||
// Pin the pthread worker script. Same-origin → direct URL; cross-origin CDN
|
||||
// → a same-origin blob shim that importScripts the glue (see helper above).
|
||||
mainScriptUrlOrBlob: pthreadWorkerScript(base, tool, traceMask),
|
||||
mainScriptUrlOrBlob: pthreadWorkerScript(base, bundle, traceMask),
|
||||
// Own the wasm fetch so we can report download progress (see helper). Streams
|
||||
// straight into the compiler; passing `module` to the callback lets emscripten
|
||||
// share it with the pthread workers (this hook fires on the main thread only).
|
||||
|
|
@ -426,7 +441,7 @@ async function doBoot(opts: BootOptions): Promise<void> {
|
|||
void (async () => {
|
||||
try {
|
||||
onStatus("Downloading…");
|
||||
const resp = await fetchWasmWithProgress(`${base}/${tool}.wasm`, onProgress);
|
||||
const resp = await fetchWasmWithProgress(`${base}/${bundle}.wasm`, onProgress);
|
||||
const ct = resp.headers.get("content-type") ?? "";
|
||||
if (ct.includes("application/wasm") && WebAssembly.instantiateStreaming) {
|
||||
const { instance, module } = await WebAssembly.instantiateStreaming(
|
||||
|
|
@ -460,6 +475,6 @@ async function doBoot(opts: BootOptions): Promise<void> {
|
|||
// Emscripten's _scriptName.
|
||||
await loadScript(`${base}/wx.js`);
|
||||
await loadScript(`${base}/wx-dom.js`);
|
||||
await loadScript(`${base}/${tool}.js`);
|
||||
log(`[boot] injected wx.js + wx-dom.js + ${tool}.js (base=${base})`);
|
||||
await loadScript(`${base}/${bundle}.js`);
|
||||
log(`[boot] injected wx.js + wx-dom.js + ${bundle}.js (base=${base})`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,41 @@ export const TOOL_ARGV0: Record<Tool, string> = {
|
|||
gerbview: "/usr/bin/gerbview",
|
||||
};
|
||||
|
||||
/**
|
||||
* Which deployed WASM bundle actually backs each tool. `footprint_editor` and
|
||||
* `symbol_editor` are the SAME compiled engine as `pcbnew` / `eeschema` — they
|
||||
* were only ever a second launcher pinned to a different build-time frame — so
|
||||
* after editor-unification they load the parent bundle and select their frame at
|
||||
* runtime (see `TOOL_FRAME`). Every other tool backs its own bundle. Used to
|
||||
* resolve the CDN asset folder and the `<bundle>.{wasm,js}` filenames.
|
||||
*/
|
||||
export const TOOL_BUNDLE: Record<Tool, Tool> = {
|
||||
pcbnew: "pcbnew",
|
||||
eeschema: "eeschema",
|
||||
calculator: "calculator",
|
||||
pl_editor: "pl_editor",
|
||||
symbol_editor: "eeschema",
|
||||
footprint_editor: "pcbnew",
|
||||
gerbview: "gerbview",
|
||||
};
|
||||
|
||||
/**
|
||||
* The frame token passed to the WASM launcher via `Module.arguments`
|
||||
* (`--frame=<token>`, parsed in `kicad/common/single_top.cpp`) so a shared bundle
|
||||
* opens a specific editor frame. Tools whose bundle already defaults to the right
|
||||
* frame need no token (`undefined`). Tokens mirror `kicad/kicad.cpp`'s `--frame`
|
||||
* parser, plus `symedit` for the symbol editor (which upstream's CLI lacks).
|
||||
*/
|
||||
export const TOOL_FRAME: Record<Tool, string | undefined> = {
|
||||
pcbnew: undefined,
|
||||
eeschema: undefined,
|
||||
calculator: undefined,
|
||||
pl_editor: undefined,
|
||||
symbol_editor: "symedit",
|
||||
footprint_editor: "fpedit",
|
||||
gerbview: undefined,
|
||||
};
|
||||
|
||||
/**
|
||||
* Every standalone tool here boots through common/single_top.cpp, which runs
|
||||
* STARTWIZARD::CheckAndRun() — the first-run "KiCad Setup" wizard. It shows
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { Tool } from "@pcbjam/shared";
|
||||
import { WASM_MANIFEST_FILE, WASM_ROOT } from "@/lib/config";
|
||||
import { TOOL_BUNDLE } from "./constants";
|
||||
|
||||
/**
|
||||
* Resolve the per-tool WASM asset base at runtime from the CDN release manifest.
|
||||
|
|
@ -42,10 +43,14 @@ export async function resolveWasmBase(
|
|||
): Promise<string> {
|
||||
if (override) return override.replace(/\/+$/, "");
|
||||
if (!WASM_MANIFEST_FILE) return WASM_ROOT; // flat (dev / same-origin)
|
||||
// A tool may be served by a shared bundle (footprint_editor→pcbnew,
|
||||
// symbol_editor→eeschema); resolve the folder/version of the bundle, not the
|
||||
// logical tool (the merged bundles are the only ones published).
|
||||
const bundle = TOOL_BUNDLE[tool] ?? tool;
|
||||
const manifest = await loadManifest();
|
||||
const ver = manifest.tools?.[tool];
|
||||
const ver = manifest.tools?.[bundle];
|
||||
if (!ver) {
|
||||
throw new Error(`no WASM version for "${tool}" in ${WASM_MANIFEST_FILE}`);
|
||||
throw new Error(`no WASM version for "${bundle}" in ${WASM_MANIFEST_FILE}`);
|
||||
}
|
||||
return `${WASM_ROOT}/${tool}/${ver}`;
|
||||
return `${WASM_ROOT}/${bundle}/${ver}`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue