Skip to content

Commit 25d1452

Browse files
[perf] add batch update method to spatial index - addresses review feedback
Optimizes batch node bounds updates by eliminating redundant cache invalidations. Previously each update() call invalidated the cache, causing O(n) cache clears for n nodes. Now batchUpdate() performs all updates and invalidates once, reducing overhead for large batch operations like node paste or multi-node resize.
1 parent 1ebde68 commit 25d1452

File tree

3 files changed

+56
-2
lines changed

3 files changed

+56
-2
lines changed

src/renderer/core/layout/store/layoutStore.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,23 +1103,29 @@ class LayoutStoreImpl implements LayoutStore {
11031103
operation: BatchUpdateBoundsOperation,
11041104
change: LayoutChange
11051105
): void {
1106+
const spatialUpdates: Array<{ nodeId: NodeId; bounds: Bounds }> = []
1107+
11061108
for (const nodeId of operation.nodeIds) {
11071109
const data = operation.bounds[nodeId]
11081110
const ynode = this.ynodes.get(nodeId)
11091111
if (!ynode || !data) continue
11101112

1111-
this.spatialIndex.update(nodeId, data.bounds)
1112-
11131113
ynode.set('position', { x: data.bounds.x, y: data.bounds.y })
11141114
ynode.set('size', {
11151115
width: data.bounds.width,
11161116
height: data.bounds.height
11171117
})
11181118
ynode.set('bounds', data.bounds)
11191119

1120+
spatialUpdates.push({ nodeId, bounds: data.bounds })
11201121
change.nodeIds.push(nodeId)
11211122
}
11221123

1124+
// Batch update spatial index for better performance
1125+
if (spatialUpdates.length > 0) {
1126+
this.spatialIndex.batchUpdate(spatialUpdates)
1127+
}
1128+
11231129
if (change.nodeIds.length) {
11241130
change.type = 'update'
11251131
}

src/renderer/core/spatial/SpatialIndex.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ export class SpatialIndexManager {
5555
this.invalidateCache()
5656
}
5757

58+
/**
59+
* Batch update multiple nodes' bounds in the spatial index
60+
* More efficient than calling update() multiple times as it only invalidates cache once
61+
*/
62+
batchUpdate(updates: Array<{ nodeId: NodeId; bounds: Bounds }>): void {
63+
for (const { nodeId, bounds } of updates) {
64+
this.quadTree.update(nodeId, bounds)
65+
}
66+
this.invalidateCache()
67+
}
68+
5869
/**
5970
* Remove a node from the spatial index
6071
*/

vue-litegraph-integration-plan.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Vue Nodes & LiteGraph Interaction Initiatives
2+
3+
## Initiative 1: CRDT Layout Sync Reliability
4+
- **Goal:** Ensure LiteGraph node geometry stays aligned with Vue-managed DOM so ghost hit targets never occur.
5+
- **Scope:**
6+
- Update `layoutStore.batchUpdateNodeBounds` to surface changes through the existing `applyOperation`/`notifyChange` pipeline (or emit an equivalent `LayoutChange`).
7+
- Verify downstream listeners (`useLayoutSync`, link/slot sync) respond when DOM-driven measurements land.
8+
- Add regression coverage: unit test or integration harness exercising ResizeObserver-driven updates, asserting LiteGraph node.pos keeps pace.
9+
- Document behavior in layout store comments to prevent future bypasses.
10+
- **Deliverables:** Code changes + tests + release notes entry summarizing the fix.
11+
- **Risks/Mitigations:**
12+
- _Risk:_ Double-applying operations if observers already react elsewhere. _Mitigation:_ Audit listeners to ensure they only consume change events once.
13+
- _Risk:_ Performance regressions from extra change objects. _Mitigation:_ Benchmark batch updates on large graphs before merge.
14+
- **Next Steps:**
15+
1. Implement the change in a PR branch (e.g. `fix/layout-batch-change`).
16+
2. Run `pnpm test:unit` and relevant integration checks.
17+
3. Prepare PR with reproduction + resolution notes.
18+
19+
## Initiative 2: LiteGraph Pointer Handling Trim in Vue Mode
20+
- **Goal:** Let Vue-driven nodes fully own node-level pointer interactions while LiteGraph keeps canvas-wide controls.
21+
- **Scope:**
22+
- Introduce guard(s) in `LGraphCanvas` so node-specific handlers (`processMouseDown`, `#processNodeClick`, link slot targeting) early-exit when `LiteGraph.vueNodesMode` is true unless explicitly forwarded from Vue.
23+
- Optionally validate node hits against `layoutStore` before acting, reducing reliance on LiteGraph’s stale geometry.
24+
- Ensure link creation, reroute edits, groups, background panning remain intact.
25+
- Add targeted tests (unit or e2e) covering Vue mode click/drag behavior on empty canvas, node body, slots.
26+
- Update developer docs describing the event division of responsibilities.
27+
- **Deliverables:** Code adjustments + automated tests + doc updates.
28+
- **Risks/Mitigations:**
29+
- _Risk:_ Breaking legacy fallback when Vue nodes are disabled. _Mitigation:_ Gate new logic strictly on `LiteGraph.vueNodesMode`.
30+
- _Risk:_ Missing a pathway (e.g., double-click, middle-click). _Mitigation:_ Audit event map and add assertions/logging for unexpected fall-through.
31+
- **Next Steps:**
32+
1. After Initiative 1 merges, branch (e.g. `feature/vue-node-pointer-guard`).
33+
2. Implement guards + tests.
34+
3. Validate with manual smoke test in Vue mode and legacy mode.
35+
36+
---
37+
*Prepared October 6, 2025.*

0 commit comments

Comments
 (0)