//! Retained CPU-layout diagnostics on one reproducible random tree. //! //! Counters and phase timers: //! //! cargo test --release --features layout-diagnostics \ //! --test layout_diagnostics -- --ignored --nocapture //! //! Uninstrumented hardware totals for one phase: //! //! IRIS_PHASE=resize IRIS_FRAMES=1000 perf stat \ //! -e cycles:u,instructions:u cargo test --release \ //! --test layout_diagnostics -- --ignored --nocapture //! //! `IRIS_PHASE` is `cold`, `repaint`, `size`, `scroll`, `resize`, or `all`. //! `IRIS_SEED`, `IRIS_DEPTH`, and `IRIS_FRAMES` select the load. use iris::harness::Harness; use iris::prelude::*; use iris::random::{Edits, Tree, grow}; use std::time::Instant; const OUTPUT: (f32, f32) = (1920.0, 1200.0); fn env(name: &str, fallback: T) -> T { std::env::var(name) .ok() .and_then(|value| value.parse().ok()) .unwrap_or(fallback) } fn warm(seed: u64, depth: usize) -> (Harness, Tree) { let mut harness = Harness::new(OUTPUT); let (root, tree) = grow(&mut harness.rsc, seed, depth, &Edits::default()); harness.state.root = Some(root); harness.frame(); println!( "fixture: seed {seed}, depth {depth}, {} widgets, {} active", tree.ids.len(), harness.render.active_widgets() ); #[cfg(feature = "layout-diagnostics")] let _ = iris::core::layout_diagnostics::take(); (harness, tree) } fn report(label: &str, mut elapsed: Vec, _harness: &Harness) { elapsed.sort_by(|a, b| a.partial_cmp(b).unwrap()); let frames = elapsed.len(); println!( "{label}: {frames} frame(s), min {:.3} ms, median {:.3} ms, total {:.1} ms", elapsed[0], elapsed[frames / 2], elapsed.iter().sum::(), ); #[cfg(feature = "layout-diagnostics")] { let diagnostics = iris::core::layout_diagnostics::take(); print!("{}", diagnostics.per_frame(frames)); for callsite in diagnostics.hot_text().iter().take(3) { let mut ancestry = Vec::new(); let mut id = Some(callsite.id); while let Some(widget) = id { ancestry.push(_harness.rsc.widgets().label(widget).as_str()); id = _harness .render .active .get(&widget) .and_then(|active| active.parent); } println!(" text ancestry: {}", ancestry.join(" < ")); } } } fn run( label: &str, frames: usize, harness: &mut Harness, mut change: impl FnMut(&mut Harness, usize), ) { let mut elapsed = Vec::with_capacity(frames); for frame in 0..frames { change(harness, frame); let start = Instant::now(); harness.frame(); elapsed.push(start.elapsed().as_secs_f64() * 1_000.0); } report(label, elapsed, harness); } #[test] #[ignore = "measurement, not a check"] fn layout_cost() { let seed = env("IRIS_SEED", 1_u64); let depth = env("IRIS_DEPTH", 7_usize); let frames = env("IRIS_FRAMES", 100_usize); assert!(frames > 0, "IRIS_FRAMES must be greater than zero"); let phase = env("IRIS_PHASE", String::from("all")); assert!( ["all", "cold", "repaint", "size", "scroll", "resize"].contains(&phase.as_str()), "unknown IRIS_PHASE {phase:?}" ); let selected = |name| phase == "all" || phase == name; if selected("cold") { let mut harness = Harness::new(OUTPUT); let (root, tree) = grow(&mut harness.rsc, seed, depth, &Edits::default()); harness.state.root = Some(root); println!( "fixture: seed {seed}, depth {depth}, {} widgets", tree.ids.len() ); #[cfg(feature = "layout-diagnostics")] let _ = iris::core::layout_diagnostics::take(); run("cold", 1, &mut harness, |_, _| {}); drop(tree); } if selected("repaint") { let (mut harness, tree) = warm(seed, depth); let leaf = tree.ids[0]; run("repaint", frames, &mut harness, move |harness, _| { let _ = harness.rsc.widgets_mut().get_dyn_mut(leaf); }); } if selected("size") { let (mut harness, tree) = warm(seed, depth); let sized = tree.sized[0]; run("size", frames, &mut harness, move |harness, frame| { harness.rsc[sized].x = Some(Len::abs(100.0 + (frame % 2) as f32 * 40.0)); }); } if selected("scroll") { let (mut harness, tree) = warm(seed, depth); let scroll = tree.scrolls[0]; run("scroll", frames, &mut harness, move |harness, frame| { harness.rsc[scroll].scroll(if frame % 2 == 0 { 12.0 } else { -12.0 }); }); } if selected("resize") { let (mut harness, tree) = warm(seed, depth); run("resize", frames, &mut harness, |harness, frame| { harness.resize((OUTPUT.0 - (frame % 2) as f32 * 8.0, OUTPUT.1)); }); drop(tree); } }