diff --git a/.jules/bolt.md b/.jules/bolt.md index d37a631..c92ba7f 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -1,3 +1,7 @@ ## 2024-05-23 - Throttling High-Frequency Events **Learning:** High-frequency events like `mousemove` can cause performance issues if they trigger expensive DOM updates or calculations on every firing. **Action:** Use `requestAnimationFrame` to throttle these updates to the screen refresh rate. Ensure that the event data (like coordinates) is captured synchronously in the event handler so that the rAF callback has access to the latest state. + +## 2024-05-24 - Spatial Culling for LiteGraph Nodes +**Learning:** LiteGraph nodes expose `pos` and `size` properties in graph coordinates. Iterating over all nodes and accessing their DOM widgets (e.g., via `getBoundingClientRect`) causes massive layout thrashing and performance degradation when the graph is large. +**Action:** Always pre-calculate the visible graph area (converting screen/mouse coordinates to graph coordinates) and filter nodes using simple AABB overlap checks before accessing any DOM elements. This reduces O(N) DOM reads to O(K) where K is the number of visible nodes. diff --git a/src/magnify-glass/MagnifyGlass.ts b/src/magnify-glass/MagnifyGlass.ts index 9744115..7e254f2 100644 --- a/src/magnify-glass/MagnifyGlass.ts +++ b/src/magnify-glass/MagnifyGlass.ts @@ -380,7 +380,40 @@ export class MagnifyGlass { const nodes: ComfyNode[] = graph._nodes; if (!nodes) return; + // Calculate source region in graph coordinates for culling + // This mirrors calculateSourceRegion() but stops at graph coords + const rect = this.litegraphCanvas.getBoundingClientRect(); + const dpr = rect.width > 0 ? this.litegraphCanvas.width / rect.width : 1; + const cursorCssX = this.state.x / dpr; + const cursorCssY = this.state.y / dpr; + const cursorGraphX = (cursorCssX - this.state.canvasOffsetX) / this.state.canvasScale; + const cursorGraphY = (cursorCssY - this.state.canvasOffsetY) / this.state.canvasScale; + const targetGraphCenterX = cursorGraphX + this.config.offsetX; + const targetGraphCenterY = cursorGraphY + this.config.offsetY; + const sourceGraphWidth = (this.config.glassSize / this.config.zoomFactor) / this.state.canvasScale; + const sourceGraphHeight = (this.config.glassSize / this.config.zoomFactor) / this.state.canvasScale; + + const sourceGraphRect = { + x: targetGraphCenterX - (sourceGraphWidth / 2), + y: targetGraphCenterY - (sourceGraphHeight / 2), + width: sourceGraphWidth, + height: sourceGraphHeight + }; + for (const node of nodes) { + // Optimization: Cull nodes that are not in the source region + if (node.pos && node.size) { + const nodeRect = { + x: node.pos[0], + y: node.pos[1], + width: node.size[0], + height: node.size[1] + }; + if (!rectsOverlap(sourceGraphRect, nodeRect)) { + continue; + } + } + const widgets = (node as any).widgets; if (!widgets) continue;