From ed7a4e9c2459ebf9b12a072eddd6dd89510eccc0 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Wed, 10 Sep 2025 19:02:48 -0700 Subject: [PATCH] Improve churn --- src/renderer/core/layout/store/layoutStore.ts | 66 +++++++------------ src/renderer/core/layout/utils/geometry.ts | 11 ++++ 2 files changed, 34 insertions(+), 43 deletions(-) create mode 100644 src/renderer/core/layout/utils/geometry.ts diff --git a/src/renderer/core/layout/store/layoutStore.ts b/src/renderer/core/layout/store/layoutStore.ts index 72786583b..99f807ffd 100644 --- a/src/renderer/core/layout/store/layoutStore.ts +++ b/src/renderer/core/layout/store/layoutStore.ts @@ -39,6 +39,7 @@ import { type Size, type SlotLayout } from '@/renderer/core/layout/types' +import { sameBounds, samePoint } from '@/renderer/core/layout/utils/geometry' import { SpatialIndexManager } from '@/renderer/core/spatial/SpatialIndex' type YEventChange = { @@ -413,12 +414,8 @@ class LayoutStoreImpl implements LayoutStore { // Short-circuit if bounds and centerPos unchanged if ( existing && - existing.bounds.x === layout.bounds.x && - existing.bounds.y === layout.bounds.y && - existing.bounds.width === layout.bounds.width && - existing.bounds.height === layout.bounds.height && - existing.centerPos.x === layout.centerPos.x && - existing.centerPos.y === layout.centerPos.y + sameBounds(existing.bounds, layout.bounds) && + samePoint(existing.centerPos, layout.centerPos) ) { // Only update path if provided (for hit detection) if (layout.path) { @@ -457,6 +454,13 @@ class LayoutStoreImpl implements LayoutStore { const existing = this.slotLayouts.get(key) if (existing) { + // Short-circuit if geometry is unchanged + if ( + samePoint(existing.position, layout.position) && + sameBounds(existing.bounds, layout.bounds) + ) { + return + } // Update spatial index this.slotSpatialIndex.update(key, layout.bounds) } else { @@ -475,9 +479,18 @@ class LayoutStoreImpl implements LayoutStore { ): void { if (!updates.length) return - // Update spatial index and map entries + // Update spatial index and map entries (skip unchanged) for (const { key, layout } of updates) { - if (this.slotLayouts.has(key)) { + const existing = this.slotLayouts.get(key) + + if (existing) { + // Short-circuit if geometry is unchanged + if ( + samePoint(existing.position, layout.position) && + sameBounds(existing.bounds, layout.bounds) + ) { + continue + } this.slotSpatialIndex.update(key, layout.bounds) } else { this.slotSpatialIndex.insert(key, layout.bounds) @@ -604,12 +617,8 @@ class LayoutStoreImpl implements LayoutStore { // Short-circuit if bounds and centerPos unchanged (prevents spatial index churn) if ( existing && - existing.bounds.x === layout.bounds.x && - existing.bounds.y === layout.bounds.y && - existing.bounds.width === layout.bounds.width && - existing.bounds.height === layout.bounds.height && - existing.centerPos.x === layout.centerPos.x && - existing.centerPos.y === layout.centerPos.y + sameBounds(existing.bounds, layout.bounds) && + samePoint(existing.centerPos, layout.centerPos) ) { // Only update path if provided (for hit detection) if (layout.path) { @@ -1018,9 +1027,6 @@ class LayoutStoreImpl implements LayoutStore { // Hit detection queries can run before CRDT updates complete this.spatialIndex.update(operation.nodeId, newBounds) - // Update associated slot positions synchronously - this.updateNodeSlotPositions(operation.nodeId, operation.position) - // Then update CRDT ynode.set('position', operation.position) this.updateNodeBounds(ynode, operation.position, size) @@ -1047,9 +1053,6 @@ class LayoutStoreImpl implements LayoutStore { // Hit detection queries can run before CRDT updates complete this.spatialIndex.update(operation.nodeId, newBounds) - // Update associated slot positions synchronously (size changes may affect slot positions) - this.updateNodeSlotPositions(operation.nodeId, position) - // Then update CRDT ynode.set('size', operation.size) this.updateNodeBounds(ynode, position, operation.size) @@ -1330,29 +1333,6 @@ class LayoutStoreImpl implements LayoutStore { } } - /** - * Update slot positions when a node moves - * TODO: This should be handled by the layout sync system (useSlotLayoutSync) - * rather than manually here. For now, we'll mark affected slots as needing recalculation. - */ - private updateNodeSlotPositions(nodeId: NodeId, _nodePosition: Point): void { - // Mark all slots for this node as potentially stale - // The layout sync system will recalculate positions on the next frame - const slotsToRemove: string[] = [] - - for (const [key, slotLayout] of this.slotLayouts) { - if (slotLayout.nodeId === nodeId) { - slotsToRemove.push(key) - } - } - - // Remove from spatial index so they'll be recalculated - for (const key of slotsToRemove) { - this.slotSpatialIndex.remove(key) - this.slotLayouts.delete(key) - } - } - // Helper methods private layoutToYNode(layout: NodeLayout): Y.Map { const ynode = new Y.Map() diff --git a/src/renderer/core/layout/utils/geometry.ts b/src/renderer/core/layout/utils/geometry.ts new file mode 100644 index 000000000..9722dca4f --- /dev/null +++ b/src/renderer/core/layout/utils/geometry.ts @@ -0,0 +1,11 @@ +import type { Bounds, Point } from '@/renderer/core/layout/types' + +export function samePoint(a: Point, b: Point): boolean { + return a.x === b.x && a.y === b.y +} + +export function sameBounds(a: Bounds, b: Bounds): boolean { + return ( + a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height + ) +}