//! The screen the bench renders. //! //! A copy of MNW's `library_contacts` pane, which is a real converted screen //! rather than a shape invented for a benchmark. It was picked for the same //! reason that batch picked it: it holds every table member the vocabulary has //! grown and nothing that is still unnamed, so a cost measured here is a cost a //! shipped screen pays. //! //! What it deliberately keeps from the original: two tables rather than one, a //! value that is a link in two flavours (a route this app answers and a //! `mailto:` that leaves), a destructive per-row act, and column widths and //! priorities that make the narrowing pass do work. A single-column table of //! plain strings would measure the loop and not the renderer. //! //! The row counts are the bench's axis, not the screen's: see [`SIZES`]. use quasi_router::Node; use crate::staging; /// Row counts, and why these three. /// /// Any single number hides it. /// /// 5 is a screen someone actually has, 25 is a full page, 200 is the size at /// which a per-row cost stops being deniable. pub const SIZES: [usize; 3] = [5, 25, 200]; /// One buyer row's worth of strings. pub(crate) struct Buyer { pub(crate) username: String, pub(crate) email: String, pub(crate) purchases: String, pub(crate) spent: String, pub(crate) last_purchase: String, } /// One shared-with row's worth of strings. pub(crate) struct Shared { pub(crate) seller_id: String, pub(crate) username: String, pub(crate) name: String, } /// Rows built once, outside the timed region. /// /// The bench measures rendering, so building the strings must not be inside the /// loop. They are generated rather than repeated so that no two cells are the /// same string, which keeps an allocator from making a repeated value look /// cheaper than a real one. pub struct Rows { pub(crate) buyers: Vec, pub(crate) shared: Vec, } impl Rows { /// `count` rows in each of the two tables. #[must_use] pub fn new(count: usize) -> Self { let buyers = (0..count) .map(|i| Buyer { username: format!("buyer{i:04}"), email: format!("buyer{i:04}@example.com"), purchases: (i % 37 + 1).to_string(), spent: format!("${}.{:02}", i * 7 % 900 + 3, i % 100), last_purchase: format!("Aug {:02}, 2026", i % 28 + 1), }) .collect(); let shared = (0..count) .map(|i| Shared { seller_id: format!("{:08}", i * 31 + 7), username: format!("creator{i:04}"), name: format!("Creator Number {i}"), }) .collect(); Self { buyers, shared } } } /// The pane, built the way the real screen builds it. /// /// Rebuilt per iteration on purpose. Building the description is part of what a /// request pays, and a bench that hoisted it out would measure the renderer /// against a tree the handler never actually has to make. /// /// The shape itself lives in [`crate::staging`], written once against the /// `Data` seam so that the runtime path and the staged path cannot drift. This /// is that one description evaluated over real strings. #[must_use] pub fn pane(rows: &Rows) -> Node { staging::describe(&staging::Live(rows)) }