Skip to content

feat!: optimize large graph rendering for 500-node flows - #39

Merged
pavanpodila merged 10 commits into
mainfrom
codex/large-graph-performance
Aug 8, 2026
Merged

pavanpodila merged 10 commits into
mainfrom
codex/large-graph-performance

Conversation

@pavanpodila

@pavanpodila pavanpodila commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

Reworks Vyuh Node Flow's large-graph rendering path around a retained, adaptive scene. The editor keeps rich Flutter widgets where interaction needs them and uses batched painting for the rest of the graph.

The target fixture is 500 nodes and 955 connections at a 120 Hz frame budget (8.33 ms).

User-visible result

  • Panning and zooming large graphs no longer rebuild every node widget on every camera tick.
  • Dense or zoomed-out graphs switch to a retained painted overview.
  • Selected, dragged, resized, or connection-active nodes remain interactive widget overlays.
  • Sticky notes preserve their text in the painted scene; dragging a note never blanks its content.
  • Overview mode retains node tap, selection, double-tap, drag, and pointer-cancel rollback.
  • Ports and connection editing return automatically when the editor leaves overview mode.

Architecture changes

Node scene

  • Adds explicit widgets, navigation, and overview scene modes.
  • Uses the actual on-screen node count for LOD decisions; the off-screen culling preload no longer forces overview mode.
  • Removes empty node layers and idle interaction painters/listeners from the render tree.
  • Narrows MobX Observer boundaries so cursor and resize state do not rebuild complete node subtrees.
  • Adds thumbnailCacheKey for retained thumbnail invalidation and content-faithful custom painting.

Connection scene

  • Resolves immutable connection render snapshots outside CustomPainter.paint.
  • Makes shouldRepaint an O(1) stable-revision comparison.
  • Uses straight, endpoint-free overview paths and bypasses router/path-cache work.
  • Batches static edges by style and spatial tile, capped at 32 contours per batch.
  • Retains unaffected batches across topology changes; selected and animated edges remain isolated.
  • Uses indexed connection lookup in drag and dirty-flush hot paths and culls off-screen labels.

Camera, spatial index, and mutations

  • Separates the per-frame camera signal from committed MobX viewport state.
  • Coalesces spatial culling updates behind hysteresis margins.
  • Reconciles node, port, and connection-segment geometry incrementally instead of clearing and rebuilding the index.
  • Adds nested spatial mutation batching with one outer invalidation.
  • Adds mutateGraph(...) as the synchronous graph transaction boundary.
  • Adds adjacency maps and O(1) indexed node/connection lookup for mutation hot paths.
  • Coarsens dot/cross grid spacing at low zoom to prevent excessive draw calls.

API and maintainability

  • Controller collections are stable, reactive, read-only views.
  • Removes the mutable observable collection getters.
  • Adds mutateNodeData(...) for controller-mediated mutable data changes.
  • Keeps connection label generics typed end-to-end instead of falling back to dynamic.
  • Caches nodesBounds as a reactive computed value.
  • Adds focused regression coverage for scene transitions, pointer cancellation, observer boundaries, spatial reconciliation, snapshot retention, and idle layers.

Breaking changes

  • nodesObservable, connectionsObservable, selectedNodeIdsObservable, and selectedConnectionIdsObservable are removed.
  • nodes, connections, selectedNodeIds, and selectedConnectionIds are read-only live views; mutate through controller APIs.
  • Adaptive LOD is enabled by default with maxInteractiveNodes: 200. Use LodPlugin(enabled: false) for an always-widget scene.
  • viewport is the live camera, while committed reactive state and per-frame camera rendering have separate signals.
  • GridTheme adaptively coarsens spacing; minScreenSpacing defaults to 24 logical pixels.
  • Connection label builders preserve their connection data generic rather than using dynamic.

Performance evidence

Deterministic fixture: 500 nodes, 955 edges, 100 warmup frames, and 100 measured frames per workload. Timings are hardware-specific and should be reproduced on target devices.

macOS profile — adaptive scene

Workload p50 total p95 total Frames over 8.33 ms
Pan 4.76 ms 6.14 ms 2 / 102
Zoom 4.15 ms 6.43 ms 1 / 102
Single-node drag/drop 2.04 ms 3.08 ms 0 / 102
Add/remove node + 2 edges 5.92 ms 6.33 ms 0 / 102

Chrome release + Wasm — adaptive scene

Workload p50 total p95 total
Pan 4.23 ms 4.63 ms
Zoom 3.87 ms 4.82 ms
Single-node drag/drop 4.30 ms 5.70 ms
Add/remove node + 2 edges 9.87 ms 10.89 ms

The web topology workload is the remaining measured path above the 120 Hz budget. Pan, zoom, and drag are within budget in the recorded adaptive run.

The benchmark emits structured JSON with p50/p95/p99/max UI, raster, and total spans; frame-budget misses; delivered-frame counts; workload counters; renderer metadata; and effective LOD state.

Validation

  • flutter analyze packages/vyuh_node_flow — clean
  • flutter analyze packages/demo — clean
  • Full vyuh_node_flow suite — 6,915 tests passed
  • Focused editor, LOD, stats, and note-rendering gate — 1,060 tests passed
  • 100-frame macOS profile benchmark completed
  • 100-frame Chrome release/Wasm benchmark completed
  • git diff --check — clean

Release

  • Current package version: vyuh_node_flow 0.31.0
  • Melos release commit and tag: vyuh_node_flow-v0.31.0
  • Local web release testing is documented with port 8092, leaving an existing application on localhost:8080 untouched.

Add incremental graph indexing, adaptive overview rendering, isolated paint layers, cached minimap geometry, and a 500-node profile benchmark.

BREAKING CHANGE: controller collections are now read-only, mutable observable collection getters are removed, connection label builders preserve their data type, and adaptive LOD is enabled by default.
 - vyuh_node_flow@0.28.0
@codecov

codecov Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.10029% with 60 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.32%. Comparing base (f5c013c) to head (59b4643).

Files with missing lines Patch % Lines
...e_flow/lib/src/connections/connection_painter.dart 92.62% 18 Missing ⚠️
...s/vyuh_node_flow/lib/src/nodes/node_container.dart 65.00% 14 Missing ⚠️
...w/lib/src/editor/node_flow_editor_hit_testing.dart 46.15% 7 Missing ⚠️
...yuh_node_flow/lib/src/editor/node_flow_editor.dart 93.42% 5 Missing ⚠️
...h_node_flow/lib/src/editor/layers/nodes_layer.dart 90.47% 4 Missing ⚠️
...ow/lib/src/shared/spatial/graph_spatial_index.dart 94.11% 4 Missing ⚠️
..._flow/lib/src/editor/layers/interaction_layer.dart 80.00% 3 Missing ⚠️
...w/lib/src/editor/layers/nodes_thumbnail_layer.dart 87.50% 2 Missing ⚠️
...flow/lib/src/editor/controller/connection_api.dart 97.29% 1 Missing ⚠️
.../lib/src/editor/controller/dirty_tracking_api.dart 0.00% 1 Missing ⚠️
... and 1 more
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #39      +/-   ##
==========================================
+ Coverage   88.79%   90.32%   +1.52%     
==========================================
  Files         143      143              
  Lines        9722    10322     +600     
==========================================
+ Hits         8633     9323     +690     
+ Misses       1089      999      -90     
Flag Coverage Δ
unittests 90.32% <94.10%> (+1.52%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...e_flow/lib/src/connections/connections_canvas.dart 100.00% <100.00%> (ø)
...low/lib/src/editor/controller/editor_init_api.dart 96.42% <ø> (-0.30%) ⬇️
...node_flow/lib/src/editor/controller/graph_api.dart 80.76% <100.00%> (-0.11%) ⬇️
...ib/src/editor/controller/node_flow_controller.dart 93.37% <100.00%> (+0.97%) ⬆️
...rc/editor/controller/node_flow_controller_api.dart 91.12% <100.00%> (-0.06%) ⬇️
...e_flow/lib/src/editor/controller/viewport_api.dart 90.12% <100.00%> (+1.15%) ⬆️
...lib/src/editor/layers/connection_labels_layer.dart 91.15% <100.00%> (ø)
..._flow/lib/src/editor/layers/connections_layer.dart 100.00% <100.00%> (+30.00%) ⬆️
...yuh_node_flow/lib/src/editor/node_flow_config.dart 100.00% <ø> (ø)
...ckages/vyuh_node_flow/lib/src/grid/grid_theme.dart 100.00% <100.00%> (ø)
... and 18 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Separate live camera updates from committed reactive viewport state, render immutable connection snapshots with packed overview batches, coarsen low-zoom grids, and elide idle full-canvas layers.

Add a deterministic 100-frame profile harness for pan, zoom, drag, and node/edge topology churn.

BREAKING CHANGE: high-frequency viewport rendering now uses live camera state while viewportObservable represents committed interaction state; GridTheme adaptively coarsens low-zoom spacing by default.
 - vyuh_node_flow@0.29.0
 - vyuh_node_flow@0.30.0
@pavanpodila

Copy link
Copy Markdown
Contributor Author

Architecture follow-up pushed in 8ca862a, release versioned/tagged as vyuh_node_flow-v0.30.0, with local release instructions in 5e92e17.

Key changes:

  • NodeSceneMode.widgets/navigation/overview; real pan/zoom gestures replace ordinary node widgets with the painted scene while selected/active nodes remain promoted.
  • mutateGraph(...) provides one synchronous MobX/spatial mutation boundary; legacy batch is deprecated.
  • spatial grids and connection segments reconcile incrementally.
  • overview connection batches retain unaffected geometry across topology churn.

100-frame Chrome 151 release/Wasm, 500 nodes / 955 edges:

  • full widgets: pan p50 133.945 ms; zoom 131.496 ms
  • painted navigation: pan 4.355 ms; zoom 4.135 ms; zero 8.33 ms misses
  • adaptive: pan 4.230 ms; zoom 3.870 ms; drag 4.301 ms
  • adaptive topology churn: 9.865 ms p50 / 10.890 ms p95, down from 13.065 / 14.590 ms; still 1.532 ms over the 120 Hz p50 budget on web

Native macOS profile adaptive topology churn is 5.916 ms p50 / 6.326 ms p95 with zero misses. Full package: 6,911 tests passed; package and demo analysis clean.

@pavanpodila
pavanpodila merged commit 66af32b into main Aug 8, 2026
3 checks passed
@pavanpodila
pavanpodila deleted the codex/large-graph-performance branch August 8, 2026 07:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant