Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-flow/README.md

Falsett — animated flow facet

The flow component was created by Andreas Persson.

facett-flow renders supplied directed graphs as curved connections with animated packets. Its hub fixture places inputs on the left/top, outputs on the right/bottom, and a translucent central circle through which the animation remains visible.

The crate follows facett-core's Elm / Facet contract. It owns no connections, queries, files, or wall clock. The host supplies nodes, positions, groups, edges and time; derived picking/cluster caches and GPU buffers are outside the serialized model.

Use in a host

The umbrella exports the component as facett::flow. Enable its optional GPU lane in a host with an egui-wgpu renderer:

[dependencies]
facett = { path = "../facett", features = ["flow-wgpu"] }

Install the renderer once on the host's existing render state. Facett's shared checked installer handles validation without blocking; failures are recorded in the GPU lane and leave the CPU preview available:

,ignore
facett::flow::gpu::install_renderer(render_state);

Supply a graph using FlowGraph::new(id, graph), or use the explicitly synthetic 10,000-node fixture:

,ignore
use facett::core::Elm;
use facett::flow::{FlowGraph, Msg};

let mut flow = FlowGraph::local();
flow.update(Msg::Tick(elapsed_seconds)); // drive this each animation frame
deck.push(Box::new(flow));

After adding a facet to a deck, route subsequent ticks through its message API (for example Facet::update_json with {"Tick":0.016}); construction-time ticking alone does not animate it. Request repaint while running. The showcase's Falsett tab is a complete example of host-clock injection, pause and reduced-motion controls. It inherits the host palette; the standalone example starts in dark mode.

Graph uses stable string node IDs, finite supplied 2D coordinates, nonempty groups, node kinds (0 input, 1 intermediate, 2 output, 3 hub), and directed endpoint IDs. Invalid graphs are rejected. Msg::Replace replaces data; Msg::Select uses a stable ID; Msg::Clustered explicitly switches to a counted group overview. The hub fixture has N - 1 links, and its grouped view has 25 nodes / 24 links at 10k and 100k.

Without wgpu, or before the host installs a renderer, the same facet paints a clearly labeled CPU preview capped at 2,000 points / 4,000 straight links. This is not the full dense GPU view. GPU buffers remain resident during animation and camera changes; only graph/mode changes upload geometry again. Each pane has its own resources.

Build and run locally

From the workspace root, using the repository's Rust toolchain and dependency setup:

cargo run --release -p facett-flow --features wgpu --example flow_lab -- \
  --nodes 10000 --layout hub --theme dark

The executable accepts --nodes N, --layout hub|stages, --theme dark|light|device and --benchmark. It defaults to 10,000 nodes, the hub layout and dark mode. Screenshots and benchmark.json go to $FLOW_RESULTS, or to target/flow-lab. The example's window/screenshot crates are dev-dependencies; default library builds carry no GPU or native window dependency.

The main gallery registers the same component under Grafer → Falsett:

FACETT_DEMO_TAB=Falsett cargo run -p facett-demo --bin facett-demo

On Linux, run inside an X11/Wayland session with a working hardware Vulkan driver. On macOS the native GPU backend uses Metal. Software GPU adapters are refused.

Verification and limits

cargo test -p facett-flow --test contracts
cargo test -p facett-flow --features wgpu --test gpu_panes   # FACETT_TEST_GPU=discrete|integrated
cargo clippy --no-deps -p facett-flow --features wgpu --lib --tests --example flow_lab -- -D warnings
cargo test -p facett --test matrix
cargo test -p facett-demo flow_tab::tests --lib
cargo test -p facett-demo --test mega_matrix

Contract tests cover graph/model validation, serialization, stable selection, pause, reduced motion, host themes, exact 10k/100k fixture counts, and WGSL parsing/validation. A 44-second native release run on 2026-10-04 against main at 6a9bc66b (egui 0.36.2 / wgpu 30.0.1) measured roughly 118–120 FPS for 10,000 hub nodes / 9,999 links on Apple M2 Pro/Metal (raw overview, pan/zoom and detail; 3 seconds warmup + 8 seconds sampled per phase). This used an isolated workspace because the full checkout references missing sibling repositories. It is not a full gallery measurement or a guarantee for other graphs or hosts. GPU pass timings were unavailable. The 1M-node validation budget is not a measured capacity. Linux GPU runtime and million-node performance have not been verified.

Shared host renderer and optional features

host exposes a persistent FlowScene, View, PaintOpts and paint. The caller keeps its model, camera, clock, interactions and overlays. Construct the scene on graph changes, not inside every paint call. with_key combines the caller key with a digest of all graph data, including positions; changed geometry cannot reuse stale GPU buffers. replace validates before mutation, preserves an unchanged scene and clears ordering-dependent tables when a graph changes.

,ignore
let scene = facett_flow::FlowScene::with_key(graph, stable_document_key)?;
let view = facett_flow::View::from_navigable(&navigation, rect);
let painted = facett_flow::paint(ui, pane_id, rect, &scene, &view,
    &facett_flow::PaintOpts {
        seconds: host_clock,
        cpu: facett_flow::CpuStyle::Full { max_edges: 50_000 },
        ..Default::default()
    });

Use a distinct persistent pane ID for each view. With wgpu, call gpu::evict_idle(render_state, ctx.cumulative_frame_nr()) even on frames where no flow pane is drawn, so closing the last view releases its GPU resources.

Feature Explicit opt-in at runtime
labels FlowGraph::set_labels(true); labels use the shared collision placer
boxes FlowScene::set_sizes(Sizes) or Msg::Sizes
arrows FlowScene::set_arrows(true)
status set_status(StatusTable) or Msg::Status; theme-aware StatusPalette
rate set_rates(Rates) or Msg::Rates; flowsim's speed/count planning
layered Graph::layered(LayeredOpts) using facett-core
compact-state set_compact_state(true) or compact_state_json()
hit-test hit / hits_in, with node rectangles and 16-segment cubic picking
lod Main's adaptive segmentation is retained; PaintOpts::min_packet_px optionally hides packets/trails on short projected edges

Features expose capabilities; enabling a dependency's feature does not activate labels or compact JSON on other instances. Elm::state() always returns the full model. The compact digest is cached when the graph changes. Sizes/status/rates are validated atomically; invalid lengths and invalid numeric values leave the previous state intact. Rate None retains the ordinary packet, Some(0) means no packets or trail. Choose Animation::External when another component supplies particles, or Animation::None for a static diagram.

Painted::on_screen is a stable, budgeted label list. visible_nodes is independent of that budget; hosts use it for content and interaction. Node visibility/picking reuses the existing 64-unit centre buckets with box-extent filtering; edge queries scan edge bounds and test the cubic in 16 segments. No new spatial-tree index is introduced. Large edge picking remains linear.

The unchanged flow.wgsl remains the plain pipeline. Optional tables allocate a separate group-1 buffer set and select the composed flow_ext.wgsl entry points. Attribute-only updates leave geometry and offscreen targets resident. All-None status/rate tables retain the plain pipeline. src/wgsl-compositions.txt drives both offline Naga validation and the browser compilation gate (all 32 on/off hook combinations plus plain). GPU tests are explicitly gated by FACETT_TEST_GPU=integrated|discrete|software; an unset gate is a skip, not device evidence. flow_lab reports the compiled feature set alongside CPU timings, upload bytes and GPU pass p50/p95. Adaptive LOD is already part of the latest main baseline; no new performance improvement is claimed without measurement.

flow_lab --scenario plain|host|labels|boxes|arrows|status|rate|layered|compact-state|hit-test|lod|all activates explicit benchmark scenes independently of compiled capabilities. Missing features are reported before the window opens. Scenario metadata and compact-state read timings accompany the existing four-phase measurements. The development-only dev/flow-bench runner supplies the planned component and 100k/1M core-layout timing fixtures without adding runtime dependency edges.