//! What `wai.world.model`'s integer renderer holds, through the integer operator set, //! against the direct renderer it replaced (`int_world::legacy`, feature `legacy_int`), //! measured with a global allocator that tracks the bytes live and their peak. //! //! The renderer declares no cost: it has no payload (`extensions/integer-operators.md` //! ยง13.5, informative). So what it holds is held to the direct renderer: with the set //! loaded beforehand (`int_world::load`, once per clip or per session, as the browser's //! `WorldModel` holds it), a clip through the operator set holds at least its frames and //! no more than the direct renderer holds for the same clip, both on the reference //! kernels; and so does each step and each frame of the live path. //! //! Its own test binary with a single test, so that no other test's allocations land in //! the window it measures. #![cfg(all(feature = "world_int", feature = "legacy_int"))] use std::alloc::{GlobalAlloc, Layout, System}; use std::path::PathBuf; use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; use serde_json::Value; use wai::int_hyper_synth::IntModel; use wai::int_kernels::{with_policy, Policy}; use wai::int_world::{self, legacy, ACTIONS}; /// The system allocator, tracking the bytes live and the most live at once. struct Live; static CUR: AtomicUsize = AtomicUsize::new(0); static PEAK: AtomicUsize = AtomicUsize::new(0); unsafe impl GlobalAlloc for Live { unsafe fn alloc(&self, l: Layout) -> *mut u8 { PEAK.fetch_max(CUR.fetch_add(l.size(), Relaxed) + l.size(), Relaxed); unsafe { System.alloc(l) } } unsafe fn alloc_zeroed(&self, l: Layout) -> *mut u8 { PEAK.fetch_max(CUR.fetch_add(l.size(), Relaxed) + l.size(), Relaxed); unsafe { System.alloc_zeroed(l) } } unsafe fn realloc(&self, p: *mut u8, l: Layout, new: usize) -> *mut u8 { if new >= l.size() { PEAK.fetch_max(CUR.fetch_add(new - l.size(), Relaxed) + (new - l.size()), Relaxed); } else { CUR.fetch_sub(l.size() - new, Relaxed); } unsafe { System.realloc(p, l, new) } } unsafe fn dealloc(&self, p: *mut u8, l: Layout) { CUR.fetch_sub(l.size(), Relaxed); unsafe { System.dealloc(p, l) } } } #[global_allocator] static ALLOC: Live = Live; /// The most bytes `f` holds at once beyond what was live when it started, and its result /// (still held when the measure is taken). fn peak_during(f: impl FnOnce() -> R) -> (R, u64) { let base = CUR.load(Relaxed); PEAK.store(base, Relaxed); let r = f(); (r, (PEAK.load(Relaxed) - base) as u64) } fn read(rel: &str) -> Vec { let p = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/vectors").join(rel); std::fs::read(&p).unwrap_or_else(|e| panic!("{}: {e}", p.display())) } #[test] fn the_renderer_holds_no_more_than_the_direct_renderer() { for (d, js, t, g) in [("int_world", "world.json", "world_T.bin", "world_gs.bin"), ("int_world2", "world2.json", "world2_T.bin", "world2_gs.bin")] { let j: Value = serde_json::from_slice(&read(&format!("{d}/{js}"))).unwrap(); let (c, lat, afrac) = (j["c"].as_u64().unwrap() as usize, j["lat"].as_u64().unwrap() as usize, j["afrac"].as_u64().unwrap() as u32); let z0: Vec = j["z0"].as_array().unwrap().iter().map(|v| v.as_i64().unwrap()).collect(); let mv = |a: &Value| ACTIONS[a.as_u64().unwrap() as usize]; let rows: Vec> = match j.get("pairs") { Some(p) => p.as_array().unwrap().iter().map(|p| { let ((xa, ya), (xb, yb)) = (mv(&p[0]), mv(&p[1])); [xa, ya, xb, yb].iter().map(|v| v << afrac).collect() }).collect(), None => j["actions"].as_array().unwrap().iter().map(|a| { let (x, y) = mv(a); vec![x << afrac, y << afrac, 0] }).collect(), }; let (tm, gm) = (IntModel::from_bytes(&read(&format!("{d}/{t}"))).unwrap(), IntModel::from_bytes(&read(&format!("{d}/{g}"))).unwrap()); // A clip: the direct renderer, then the renderer with its set loaded beforehand. let (want, direct) = with_policy(Policy::reference(), || peak_during(|| legacy::decode_planes(&tm, &gm, &z0, &rows, c, lat))); let (world, set) = peak_during(|| int_world::load(&tm, &gm, c).unwrap()); let (got, held) = with_policy(Policy::reference(), || peak_during(|| world.decode_planes(&z0, &rows, lat).unwrap())); assert!(got.frames == want.frames, "{d}: the frames through the lowering are the direct renderer's"); let frames: u64 = got.frames.iter().map(|f| f.len() as u64).sum(); assert!(held >= frames && held <= direct, "{d}, set loaded: held {held}; the direct renderer held {direct}; the frames are {frames}"); // The whole call, which loads the set first. let (r, whole) = with_policy(Policy::reference(), || peak_during(|| int_world::decode_planes(&tm, &gm, &z0, &rows, c, lat))); drop(r.unwrap()); assert!(whole <= set + direct, "{d}: the call that loads the set held {whole}, over the set loaded ({set}) and the direct renderer's {direct}"); // The live path: one transition, then one frame, against the direct renderer's. let (zd, step_direct) = with_policy(Policy::reference(), || peak_during(|| legacy::step_planes(&tm, &z0, &rows[0], c, lat))); let (zo, step_held) = with_policy(Policy::reference(), || peak_during(|| world.step(&z0, &rows[0], lat).unwrap())); assert!(zo == zd && step_held <= step_direct, "{d}: one step held {step_held}; the direct step held {step_direct}"); let (fd, frame_direct) = with_policy(Policy::reference(), || peak_during(|| legacy::decode_planes(&tm, &gm, &zd, &[], c, lat).frames)); let (fo, frame_held) = with_policy(Policy::reference(), || peak_during(|| world.render(&zo, lat).unwrap().0)); assert!(fo == fd[0] && frame_held <= frame_direct, "{d}: one frame held {frame_held}; the direct frame held {frame_direct}"); println!("{d}: a clip of {} frames, set loaded beforehand, held {held}; the direct renderer held {direct}; loading the set held {set}; the call that loads it held {whole}; one step held {step_held} (direct {step_direct}); one frame {frame_held} (direct {frame_direct})", got.frames.len()); } }