refactor(joint-react): immutable lazy Map-backed cells container (O(1) commits)#3423
Open
samuelgja wants to merge 1 commit into
Open
refactor(joint-react): immutable lazy Map-backed cells container (O(1) commits)#3423samuelgja wants to merge 1 commit into
samuelgja wants to merge 1 commit into
Conversation
…erformance and clarity - Introduced a pending change set in graph projection to batch updates and reduce unnecessary re-renders. - Replaced direct cell manipulation with staged writes and removals to optimize incremental updates. - Updated the container interface to support batch updates, improving efficiency during state changes. - Refactored subscription methods for better clarity and performance. - Adjusted the way cells are synchronized from the graph to ensure consistency and reduce overhead. - Cleaned up code formatting and improved readability in various components.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Rewrites the
@joint/reactcells container from a mutable array-of-immutable-items to a lazy, memoised,Map-backed immutable snapshot, and rewires the reactive layer around it.Container (
state-container.ts)Map<CellId, Cell>is the single source of truth:get(id)/has(id)/getSize()are O(1).getSnapshot()buildsArray.from(byId.values())lazily and memoises it;batchSet(changeSet)mutates only the Map and invalidates the memo. Commits are O(change-set), never O(n) — no array copy per commit. The first consumer to read after a commit builds the array once; every other consumer shares that reference (so React never tears). If nothing reads it (a drag with only per-id subscribers), it is never built.getIds()is a separately-memoised id list, invalidated only on add/remove — so "which cells exist" readers do no work during a drag.set/delete/reset/commitChanges/getVersion/subscribeToSizeand theindexById/itemsbookkeeping.Reactive layer
graph-projectionaccumulates one{ added, changed, removed }change set per commit and flushes viabatchSet, preserving the existingdeferCommit/ batch timing.[...getAll()]copies and hold the immutable reference (onCellsChange;useGraphTransactionrollback: O(n) copy → O(1) ref-hold).useContainerKeysdeleted; the portal id-list uses a small memoiseduseCellIds(). The hand-rolleduseSyncExternalStorecell subscriptions (useElementDataSnapshot,useUnresolvedElement,LinkItem) now reuseuseCells.useCellsuses a per-id snapshot token, so single-cell forms only re-read when that cell changes.Motivation and Context
The container previously handed out a live mutable array, forcing every consumer into a defensive
[...getAll()]copy. The goal was a fully immutable snapshot (stable refs consumers can hand straight to React state, no copies) without regressing the O(1) drag hot path — a naive "rebuild the array every commit" immutable design regresses to O(n)/commit; the lazy memoised design avoids that.Benchmarks (back-to-back on one machine, old vs new)
batchSetis flat at ~0.4µs from 100 → 100,000 cells. At 1,000,000 nodes, updating 50 cells = 11µs (O(change-set), independent of graph size).onCellsChangefull-array re-push →graph.syncCells(N), which is inherently O(N) and equal old-vs-new (within jsdom noise). Controlled apps that consumeonIncrementalCellsChange(the delta) stay O(1).Net: immutable + simpler + uncontrolled ~2× faster + container commits O(1), no regression.
Correctness
changedbucket leftgetIds()stale, so its portal would not render until the next structural commit (getSnapshot/getIdstearing).@joint/react: 1017 tests ·@joint/react-plus: 235 tests · typecheck + lint + knip green.Screenshots (if appropriate):
N/A (headless library).