From 7e454104aaa6678329650e76ab4c53dcab4ee583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gerg=C5=91=20T=C3=B6rcsv=C3=A1ri?= Date: Fri, 12 Jun 2026 16:08:52 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20libs=200002=20spike=20=E2=80=94=20kicad?= =?UTF-8?q?Libs=20provider=20stub=20+=20lib-table=20seeding;=20bump=20kica?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- kicad | 2 +- web/standalone/src/wasm/boot.ts | 11 +- .../src/wasm/libs/spike-provider.ts | 124 ++++++++++++++++++ 3 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 web/standalone/src/wasm/libs/spike-provider.ts diff --git a/kicad b/kicad index 693c513..378b502 160000 --- a/kicad +++ b/kicad @@ -1 +1 @@ -Subproject commit 693c513293e8706c0c9d2a0304ea098cfd391864 +Subproject commit 378b502e27bb84d2135464691abbfe5dc74661e3 diff --git a/web/standalone/src/wasm/boot.ts b/web/standalone/src/wasm/boot.ts index 3ff2279..a00581d 100644 --- a/web/standalone/src/wasm/boot.ts +++ b/web/standalone/src/wasm/boot.ts @@ -5,6 +5,10 @@ import { TOOL_ARGV0, TOOL_NEEDS_CONFIG_SEED, } from "./constants"; +import { + installSpikeLibsProvider, + SPIKE_LIB_TABLE_ROW, +} from "./libs/spike-provider"; /** * Boot a KiCad tool directly in the main React document — no iframe. @@ -88,6 +92,9 @@ async function doBoot(opts: BootOptions): Promise { // mismatches the viewport, breaking the whole AUI layout (toolbars/panels). (w as unknown as { mainWindow: HTMLElement }).mainWindow = container; + // libs 0002 spike: must exist before any plugin call can suspend on it. + installSpikeLibsProvider(log); + onStatus("Downloading…"); // Prefetch images.tar.gz in parallel with the (much larger) wasm download — @@ -174,9 +181,11 @@ async function doBoot(opts: BootOptions): Promise { 2, ), ); + // libs 0002 spike: one remote lib row; the PCBJAM plugin resolves it + // through window.kicadLibs (installed in doBoot). writeIfAbsent( `${KICAD_CONFIG_DIR}/sym-lib-table`, - "(sym_lib_table\n (version 7)\n)\n", + `(sym_lib_table\n (version 7)\n${SPIKE_LIB_TABLE_ROW}\n)\n`, ); writeIfAbsent( `${KICAD_CONFIG_DIR}/fp-lib-table`, diff --git a/web/standalone/src/wasm/libs/spike-provider.ts b/web/standalone/src/wasm/libs/spike-provider.ts new file mode 100644 index 0000000..7d33d60 --- /dev/null +++ b/web/standalone/src/wasm/libs/spike-provider.ts @@ -0,0 +1,124 @@ +/** + * libs 0002 spike: a static JS-side library provider. + * + * The kicad fork's SCH_IO_PCBJAM_LIB plugin (lib-table type "PCBJAM") calls + * `globalThis.kicadLibs.request(op, lib, arg)` through an Asyncify suspension + * (EM_ASYNC_JS) and blocks the C++ stack until the returned promise resolves: + * + * request("list", "pcbjam://spike", "") -> JSON {"symbols": [...]} + * request("get", "pcbjam://spike", "R") -> full kicad_symbol_lib s-expr + * document holding that symbol + * + * This stub serves two handcrafted symbols from memory — no server. An + * artificial delay (`?libdelay=1500` in the URL) exercises the reentrancy + * question: the tab must stay alive while C++ is suspended mid-fetch. + */ + +export const SPIKE_LIB_NICKNAME = "pcbjam-spike"; +export const SPIKE_LIB_URI = "pcbjam://spike"; + +/** sym-lib-table row seeded at boot (see boot.ts seedKicadConfig). */ +export const SPIKE_LIB_TABLE_ROW = ` (lib (name "${SPIKE_LIB_NICKNAME}")(type "PCBJAM")(uri "${SPIKE_LIB_URI}")(options "")(descr "pcbjam libs 0002 spike"))`; + +const wrapLib = (symbolBody: string) => + `(kicad_symbol_lib (version 20241209) (generator "pcbjam") (generator_version "0.1") +${symbolBody} +) +`; + +const SYMBOLS: Record = { + R: ` (symbol "R" (pin_numbers hide) (pin_names (offset 0)) (exclude_from_sim no) (in_bom yes) (on_board yes) + (property "Reference" "R" (at 2.032 0 90) (effects (font (size 1.27 1.27)))) + (property "Value" "R" (at 0 0 90) (effects (font (size 1.27 1.27)))) + (property "Footprint" "" (at -1.778 0 90) (effects (font (size 1.27 1.27)) hide)) + (property "Datasheet" "~" (at 0 0 0) (effects (font (size 1.27 1.27)) hide)) + (property "Description" "Resistor (pcbjam spike)" (at 0 0 0) (effects (font (size 1.27 1.27)) hide)) + (property "ki_keywords" "R res resistor" (at 0 0 0) (effects (font (size 1.27 1.27)) hide)) + (symbol "R_0_1" + (rectangle (start -1.016 -2.54) (end 1.016 2.54) (stroke (width 0.254) (type default)) (fill (type none))) + ) + (symbol "R_1_1" + (pin passive line (at 0 3.81 270) (length 1.27) + (name "~" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27))))) + (pin passive line (at 0 -3.81 90) (length 1.27) + (name "~" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27))))) + ) + )`, + C: ` (symbol "C" (pin_numbers hide) (pin_names (offset 0.254)) (exclude_from_sim no) (in_bom yes) (on_board yes) + (property "Reference" "C" (at 0.635 2.54 0) (effects (font (size 1.27 1.27)) (justify left))) + (property "Value" "C" (at 0.635 -2.54 0) (effects (font (size 1.27 1.27)) (justify left))) + (property "Footprint" "" (at 0.9652 -3.81 0) (effects (font (size 1.27 1.27)) hide)) + (property "Datasheet" "~" (at 0 0 0) (effects (font (size 1.27 1.27)) hide)) + (property "Description" "Capacitor (pcbjam spike)" (at 0 0 0) (effects (font (size 1.27 1.27)) hide)) + (property "ki_keywords" "cap capacitor" (at 0 0 0) (effects (font (size 1.27 1.27)) hide)) + (symbol "C_0_1" + (polyline (pts (xy -2.032 -0.762) (xy 2.032 -0.762)) (stroke (width 0.508) (type default)) (fill (type none))) + (polyline (pts (xy -2.032 0.762) (xy 2.032 0.762)) (stroke (width 0.508) (type default)) (fill (type none))) + ) + (symbol "C_1_1" + (pin passive line (at 0 3.81 270) (length 2.794) + (name "~" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27))))) + (pin passive line (at 0 -3.81 90) (length 2.794) + (name "~" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27))))) + ) + )`, +}; + +export type KicadLibsRequest = ( + op: string, + lib: string, + arg: string, +) => Promise; + +declare global { + interface Window { + kicadLibs?: { request: KicadLibsRequest }; + } +} + +function artificialDelayMs(): number { + const raw = new URLSearchParams(window.location.search).get("libdelay"); + const n = raw ? Number(raw) : 0; + return Number.isFinite(n) && n > 0 ? n : 0; +} + +const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); + +/** + * KiCad's lib-table path handling normalizes URIs as if they were file paths: + * "pcbjam://spike" arrives as "/pcbjam:/spike" or even + * "/pcbjam:/spike" (made absolute against the cwd). Recover the + * lib id after the "pcbjam:" marker. Finding for 0003: give the real plugin a + * path shape that survives normalization, or normalize C++-side. + */ +function libIdFromMangledUri(lib: string): string | null { + const m = lib.match(/pcbjam:\/*(.+)$/); + return m ? m[1] : null; +} + +export function installSpikeLibsProvider(log: (msg: string) => void): void { + if (window.kicadLibs) return; + + const delay = artificialDelayMs(); + + const request: KicadLibsRequest = async (op, lib, arg) => { + log(`[libs] request op=${op} lib=${lib} arg=${arg} (delay=${delay}ms)`); + + if (libIdFromMangledUri(lib) !== "spike") return null; + if (delay) await sleep(delay); + + switch (op) { + case "list": + return JSON.stringify({ symbols: Object.keys(SYMBOLS) }); + case "get": { + const body = SYMBOLS[arg]; + return body ? wrapLib(body) : null; + } + default: + return null; + } + }; + + window.kicadLibs = { request }; + log(`[libs] spike provider installed (${SPIKE_LIB_URI})`); +}