pcbjam/docs/features/ysync-review/12-opt-hot-path-full-model-work.md
Gergő Törcsvári 62ca571802
feat(ysync): lib_symbols travel + layout save-sync (miss 08), TS hot-path opts (12), diff-on-rebind adopt (13) — doc 18
Miss 08A: the binding stores wire-carried (lib_symbols …) definitions in
kdoc_libsymbols and prefixes them on apply wires — a joiner that never saw a
symbol adopts it WITH its definition (new e2e ysync-libsymbols.spec.ts).
Miss 08B: registerSaveHook gains onSavedText; WasmTool routes saved-file text
to syncLayoutToY (per sheet room via the manager's syncLayoutFromSave, or the
single-room doc) so title block / paper / setup edits converge instead of
drifting. Opt 12 (TS half): zod off the observer hot path (yToItemUnchecked),
children index built once per conversion. Opt 13: seed()'s adopt diffs the
editor snapshot against the doc view and applies only the doc-authoritative
difference — clean rebinds apply nothing, the adopt undo entry shrinks to the
real changed set. Opt 14 deliberately deferred (doc 18). All TS-side; no wasm
rebuild (the C++ blob/findLib sides already carried definitions).

Verified: shared 107, standalone 79 (+2 known pre-existing wasm-assets),
ysync e2e 21/21 chromium, collab regression 21/3-skip firefox.

Bumps: web/pcbjam-shared (lib_symbols channel + syncLayoutToY + opts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgThWXtdvrYLK47EDFoGdq
2026-07-06 08:57:58 +02:00

3.6 KiB
Raw Blame History

Optimization 12 — O(full-model) work on every edit, apply, and remote batch

Severity: performance (fine on demo boards; seconds-per-edit territory at 510k items) Status: TS items DONE 2026-07-03 (zod off the observer path, children index once — see 18); C++ item 1 delivered by 17 batch 3 except the scalar-scan/legacy-wire retirement (goes with the legacy-wire removal)

The costs, per hot path

Every local commit (C++):

  • flushDiff runs snapshotByUuiditemToJson for every item on the board/screen (json allocation per item) — plus a full map compare against g_baseline (pcbnew_embind.cpp:546-668, eeschema :374-453).
  • The legacy scalar wire is still built and emitted alongside the v2 wire even though production registers no onDelta listener (the json arrays are constructed regardless; the EM_ASM no-ops).

Every remote apply (C++):

  • rebaseline() at the end of doApply/doApplyItems — another full-model snapshotByUuid.

Every remote batch (TS):

  • The observeDeep handler builds itemsView()yToItem for every item, each going through a zod .parse of its entire body tree (kicad-binding.ts:82-88,116). zod validation dominates; on a large board this is by far the most expensive step, and it runs even for a single-item remote nudge.
  • deltaToItemsWire's coveredByAncestor/renderItem only need the delta's items plus their ancestor chains and descendant subtrees — not the full view.
  • itemsWireToDeltadescendants() rebuilds the full children index per wire item (items-wire.ts:63-83) → O(n·m) for an m-item wire.

Drift check: full scratch save + fileToDoc + yToDoc + docDelta — but only every 50 doc updates and at unload; acceptable as designed. (The beforeunload check is synchronous full-model work and will add visible tab-close latency on big boards — worth a size guard, not a redesign.)

Fixes, in leverage order

  1. Dirty-set + blob-hash diffing on the C++ side — the same change bug 04 needs for correctness. Collect touched-item uuids from the listener callbacks (currently discarded), lift children to roots, and post-settle compare only those roots' blob hashes against a uuid→hash baseline. Replaces both full snapshots (flush AND rebaseline — see 05 for the targeted-rebaseline tie-in) and retires the scalar snapshot + legacy emit entirely. Per-edit cost drops from O(board) to O(edit).
  2. Kill zod on the TS hot path. yToItem's schema parse guards against malformed Y content, but the observer path re-validates the entire model on every batch. Either:
    • cache the materialized KicadItem per item Y.Map (WeakMap keyed by the Y.Map, invalidated from the event's changed keys), so a batch only converts touched items + the subtrees renderItem walks; or
    • validate at trust boundaries only (wire parse already zod-validates; Y reads can use a cheap structural cast) and keep zod for seed/materialize paths.
  3. Scope the view to the delta. deltaToItemsWire needs view for ancestor chains and descendant rendering; build it lazily (resolve items.get(uuid) on demand) instead of materializing the whole map up front.
  4. Index children once per conversion. Build the parent→children map once per itemsWireToDelta call, not per wire item.

Items 24 are contained TS changes; item 1 is the structural one and pairs with the bug-04/05 fixes — do them as one piece of work.