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

nordisk/dwarves

crates/nornir-warehouse-nav/tests/robot_world_frame_budget.rs

//! **robot_world_frame_budget** — the RED-when-broken guard against the per-frame relayout bug.
//!
//! The world graph's `build_world` runs a full 80-iteration Barnes–Hut force converge over the
//! bounded preview. Before the fix it ran on EVERY egui repaint (from `ui()` and `state_json()`),
//! so a "20-node" world — inflated to a ~136-node preview — re-converged every frame (~48 ms
//! release / hundreds of ms debug; the default 4 000-node preview ~140 ms), which is why the world
//! was "segt för 20 noder". The fix caches the converged `WorldBuild` and only rebuilds on a real
//! data/zoom change.
//!
//! This guard is in its OWN test binary so the process-wide `world_build_calls()` counter reflects
//! only this test (each `cargo test` integration file is a separate process). The hard, deterministic
//! signal is the build-count delta (== 1 per data/zoom change, == 0 across repaints); the frame-time
//! budget is a soft, generously-margined sanity check that also prints the real cached cost.

#![cfg(not(target_arch = "wasm32"))]

use std::cell::RefCell;
use std::rc::Rc;
use std::time::Instant;

use egui::vec2;
use egui_kittest::Harness;
use facett_core::Facet;
use nornir_warehouse_nav::{world_build_calls, WarehouseNav};

#[test]
fn world_layout_is_cached_not_rerun_every_frame() {
    let nav = Rc::new(RefCell::new(WarehouseNav::from_fixture_with_functions(20)));
    let n = nav.clone();
    let mut h = Harness::builder()
        .with_size(vec2(1000.0, 700.0))
        .build_ui(move |ui| {
            n.borrow_mut().ui(ui);
        });

    // ── Enter world mode at a ~20-node preview: the layout builds EXACTLY ONCE. ──
    let base = world_build_calls();
    nav.borrow_mut().update_json(r#"{"world_target":20}"#);
    nav.borrow_mut().update_json(r#"{"world":true}"#);
    h.run();
    assert_eq!(
        world_build_calls() - base,
        1,
        "entering world mode builds the converged layout exactly once"
    );

    // ── 120 repaints with NO data change add ZERO builds (was: one converge per frame). ──
    let start = world_build_calls();
    let t = Instant::now();
    for _ in 0..120 {
        h.run();
    }
    let per_frame_ms = t.elapsed().as_secs_f64() * 1000.0 / 120.0;
    assert_eq!(
        world_build_calls() - start,
        0,
        "no repaint re-runs the layout — the fix caches the converged graph"
    );
    println!("20-node world: {per_frame_ms:.4} ms/frame cached (uncached converge ≈ 48 ms/frame, release)");
    assert!(
        per_frame_ms < 40.0,
        "a cached 20-node repaint is effectively free, got {per_frame_ms:.3} ms/frame"
    );

    // ── A real zoom/target change (→ 1000-node preview) rebuilds ONCE, then caches again. ──
    let before = world_build_calls();
    nav.borrow_mut().update_json(r#"{"world_target":1000}"#);
    h.run();
    assert_eq!(
        world_build_calls() - before,
        1,
        "a real zoom/target change rebuilds the layout exactly once"
    );
    let start = world_build_calls();
    let t = Instant::now();
    for _ in 0..120 {
        h.run();
    }
    let per_frame_ms = t.elapsed().as_secs_f64() * 1000.0 / 120.0;
    assert_eq!(
        world_build_calls() - start,
        0,
        "the 1000-node world is cached across repaints too"
    );
    println!("1000-node world: {per_frame_ms:.4} ms/frame cached (uncached converge ≈ 68 ms/frame, release)");
    assert!(
        per_frame_ms < 60.0,
        "a cached 1000-node repaint stays interactive, got {per_frame_ms:.3} ms/frame"
    );
}