System Map — fluid flowing through glass pipes (design)
A selectable demo in facett-demo: a nice little map of the nordisk constellation where components are labelled glass tanks/junctions joined by glass pipes with fluid visibly flowing through them (fluid = data / activity moving between components). This doc records how it is modelled, how the glass-pipe render works, how the fluid steps, and exactly how zero-copy / zero-alloc / zero-wait are met on the frame path.
Where the pieces live (L5 reuse — nothing twinned)
| Piece | Crate | Reuses |
| The fluid solver | facett-cfd::fluid::FluidField |
znippy gatling (gatling_run) |
| The glass-pipe render skin | facett-graph3d::pipes::SystemMap |
Graph3D camera/projection/layout/selection · neon glow orbs · graph_gpu bloom lane |
| The demo wiring | facett-demo::demo_systemmap |
the deck picker + LEGEND + the mega-matrix |
SystemMap is a skin over Graph3D exactly like CodeVault: the orbit/spin camera, the Layout3D node placement, the orthographic projection and the click-selection all come from an embedded Graph3D (its Graph3DView IS the Elm Model). The map adds only the genuinely new bits: the glass-pipe / glass-tank paint and the fluid→pipe wiring. No new renderer, no new fluid solver.
Dependency direction is clean (no cycle): facett-graph3d → facett-cfd; facett-cfd does not depend on facett-graph3d.
The model (nodes / pipes + where the topology comes from)
SystemMap::new(nodes: Vec<(label, Color32)>, edges: Vec<(from, to)>, layout):
- nodes → components, drawn as glass tanks; laid out by the reused
Graph3D(Forcereads most like a plumbing diagram). - edges → pipes; edge
igets fluid pipeiin theFluidField. - The demo topology (
facett-demo::demo_systemmap) is the nordisk constellation:holger → nornir → dwarves → korp, withfacett · skade · knutfeeding the hub — 7 components, 8 pipes. Each pipe's fluid is tinted by its source component, so you can read whose data is flowing.
The glass-pipe render (which renderer, the transparency/fresnel approach)
Reuses Graph3D's projection (project_all → screen pos + depth) and paints on the egui painter (the always-available CPU lane, wasm-clean — same as CodeVault's transparent boxes). Per pipe (paint_glass_pipe, back-to-front by depth):
- Translucent wall — a filled quad the width of the pipe at
GLASS_ALPHA = 0.16(× a depth term), so far pipes + the fluid show through the glass. - Fresnel rim — a brighter thin stroke down both silhouette edges (glass edges catch light).
- Specular streak — a thin white highlight offset toward the light (the glossy sheen).
The fluid inside (paint_fluid) samples the pipe's dye row borrowed from the FluidField and draws it as a luminous column whose per-segment alpha follows the local dye — so the pulsing inlet packets read as bright slugs marching in the flow direction, with a bright specular core on hot packets.
Nodes (paint_glass_node) are glass tanks: a translucent tinted body + a frosted core + a fresnel rim ring + a specular highlight dot, with a neon::paint_node_orb emissive glow behind (bloom-fed on the GPU lane).
GPU path (feature gpu → facett-graph3d/wgpu): SystemMap::gpu_scene feeds tank centres + pipes (radiance scaled by each pipe's live dye load, so a gushing pipe blooms hotter) into the reused graph_gpu additive-HDR + bright-pass + gaussian-blur bloom lane — the same bloom Graph3D/CodeVault use. Without a GPU (headless CI / wasm) the CPU translucent paint above draws; the wgpu snapshot test still renders it onto a real GPU texture (Loki's RTX 5080).
The fluid step (facett-cfd + GPU-vs-gatling)
FluidField is a 1-D dye advection per pipe (CELLS = 48 samples): upwind advection at a signed per-pipe velocity (= flow direction/rate), gentle diffusion, slight decay, and a pulsing dye source injected at the inlet so packets form and travel. Pipes are independent ⇒ embarrassingly parallel.
- CPU (the shipped accelerated API):
FluidField::stepfans the per-pipe work across all cores through the znippy gatling fork-join (gatling_run) —rayonis forbidden in facett.step_serialis the identical-math 1-core reference (and the wasm / threadless fallback, where gatling degrades to a serial drain). - GPU: the render/bloom is the GPU pass (async wgpu submit, double-buffered offscreen). The fluid solve stays on the non-blocking gatling lane — the two run independently, neither blocks the UI.
This is facett-cfd's first gatling consumer and a reusable accelerated fluid API any facett app can drop on its edges (FluidField::new / set_flow / step / dye).
The three zeroes — EXACTLY how they're met on the frame path
- ZERO-COPY. The renderer borrows each pipe's dye with
FluidField::dye(p)(a&[f32]slice) — it never clones the field or a buffer. The fluid's own state is a genuine double buffer swapped bystd::mem::swap(a pointer swap of twoVecs, not an element copy). No.clone()of a field/buffer anywhere in the frame loop. - ZERO-ALLOCATION. All sim state (
front/back/vel/inject) and the per-pipe dye tints are allocated once innew.FluidField::steptouches only those buffers — noVec::new/collect/format!/boxing. The gatling worker writes each pipe's disjoint back-buffer row through aSend+Syncraw pointer (the same disjoint-index pattern gatling +barnes_hutuse), so no per-pipe resultVecis collected. Guard:fluid::tests::step_is_zero_alloc_double_buffer_ping_pongpins the two buffer pointers + capacities across 240 steps and asserts the ping-pong swap — a stray realloc/clone flips a pointer → RED. (The egui paint reuses the sharedGraph3Dprojection + the egui tessellator exactly like every facett 3-D facet; the sim hot path — the expensive per-cell work — is strictly zero-alloc.) - ZERO-WAIT. The fluid advances through the single Elm
updatepath viagatling_run— a no-barrier, no-lock, no-.awaitfork-join that returns within the frame budget; the UI thread never blocks on the sim.view(the pure paint) only emits aStepFluid(dt)message from the ambient clock; the bridge applies it. GPU readback (bloom) is double-buffered offscreen, never stalling the frame.
Testing (L4 — it's visual, so it's robot/snapshot tested)
facett-demo/tests/systemmap_shot.rs— wgpu snapshot proof on the 5080: asserts the map renders NON-BLANK, the FLUID MOVES (two renders at the same fixed camera but different fluid times differ — RED if the step is frozen), and the pipes are TRANSLUCENT (a rich mid-band of intermediate-alpha pixels + the intrinsicglass_alpha < 1).facett-cfd/src/fluid.rstests — the zero-alloc ping-pong guard,gatling == serialbit-for-bit determinism (RED if the fan-out aliases/drops a pipe), downstream advection, and frame-to-frame change.facett-graph3d/src/pipes.rstests — topology (pipe per edge), fluid-moves, empty map honesty, reused-camera headless drive, headless render draws pipes.- Registered in the demo's coverage-gated mega-matrix (
System Mapcell) +LEGEND+ the deck picker, and driven live inrobot_demo/control_matrix/pop_ran_list. Afunctional_statusrow is emitted fromSystemMap::viewunder thetestmatrixfeature.