facett-graphview/tests/resident_lane.rs
//! **The RESIDENT cloud lane of `DecoratedGraphView` at scale** — what it uploads, and
//! that the unfold, the selection and the decorations are still right.
//!
//! The gate is opened without a device (`cloud-test-gate`): the paint callbacks land in
//! the frame's shapes and are never executed, so these run on a box with no GPU and
//! count what the lane SENDS. The pixels are proven on a real adapter by
//! `facett-demo`'s pages probe (`decograph` at 100k, t14s).
//!
//! Serialised: the ONE resident model is process-global.
#![cfg(feature = "gpu-cloud")]
use std::collections::HashMap;
use std::sync::Mutex;
use egui::{Color32, Pos2, Vec2};
use facett_core::render::gpu::graphcloud::cloud;
use facett_core::Facet;
use facett_graphview::decorated_view::Msg;
use facett_graphview::model::{Color, Decorations, GraphEdge, GraphModel, GraphNode, NodeDecoration, Pos};
use facett_graphview::DecoratedGraphView;
static ONE_AT_A_TIME: Mutex<()> = Mutex::new(());
const RING: Color = Color::rgb(10, 250, 20);
/// A forest: `roots` roots, each the head of a binary tree of `per` nodes.
fn forest(roots: usize, per: usize, salt: &str) -> GraphModel {
let mut nodes = Vec::new();
let mut edges = Vec::new();
for r in 0..roots {
for k in 0..per {
let id = format!("{salt}{r}-{k}");
nodes.push(GraphNode {
id: id.clone(),
label: format!("n{r}.{k}"),
fill: Color::rgb(60, 90, 160),
stroke: Color::WHITE,
pos: Pos::new(r as f32 * 400.0 + (k % 40) as f32 * 9.0, (k / 40) as f32 * 40.0),
});
if k > 0 {
edges.push(GraphEdge {
from: format!("{salt}{r}-{}", (k - 1) / 2),
to: id,
color: Color::rgb(200, 200, 220),
dashed: false,
label: None,
});
}
}
}
GraphModel { nodes, edges }
}
fn input() -> egui::RawInput {
egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(Pos2::ZERO, Vec2::new(1200.0, 800.0))), ..Default::default() }
}
fn frame(ctx: &egui::Context, v: &mut DecoratedGraphView) -> Vec<egui::epaint::ClippedShape> {
let mut out = ctx.run_ui(input(), |ui| {
egui::CentralPanel::default().show(ui, |ui| v.ui(ui));
});
out.textures_delta.clear();
out.shapes
}
fn rings(shapes: &[egui::epaint::ClippedShape], width: f32, color: Color32) -> usize {
shapes
.iter()
.filter(|s| matches!(&s.shape, egui::Shape::Rect(r) if r.stroke.width == width && r.stroke.color == color))
.count()
}
fn lane(v: &DecoratedGraphView) -> (&'static str, u64) {
let l = v.legibility();
(l.cloud_lane(), l.resident_uploads)
}
#[test]
fn decograph_uploads_per_model_and_unfold_never_per_camera_or_selection() {
let _one = ONE_AT_A_TIME.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
cloud::assume_renderer_installed_for_tests();
let n_roots = 6;
let per = cloud::RESIDENT_ABOVE / n_roots + 200;
let mut deco = Decorations { nodes: HashMap::new(), edges: Vec::new() };
deco.nodes.insert("a1-3".into(), NodeDecoration { ring: Some(RING), badge: Some("!".into()), badge_color: Some(RING), ..Default::default() });
deco.edges.push(GraphEdge { from: "a0-5".into(), to: "a2-9".into(), color: Color::rgb(230, 90, 90), dashed: true, label: Some("cut".into()) });
let mut v = DecoratedGraphView::new("d").with_model(forest(n_roots, per, "a")).with_decorations(deco).with_gpu(true);
let _ = v.update(Msg::ZoomBy(-950.0)); // well below 0.45: no labels
let ctx = egui::Context::default();
ctx.set_pixels_per_point(1.0);
let s = frame(&ctx, &mut v);
assert_eq!(lane(&v), ("resident", 1), "the first frame uploads the model");
assert_eq!(v.state_json()["legibility"]["cloud_lane"], "resident");
assert_eq!(rings(&s, 2.4, RING.into()), 1, "the decoration ring is drawn over the cloud");
// Camera-only frames.
for i in 0..10 {
let _ = v.update(if i % 2 == 0 { Msg::PanBy(6.0, -2.0) } else { Msg::ZoomBy(5.0) });
let _ = frame(&ctx, &mut v);
assert_eq!(lane(&v), ("resident", 1), "camera frame {i} uploaded nothing");
}
// Selection: an overlay (ring + lit trace), no upload.
let th = facett_core::Theme::default();
let _ = v.update(Msg::Select(Some("a3-0".into())));
let s = frame(&ctx, &mut v);
assert_eq!(lane(&v), ("resident", 1), "a selection uploads nothing");
assert_eq!(rings(&s, 3.0, th.accent), 1, "one selection ring");
let trace = s
.iter()
.filter(|c| matches!(&c.shape, egui::Shape::LineSegment { stroke, .. } if stroke.width == 1.6 && stroke.color == th.accent))
.count();
assert_eq!(trace, per - 1, "the whole lit tree of root 3 is traced (it is under the cap)");
assert_eq!(v.lit_set().len(), per);
// Fold a root: the unfold changed, so the cloud is re-sent once — without the subtree.
let _ = v.update(Msg::Select(None));
let _ = v.update(Msg::Fold("a1-0".into()));
let s = frame(&ctx, &mut v);
assert_eq!(lane(&v), ("resident", 2), "a fold re-uploads once");
assert_eq!(v.visible_ids().len(), n_roots * per - (per - 1));
assert_eq!(rings(&s, 2.4, RING.into()), 0, "a folded-away decorated node has no ring");
let _ = frame(&ctx, &mut v);
assert_eq!(lane(&v).1, 2, "and only once");
// A new model: exactly one more upload.
v = v.with_model(forest(n_roots, per, "b"));
let _ = frame(&ctx, &mut v);
let _ = frame(&ctx, &mut v);
assert_eq!(lane(&v), ("resident", 3));
}
#[test]
fn small_decographs_keep_the_per_frame_lane() {
let _one = ONE_AT_A_TIME.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
cloud::assume_renderer_installed_for_tests();
let ctx = egui::Context::default();
let mut v = DecoratedGraphView::local().with_gpu(true);
let _ = frame(&ctx, &mut v);
assert_eq!(lane(&v), ("per-frame", 0));
// Without the GPU opt-in the CPU painter draws, as always.
let mut cpu = DecoratedGraphView::new("c").with_model(forest(2, cloud::RESIDENT_ABOVE, "c"));
let _ = frame(&ctx, &mut cpu);
assert_eq!(lane(&cpu), ("cpu", 0));
}
/// The CPU cost of a camera-only frame at 100 000 nodes on the resident lane (native,
/// no tessellation, no GPU). `cargo test --release -p facett-graphview --features
/// gpu-cloud --test resident_lane -- --ignored --nocapture`.
#[test]
#[ignore = "a timing print, run by hand in --release"]
fn camera_frame_cost_at_100k() {
let _one = ONE_AT_A_TIME.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
cloud::assume_renderer_installed_for_tests();
let ctx = egui::Context::default();
let mut v = DecoratedGraphView::new("d").with_model(forest(50, 2_000, "m")).with_gpu(true);
v.fit_model(Vec2::new(1100.0, 620.0), 16.0);
let t0 = std::time::Instant::now();
let _ = frame(&ctx, &mut v);
let first = t0.elapsed();
let t1 = std::time::Instant::now();
for _ in 0..60 {
let _ = v.update(Msg::PanBy(3.0, 1.0));
let _ = frame(&ctx, &mut v);
}
let per = t1.elapsed() / 60;
eprintln!("decograph 100k: first (upload) frame {first:?}, camera frame {per:?}, uploads {}", lane(&v).1);
assert_eq!(lane(&v).1, 1);
}
/// **A pane laid out wider than the screen sends the cloud only its visible part** (the
/// loki crash, 2026-10-16: a 10 604 px paint rect made a 10 604 px offscreen, past the
/// device's 8 192 limit). Here the pane is handed a 12 000 px wide rect on a 1 200 px
/// screen: the cloud's paint callback covers the visible part only. RED before the clip
/// (the callback was 12 000 wide).
#[test]
fn a_pane_wider_than_the_screen_paints_the_cloud_over_its_visible_part_only() {
let _one = ONE_AT_A_TIME.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
cloud::assume_renderer_installed_for_tests();
let per = cloud::RESIDENT_ABOVE / 4 + 200;
let mut v = DecoratedGraphView::new("wide").with_model(forest(4, per, "w")).with_gpu(true);
let _ = v.update(Msg::ZoomBy(-950.0));
let ctx = egui::Context::default();
ctx.set_pixels_per_point(1.0);
let mut out = ctx.run_ui(input(), |ui| {
egui::CentralPanel::default().show(ui, |ui| {
let wide = egui::Rect::from_min_size(ui.max_rect().min, Vec2::new(12_000.0, 700.0));
ui.scope_builder(egui::UiBuilder::new().max_rect(wide), |ui| v.ui(ui));
});
});
out.textures_delta.clear();
let callbacks: Vec<egui::Rect> = out
.shapes
.iter()
.filter_map(|s| match &s.shape {
egui::Shape::Callback(cb) => Some(cb.rect),
_ => None,
})
.collect();
assert_eq!(lane(&v).0, "resident", "the resident lane drew");
assert_eq!(callbacks.len(), 1, "one cloud callback: {callbacks:?}");
assert!(callbacks[0].width() <= 1200.0, "the callback covers the visible part, not the 12 000 px pane: {:?}", callbacks[0]);
assert!(callbacks[0].width() > 100.0, "and it does cover it: {:?}", callbacks[0]);
}