Skip to content

Commit 755e64e

Browse files
committed
perf: cap the viewport culling margin in graph units
The culling margin was a fraction of the viewport, which is screen-space, so dividing it by the zoom made it cover more and more of the graph as the user zoomed out. At minimum zoom it more than doubled the queried area and mounted far more nodes than a pan could ever reach before the next recompute - 2012 of 3003 nodes on a large graph. Cap it at roughly five node widths, which is ample lead time at any zoom. Working zoom is unaffected: the cap only binds once the ratio exceeds it, which happens below roughly 0.35 zoom. Panning a 3000-node graph at minimum zoom: 141.2ms -> 78.7ms median frame, 2012 -> 910 nodes mounted This does not help when the whole graph is already on screen; no margin change can cull nodes that are genuinely in view.
1 parent b94ef00 commit 755e64e

2 files changed

Lines changed: 31 additions & 6 deletions

File tree

src/composables/graph/useViewportCulling.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ describe('getCullingBounds', () => {
7373
})
7474
})
7575

76+
it('caps the margin in graph units when zoomed out', () => {
77+
// At z=0.05 the ratio alone would add 10000 units of margin per edge.
78+
const bounds = getCullingBounds({ x: 0, y: 0, z: 0.05 }, VIEWPORT, 0.5)
79+
80+
expect(bounds.x).toBe(-2000)
81+
expect(bounds.width).toBe(VIEWPORT.width / 0.05 + 4000)
82+
})
83+
7684
it('accounts for pan offset and zoom', () => {
7785
// At z=2 the viewport covers half as much graph space.
7886
expect(getCullingBounds({ x: -100, y: -50, z: 2 }, VIEWPORT, 0)).toEqual({

src/composables/graph/useViewportCulling.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ import type { Bounds, NodeId } from '@/renderer/core/layout/types'
2222
/** Extra coverage beyond each viewport edge, as a fraction of viewport size. */
2323
const VIEWPORT_MARGIN_RATIO = 0.5
2424

25+
/**
26+
* Ceiling on the margin, in graph units.
27+
*
28+
* The ratio above is screen-relative, so dividing it by the zoom makes the
29+
* margin cover more and more of the graph as the user zooms out - at minimum
30+
* zoom it more than doubles the queried area, pulling in far more nodes than a
31+
* pan could reach before the next recompute. Roughly five node widths is ample
32+
* lead time at any zoom.
33+
*/
34+
const MAX_MARGIN_GRAPH_UNITS = 2000
35+
2536
/** Grace period before unmounting nodes that left the viewport. */
2637
const UNMOUNT_DELAY_MS = 250
2738

@@ -57,14 +68,20 @@ export function getCullingBounds(
5768
marginRatio = VIEWPORT_MARGIN_RATIO
5869
): Bounds {
5970
const scale = camera.z || 1
60-
const marginX = viewport.width * marginRatio
61-
const marginY = viewport.height * marginRatio
71+
const marginX = Math.min(
72+
(viewport.width * marginRatio) / scale,
73+
MAX_MARGIN_GRAPH_UNITS
74+
)
75+
const marginY = Math.min(
76+
(viewport.height * marginRatio) / scale,
77+
MAX_MARGIN_GRAPH_UNITS
78+
)
6279

6380
return {
64-
x: -marginX / scale - camera.x,
65-
y: -marginY / scale - camera.y,
66-
width: (viewport.width + marginX * 2) / scale,
67-
height: (viewport.height + marginY * 2) / scale
81+
x: -marginX - camera.x,
82+
y: -marginY - camera.y,
83+
width: viewport.width / scale + marginX * 2,
84+
height: viewport.height / scale + marginY * 2
6885
}
6986
}
7087

0 commit comments

Comments
 (0)