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.