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

nordisk/facett

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]);
}