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

nordisk/facett

facett-map/tests/still_frame_work.rs

//! **WHAT A STILL FRAME ACTUALLY REDOES** — lane GG #11, and the measurement audit #40
//! says the whole of PART 2 was missing: *"no pan-vs-still bench split"*.
//!
//! # Why the measurement comes before the fix
//!
//! Audit #11 says `place_map_labels` *"sorts label_idx and lays out labels EVERY frame,
//! 0.96–9.45 ms — the dominant still-frame CPU cost"*, and prescribes a memo. That is a
//! claim about work that DID happen, and this lane has already found three audit claims
//! that did not survive being run (#2's transparent halo, #4's pass-3 lift, #7's flattened
//! shores). So the seam is counted first and the number reported before anything is
//! memoised.
//!
//! A COUNT and not a stopwatch, deliberately. A memo's value is work that does NOT happen,
//! and work that does not happen is invisible in the output; a wall time moves with
//! whatever else the box was doing, while *1 layout over 60 still frames* against *60* is
//! the same answer on any machine. The argument `lane_scans` and `gpu_sig_walks` already
//! make in this crate.
//!
//! ```bash
//! cargo test -p facett-map --test still_frame_work -- --nocapture
//! ```

use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;

use facett_map::layer::{LatLon, MapFacet, MapTheme, MapView, WaysLayer};
use facett_map::Way;

const CANVAS: egui::Vec2 = egui::vec2(900.0, 600.0);
const FRAMES: usize = 60;

/// A roster with real NAMED ways — a layout with nothing to lay out would measure the
/// early return and not the text shaping the audit is about.
fn named_ways(n: usize) -> Vec<Way> {
    (0..n)
        .map(|i| {
            let lat = 59.30 + (i as f64) * 0.0004;
            let tags = vec![
                ("highway".to_string(), "residential".to_string()),
                ("name".to_string(), format!("Testgatan {i}")),
            ];
            Way::from_lonlat_with(
                i as i64,
                &[(18.00, lat), (18.06, lat + 0.0002), (18.12, lat)],
                &tags,
                &facett_map::MapPalette::cartographic(false),
            )
            .expect("a 3-vertex named way")
        })
        .collect()
}

/// Drive `FRAMES` frames and hand back `(label layouts, wall time)`.
///
/// The facet moves into the harness's closure, so the seam is read from INSIDE it into a
/// shared cell — the only shape `build_ui` allows, and it reads the count after each
/// frame rather than once at the end so a memo that fired late would still show.
fn drive(ways: Vec<Way>, pan: bool) -> (usize, f64) {
    let seen = Arc::new(AtomicUsize::new(0));
    let sink = Arc::clone(&seen);
    let view = MapView::new(LatLon::new(59.33, 18.06), 13.0, (CANVAS.x, CANVAS.y));
    let mut f = MapFacet::new("still-frame-work", view, MapTheme::default())
        .with_layer(Box::new(WaysLayer::new("osm-ways", ways)));

    let mut h = egui_kittest::Harness::builder().with_size(CANVAS).build_ui(move |ui| {
        if pan {
            // A pan is a CAMERA move and not a data change — exactly the case a memo
            // must not swallow. Driven through the facet's own gesture seam so it is the
            // same path a wheel/drag takes.

            f.view_mut().center.lon += 0.0002;
        }
        f.ui(ui);
        sink.store(f.label_layout_count(), Ordering::Relaxed);
    });
    let t = std::time::Instant::now();
    for _ in 0..FRAMES {
        h.run();
    }
    let ms = t.elapsed().as_secs_f64() * 1000.0;
    (seen.load(Ordering::Relaxed), ms)
}

/// **THE MEASUREMENT, reported before it is judged.**
///
/// If the still count equals the frame count the audit is right and the layout runs every
/// frame. If it is 1, it is already memoised and #11 is closed by something else. Either
/// way the number goes in the log, and the assertion below only holds the property that
/// must be true whatever the answer.
#[test]
fn report_what_a_still_frame_and_a_pan_each_cost_in_label_layouts() {
    let ways = named_ways(400);
    let (still_n, still_ms) = drive(ways.clone(), false);
    let (pan_n, pan_ms) = drive(ways, true);

    eprintln!(
        "[GG11] 400 named ways, {FRAMES} frames — STILL: {still_n} label layouts, \
         {still_ms:.1} ms ({:.2} ms/frame) | PAN: {pan_n} layouts, {pan_ms:.1} ms \
         ({:.2} ms/frame)",
        still_ms / FRAMES as f64,
        pan_ms / FRAMES as f64,
    );

    // NON-VACUITY: the frames really ran and the seam really fired. A count of 0 would
    // mean the harness never reached the painter, and every number above would be noise.
    assert!(
        still_n > 0 && pan_n > 0,
        "the label layout never ran ({still_n} still / {pan_n} pan) — the harness did not \
         reach the painter, so nothing here measures anything"
    );
    // A PAN must lay out again: the camera moved, so the boxes are in different places.
    // This is the property a memo must not break, and it is asserted now so the memo
    // cannot be written without it.
    assert!(
        pan_n > 1,
        "a panning frame must re-place its labels — the camera moved, so a memo that \
         held across a pan would pin every name to where it used to be"
    );

    // ── CHARACTERISATION: there is no memo today, and that is deliberate ─────────
    //
    // MEASURED here: **62 layouts over 60 still frames** — the layout runs on every
    // frame, so audit #11's premise is correct. Its prescribed cure is not: the doc above
    // `place_map_labels` already argues, from its own measurement, that a
    // `(camera, roster)` memo is **wrong by construction** — the placement is computed
    // against egui's font state (`layout_no_wrap` shapes through `Context::fonts`) and
    // **the font definitions and atlas carry no version a `MapFacet` can read**. Confirmed
    // by inspection: `epaint 0.35`'s `Fonts` exposes no generation, version or epoch.
    // A stale answer here is not a slow frame — it is names painted into rects the painter
    // no longer measures, which `label_collision.rs` asserts on.
    //
    // And the prerequisite that doc names — *"a facet-side (text, FontId,
    // pixels_per_point) galley cache that makes the shaping cost, not the placement, the
    // thing that is saved"* — **already exists upstream**: `epaint::text::fonts` has a
    // `GalleyCache` with generation-based GC, so identical text at the same font and
    // pixels-per-point is already shaped once and reused across frames. Building one in
    // this crate would be a LAW 5 twin of it and would buy nothing.
    //
    // So what is left per frame is the FILTER + SORT + the placement solver, not the
    // shaping — and that IS safely memoisable, by a key the font state enters through the
    // measured galley SIZES rather than through a version nobody exposes. That is the
    // shape of the fix, and it is a change of its own.
    //
    // This assertion pins today's answer so the next person to touch it does so with the
    // numbers in hand rather than by accident.
    assert!(
        still_n >= FRAMES,
        "there is no label memo today and this test records that on purpose ({still_n} \
         layouts over {FRAMES} still frames). If this has fallen, someone memoised the \
         placement — good, and this file is where the before/after belongs: update the \
         numbers above, keep the pan assertion, and say how the key covers font state."
    );
}