Prune commentary and stale Rust port notes

This commit is contained in:
iris committed 2026-09-10 00:44:13 -04:00
1 parent 3ae034a47b
commit 1e6d3b1edd
84 files changed
+334 -5648

No files matched your search

-129
View File
@@ -1,75 +1,6 @@
//! On-demand benchmarks for iris's message-list scenario -- IRIS_TODO.md's
//! "Benchmarks" item, and RUST.md's I3. Never run by `cargo test`; run
//! explicitly with `cargo bench --bench message_list --release` or
//! `./run-bench.sh`.
//!
//! **Why a plain `Instant`-timed binary, not criterion.** Every scenario
//! here is really "how many `Widget::draw` calls and primitive rewrites did
//! this frame cost," which `UiRenderState::take_counters` already answers
//! exactly (see `iris/src/layout_tests.rs`, which this file's harness
//! mirrors). A short loop that times itself and prints the counters
//! alongside the wall time says everything criterion's warm-up/sampling/
//! outlier-removal machinery would add on top, for scenarios that are
//! fundamentally about a *count*, not a noisy microbenchmark distribution
//! -- and it avoids a new dependency this crate does not otherwise need.
//! Per the code rules, the plain option is also the one shorter to explain.
//!
//! **The list under test is `iris::widget::LazySpan` (RUST.md's I3), not a
//! `ScrollArea` over a `Span` of pre-built rows.** Earlier versions of this
//! file built their own giant `Span` and wrapped it in `ScrollArea`, which
//! meant (a)/(b)/(c) below were measuring "move one big child," never the
//! virtualised widget the app's transcript screen actually needs. `LazySpan`
//! still needs every row's *widget* built up front by the caller (its
//! module doc explains why: it only ever sees `&dyn Widget` through
//! `Painter`, so it cannot construct a row lazily on its own) -- what
//! virtualisation buys is that only the rows currently on screen are ever
//! *drawn*, which is what the draw/rewrite/move counters below are
//! measuring, not construction time.
//!
//! Scenarios (LAYOUT.md's O(1) move chain, lazy_span.rs's module doc, and
//! IRIS_TODO.md's "Benchmarks" wording):
//!
//! - (a) first-frame cost of a message list of N wrapped-text rows, some
//! with an image, for N = 100 / 1,000 / 10,000. With a virtualised list
//! this is expected to stop scaling with N once N exceeds a screenful --
//! the draw/rewrite counters below are the number that used to grow 10x
//! per 10x N and should not any more.
//! - (b) per-frame cost of scrolling that list -- must be O(1) moves, not
//! re-layout.
//! - (c) the input-box case: growing a fixed-height field at the bottom of
//! the screen must move the message list above it, not re-lay its rows.
//! Reports frame time *and* the draw/rewrite/move counters LAYOUT.md
//! section 8 defines.
//! - (d) insert-above-anchor: paging older history onto the front of an
//! already-scrolled list. `LazySpan::push_front` is an O(1) index update
//! (lazy_span.rs's module doc); this measures that none of the rows already
//! on screen are touched by it.
//! - (e) expand-a-row-holding-its-edge: growing one row's height with a
//! tap recorded near one of its edges (lazy_span.rs's `note_tap`) must move
//! only the rows on the far side of it, never redraw the ones already
//! correctly placed.
//!
//! - (g) redraw-one-big-text: a single text widget of N glyphs redrawn in
//! place, which is what a tool card rebuilt on a tap costs. Every one of
//! its primitives is freed and rewritten, and so renumbered in the
//! layer's draw order -- the pass that used to be O(N^2) there
//! (`UiRenderState::apply_free`, fixed 2026-09-08). The number to watch
//! is per-glyph: it must stay flat as N grows, not grow with it.
//!
//! (f), many images with zero steady-state bind-group creation, needs a
//! real `wgpu` device and lives in `iris/examples/bench_images.rs` instead,
//! driven through `run-headless.sh` -- see that file's header.
//!
//! `UiRenderState`/`Widgets` touch no GPU or window (as `layout_tests.rs`
//! notes), so everything here runs as an ordinary `--release` binary with
//! no compositor. Numbers are recorded in RUST.md's I3 box, not here --
//! this file is the rig, not the result.
use iris::prelude::*;
use std::time::Instant;
/// The minimal `UiRsc` a benchmark needs -- identical in shape to
/// `layout_tests.rs`'s `TestRsc`.
struct BenchRsc {
ui: UiData,
}
@@ -83,18 +14,12 @@ impl UiRsc for BenchRsc {
}
}
/// Long enough to force real wrapping at a phone-plausible column width, and
/// varied enough (no two rows byte-identical) that nothing can special-case
/// on repeated content.
const BODY: &str = "The quick brown fox jumps over the lazy dog. Iris lays \
out wrapped text by shaping once per width and caching the result, so a \
row that is offered the same width twice does not reshape. This sentence \
exists only to give a row enough text to wrap across several lines at a \
typical phone column width.";
/// One message row: a wrapped `Text`, and every `image_every`th row also an
/// `Image` beneath it -- a small in-memory RGBA square rather than a file,
/// so N=10,000 rows costs no disk I/O.
fn build_row(rsc: &mut BenchRsc, i: usize, image_every: usize) -> StrongWidget {
let mut text = Text::new(format!("Message {i}: {BODY}"));
text.wrap = true;
@@ -113,9 +38,6 @@ fn build_row(rsc: &mut BenchRsc, i: usize, image_every: usize) -> StrongWidget {
}
}
/// A virtualised `LazySpan` of `n` message rows, one in `image_every` of them
/// carrying an image (0 disables images entirely). Returns the list widget
/// (weak, so the caller can drive it) and the erased root to render.
fn build_message_list(
rsc: &mut BenchRsc,
n: usize,
@@ -127,9 +49,6 @@ fn build_message_list(
list.push_back(LazyItem::new(i as u64, row));
}
let list = rsc.ui.widgets.add_strong(list);
// Driven through the span's own `ScrollController`, like every other
// scroll area in iris: what this measures has to be the path the app
// actually takes.
(list.weak(), list.any())
}
@@ -140,7 +59,6 @@ fn report(label: &str, elapsed: std::time::Duration, draws: u64, rewrites: u64,
);
}
/// (a) First-frame cost of a message list of N rows.
fn bench_first_frame(n: usize) {
let mut rsc = BenchRsc {
ui: UiData::default(),
@@ -162,10 +80,6 @@ fn bench_first_frame(n: usize) {
);
}
/// (b) Per-frame cost of scrolling an already-laid-out list of N rows.
/// Warms up (one no-op tick, matching `ScrollArea`'s own need for it before an
/// ordinary Rust `layout_tests.rs` scrolling test becomes a same-size move
/// rather than a resize), then times a run of individual scroll ticks.
fn bench_scroll(n: usize, ticks: usize) {
let mut rsc = BenchRsc {
ui: UiData::default(),
@@ -205,14 +119,6 @@ fn bench_scroll(n: usize, ticks: usize) {
);
}
/// (c) The input-box case: a fixed-height field at the bottom of the screen
/// growing by a line at a time, with a message list of N rows filling the
/// rest of the screen above it. Growing the input shrinks the *offered*
/// height of the list container (a single widget, from the outer `Span`'s
/// point of view) without changing the width it offers its content -- so
/// the rows underneath, which only care about width, must not redraw; the
/// list's own re-registration of where its content sits is the one O(1)
/// move this is checking for.
fn bench_input_grows(n: usize, lines: usize) {
let mut rsc = BenchRsc {
ui: UiData::default(),
@@ -276,13 +182,6 @@ fn bench_input_grows(n: usize, lines: usize) {
);
}
/// (d) Insert-above-anchor: the list is scrolled to its very first loaded
/// row (`jump_to_start`, an O(1) re-anchor) rather than left at the default
/// bottom, so a row prepended above it is genuinely "inserted above the
/// anchor" rather than merely far off-screen at the far end. Each
/// `push_front` is O(1) (lazy_span.rs's module doc: the anchor's slot is an
/// index, bumped by one) and, since the prepended rows never enter the
/// viewport, none of them should cost a draw either.
fn bench_insert_above_anchor(n: usize, inserts: usize) {
let mut rsc = BenchRsc {
ui: UiData::default(),
@@ -300,9 +199,6 @@ fn bench_insert_above_anchor(n: usize, inserts: usize) {
let mut total_rewrites = 0u64;
let mut total_moves = 0u64;
for i in 0..inserts {
// Older-history rows: distinct keys below every existing one, so a
// real caller's paging code (prepending an older page) is exactly
// what this loop does.
let row = build_row(&mut rsc, usize::MAX - i, 20);
rsc.ui
.widgets
@@ -333,21 +229,11 @@ fn bench_insert_above_anchor(n: usize, inserts: usize) {
);
}
/// (e) Expand-a-row-holding-its-edge: one row (fixed-height, so its size is
/// directly controllable) is grown a little at a time, each time preceded
/// by `note_tap` aimed at its own top edge -- the exact mechanism lazy_span.rs's
/// module doc describes and its unit tests check for correctness. This
/// measures its *cost*: only the rows on the far side of the grown one
/// (below it, since the top edge is held) should ever move, and nothing
/// should be redrawn purely because the list overall got taller.
fn bench_expand_holds_edge(n: usize, growths: usize) {
let mut rsc = BenchRsc {
ui: UiData::default(),
};
let mut list = LazySpan::new(Dir::DOWN, Pin::End);
// Near the end (not the very last row) so it is already on screen
// under the list's default bottom-anchored placement, for every N --
// no scrolling needed to bring it into view before measuring.
let growable_index = n.saturating_sub(3);
let mut growable = None;
for i in 0..n {
@@ -415,23 +301,10 @@ fn bench_expand_holds_edge(n: usize, growths: usize) {
);
}
/// (g) One text widget of `chars` characters, redrawn in place `redraws`
/// times -- an open tool card whose content is rebuilt, or any widget
/// holding a lot of text that a tap changes.
///
/// A redraw frees every primitive the widget owned and writes fresh ones,
/// so every glyph is renumbered in its layer's draw order. Finding the
/// handle to renumber used to be a scan of everything the same widget
/// drew, which made one redraw quadratic in its own glyph count: 1.37s for
/// 51,200 glyphs on this machine, against 20ms to shape and rasterise the
/// same text. Print per-glyph rather than per-redraw, since flat is the
/// pass condition and a total says nothing without dividing it.
fn bench_redraw_big_text(chars: usize, redraws: usize) {
let mut rsc = BenchRsc {
ui: UiData::default(),
};
// One character per glyph, and varied so nothing can collapse the
// string into a repeat.
let content: String = (0..chars)
.map(|i| char::from(b'a' + (i % 26) as u8))
.collect();
@@ -448,8 +321,6 @@ fn bench_redraw_big_text(chars: usize, redraws: usize) {
let mut total = std::time::Duration::ZERO;
for _ in 0..redraws {
// Asking for the widget mutably is what marks it for redraw --
// the same path a caller changing its content takes.
rsc.ui.widgets.get_mut(&handle).unwrap();
let start = Instant::now();
render.update(&root, &mut rsc);