Performance Node Graph während Panning #292

Closed
opened 2026-10-02 07:27:51 +02:00 by admin · 1 comment

Wie im Titel schon beschrieben: wenn es viele Nodes gibt, dann droppen die FPS wenn ich mit Mittelklick-Drag (Panning) durchführe. Noch schlimmer wird es wenn ich ein einzelnes Node bewege (da wiederum vermute ich, dass es einen kompletten re-render oder so gibt und geben muss, somit befürchte ich, dass sich das nicht verhindern lässt)

Wie im Titel schon beschrieben: wenn es viele Nodes gibt, dann droppen die FPS wenn ich mit Mittelklick-Drag (Panning) durchführe. Noch schlimmer wird es wenn ich ein einzelnes Node bewege (da wiederum vermute ich, dass es einen kompletten re-render oder so gibt und geben muss, somit befürchte ich, dass sich das nicht verhindern lässt)
admin added this to the Development project 2026-10-02 07:27:51 +02:00
admin moved this to Next in Development on 2026-10-02 07:27:56 +02:00
admin added the high-priority label 2026-10-02 08:19:01 +02:00
Owner

Done in 6d79cf5, wiki 4ff5495 (Architecture, Development).

What changes
Both gestures were slow for the same reason: every pointer move rebuilt every box, every text and every curve of the graph. The guess in the issue, that dragging one node needs a complete redraw, does not hold. It needs the node and its links.

  • Panning and zooming move the picture. Nothing of the graph is drawn again, only the grid and the rubber band.
  • A node is rebuilt only when what it shows has changed (title, values, problem, ports, selection). One node dragged used to remake around 1500 texts per frame in a graph of 388 nodes.
  • Dragging a node moves its view and draws the links again. The project counts as changed on release, so the panels no longer rebuild on every frame of the drag.
  • Nodes outside the picture are not drawn.

Measured
In the CI image, with a behavior of 97 nodes and 135 links taken from a real project and with four copies of it (388 nodes, 540 links), fitted to the canvas, 240 pointer moves 16 ms apart:

before after
pan, 97 nodes: script per move 6.7 ms 1.9 ms
pan, 388 nodes: script per move 25.2 ms 2.0 ms
pan, 388 nodes: frames per second 4.4 18.7
drag a node, 97 nodes: frames per second 4.4 33.4
drag a node, 388 nodes: frames per second 2.7 22.4
the still picture, 388 nodes: frames per second 9 21
  • The frame rates are those of the test browser, whose GPU sits behind WSL's d3d12 driver. The script times are the part that carries over to another machine. Nothing was measured on a desktop screen.
  • Not solved: the still picture of 388 nodes stays at 21 frames per second there. What is left is the drawing itself (links, masks, the panels around the graph).

Decided here

  • A drag in the graph raises no project event until release. Where a node stands concerns no panel, no run and no picture. Anything that would want to follow a node while it is dragged does not get the chance.
  • A node counts as in the picture with 12 px around its box, for the shadow, the pins and the ring of a drop target.
  • The frame rate of the still picture was left as it is, see above.

Under the hood

  • GraphCanvas.applyView no longer calls redraw(). cull() sets visible by inView from graphView.ts, drawBand() draws the rubber band and the drop mark on their own.
  • NodeView keeps a signature of everything redraw reads and one of everything rebuildTexts reads, and returns early while they hold.
  • GraphDrags asks the canvas once a frame to follow (GraphCanvasHost.moved) instead of state.preview().
  • The graph is a render group, box and pins set batchMode = 'batch': 474 draw calls a frame became 160. The mask sits on a container above the group.
  • __gm.graph.made() and __gm.graph.nodesDrawn() are what a check can see of it.

Tests

gate (melektaus-atx)   207 files, 3121 tests, CHECK EXIT 0 (3114 + 7)
e2e, 14 modules        542 PASS, 1 FAILED, E2E EXIT 1
e2e 51 alone           ALL PASSED - 12 checks
  • The modules were 4, 6, 18, 19, 20, 23, 24, 26, 28, 31, 36, 43, 51 and 53. The one red check is not from this change: module 51 (clipboard) does not find the menu entry "Paste" when it runs right after module 43, which leaves the scene in another state. The suite never runs them in that order, and 43,51 fails the same way on 32a0af6 without this change. No issue was opened for it.
  • No full run and no smokes. The sum of 1660 (1656 + 4) in README and wiki is reckoned. The CI workflow is on again: a run for 6d79cf5 was started with the push (tasks 434 and 435) and was not waited for. The commit message still says it is switched off.
  • Seven unit tests: six for inView in tests/editor/graph-view.test.ts, one in frame-preview.test.ts (a drag announces nothing).
  • Four e2e checks with a real mouse in block 6z-xii of module 06: pan, drag, zoom in and fit, a changed value.
  • Nothing was looked at in the browser after the build.
Counter-check Result
7 in the browser all work, 2 to 3 checks fall each
Done in `6d79cf5`, wiki `4ff5495` (*Architecture*, *Development*). **What changes** Both gestures were slow for the same reason: every pointer move rebuilt every box, every text and every curve of the graph. The guess in the issue, that dragging one node needs a complete redraw, does not hold. It needs the node and its links. - **Panning and zooming move the picture.** Nothing of the graph is drawn again, only the grid and the rubber band. - **A node is rebuilt only when what it shows has changed** (title, values, problem, ports, selection). One node dragged used to remake around 1500 texts per frame in a graph of 388 nodes. - **Dragging a node** moves its view and draws the links again. The project counts as changed on release, so the panels no longer rebuild on every frame of the drag. - **Nodes outside the picture are not drawn.** **Measured** In the CI image, with a behavior of 97 nodes and 135 links taken from a real project and with four copies of it (388 nodes, 540 links), fitted to the canvas, 240 pointer moves 16 ms apart: | | before | after | |---|---|---| | pan, 97 nodes: script per move | 6.7 ms | 1.9 ms | | pan, 388 nodes: script per move | 25.2 ms | 2.0 ms | | pan, 388 nodes: frames per second | 4.4 | 18.7 | | drag a node, 97 nodes: frames per second | 4.4 | 33.4 | | drag a node, 388 nodes: frames per second | 2.7 | 22.4 | | the still picture, 388 nodes: frames per second | 9 | 21 | - The frame rates are those of the test browser, whose GPU sits behind WSL's d3d12 driver. The script times are the part that carries over to another machine. Nothing was measured on a desktop screen. - **Not solved:** the still picture of 388 nodes stays at 21 frames per second there. What is left is the drawing itself (links, masks, the panels around the graph). **Decided here** - **A drag in the graph raises no project event until release.** Where a node stands concerns no panel, no run and no picture. Anything that would want to follow a node while it is dragged does not get the chance. - **A node counts as in the picture with 12 px around its box**, for the shadow, the pins and the ring of a drop target. - **The frame rate of the still picture was left as it is**, see above. **Under the hood** - `GraphCanvas.applyView` no longer calls `redraw()`. `cull()` sets `visible` by `inView` from `graphView.ts`, `drawBand()` draws the rubber band and the drop mark on their own. - `NodeView` keeps a signature of everything `redraw` reads and one of everything `rebuildTexts` reads, and returns early while they hold. - `GraphDrags` asks the canvas once a frame to follow (`GraphCanvasHost.moved`) instead of `state.preview()`. - The graph is a render group, box and pins set `batchMode = 'batch'`: 474 draw calls a frame became 160. The mask sits on a container above the group. - `__gm.graph.made()` and `__gm.graph.nodesDrawn()` are what a check can see of it. **Tests** ``` gate (melektaus-atx) 207 files, 3121 tests, CHECK EXIT 0 (3114 + 7) e2e, 14 modules 542 PASS, 1 FAILED, E2E EXIT 1 e2e 51 alone ALL PASSED - 12 checks ``` - The modules were 4, 6, 18, 19, 20, 23, 24, 26, 28, 31, 36, 43, 51 and 53. **The one red check is not from this change:** module 51 (clipboard) does not find the menu entry "Paste" when it runs right after module 43, which leaves the scene in another state. The suite never runs them in that order, and `43,51` fails the same way on `32a0af6` without this change. No issue was opened for it. - No full run and no smokes. The sum of 1660 (1656 + 4) in README and wiki is reckoned. The CI workflow is on again: a run for `6d79cf5` was started with the push (tasks 434 and 435) and was not waited for. The commit message still says it is switched off. - Seven unit tests: six for `inView` in `tests/editor/graph-view.test.ts`, one in `frame-preview.test.ts` (a drag announces nothing). - Four e2e checks with a real mouse in block 6z-xii of module 06: pan, drag, zoom in and fit, a changed value. - Nothing was looked at in the browser after the build. | Counter-check | Result | |---|---| | 7 in the browser | all work, 2 to 3 checks fall each |
admin moved this to Done in Development on 2026-10-02 10:19:02 +02:00
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: meleki/feuerwerk#292