Prune commentary and stale Rust port notes
This commit is contained in:
1 parent
3ae034a47b
commit
1e6d3b1edd
84 files changed
+334
-5648
No files matched your search
@@ -31,9 +31,6 @@ END_TENSION = 1.0
|
||||
P1 = START_TENSION * INFLEXION
|
||||
P2 = 1.0 - END_TENSION * (1.0 - INFLEXION)
|
||||
|
||||
# ViewConfiguration.getScrollFriction(), and SplineOverScroller's own
|
||||
# "look and feel tuning" constant -- a different number in a different place
|
||||
# of the same formula, which is the pair iris got the wrong way round once.
|
||||
SCROLL_FRICTION = 0.015
|
||||
TUNING = 0.84
|
||||
GRAVITY_EARTH = 9.80665
|
||||
@@ -54,7 +51,6 @@ def spline_positions():
|
||||
while True:
|
||||
x = x_min + (x_max - x_min) / 2.0
|
||||
coef = 3.0 * x * (1.0 - x)
|
||||
# Solved on the P1/P2 curve...
|
||||
tx = coef * ((1.0 - x) * P1 + x * P2) + x * x * x
|
||||
if abs(tx - alpha) < 1e-5:
|
||||
break
|
||||
@@ -62,7 +58,6 @@ def spline_positions():
|
||||
x_max = x
|
||||
else:
|
||||
x_min = x
|
||||
# ...and sampled on the tension curve.
|
||||
position[i] = coef * ((1.0 - x) * START_TENSION + x * END_TENSION) + x * x * x
|
||||
position[NB_SAMPLES] = 1.0
|
||||
return position
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -38,8 +38,6 @@ LINE_RE = re.compile(
|
||||
r"iris input: action=(?P<action>\w+) x=(?P<x>-?[0-9.]+) y=(?P<y>-?[0-9.]+) "
|
||||
r"t=(?P<t>[0-9]+)ms history=(?P<hist>[0-9]+)(?P<rest>.*)$"
|
||||
)
|
||||
# One historical sample inside `rest`: `t:x,y`, space-separated, oldest first
|
||||
# -- see `log_input_event`'s own doc for why order matters.
|
||||
HIST_RE = re.compile(r"(?P<t>[0-9]+):(?P<x>-?[0-9.]+),(?P<y>-?[0-9.]+)")
|
||||
|
||||
|
||||
|
||||
@@ -85,7 +85,6 @@ HORIZON_MILLISECONDS = 100.0
|
||||
ASSUME_POINTER_MOVE_STOPPED_MILLISECONDS = 40.0
|
||||
MIN_SAMPLE_SIZE_LSQ2 = 3
|
||||
|
||||
# ViewConfiguration.getScaledMaximumFlingVelocity(), in dp/s.
|
||||
MAXIMUM_FLING_VELOCITY_DP_S = 8000.0
|
||||
# DefaultFlingBehavior.performFling's own threshold, in the units of the
|
||||
# positions fed to the tracker -- pixels per second here.
|
||||
@@ -103,7 +102,6 @@ def poly_fit_least_squares(x, y, sample_count, degree):
|
||||
m = sample_count
|
||||
n = truncated_degree + 1
|
||||
|
||||
# a[i][h] = x[h]**i, pre-multiplied by the (always 1.0) weight.
|
||||
a = [[0.0] * m for _ in range(n)]
|
||||
for h in range(m):
|
||||
a[0][h] = 1.0
|
||||
@@ -219,7 +217,6 @@ def average(samples):
|
||||
return (samples[-1][1] - samples[0][1]) / span
|
||||
|
||||
|
||||
# --- The three recorded sample sets the Rust tests assert on. ----------------
|
||||
|
||||
# 1. `transcript-fixture/touch/flick-120hz.touch`, as `DragGesture` feeds it:
|
||||
# the DOWN position, then one position per MOVE. The UP at t=20 adds no
|
||||
@@ -243,19 +240,12 @@ ACCELERATING_FLICK = [(0, 0.0), (10, 2.0), (20, 6.0), (30, 14.0), (40, 30.0), (5
|
||||
# (a) An old, fast burst outside the 100ms horizon, then a slow steady
|
||||
# drag: the burst must not leak into the estimate.
|
||||
OLD_BURST_THEN_STEADY = [(0, 0.0)] + [(10 + i * 10, 1000.0 + i) for i in range(11)]
|
||||
# (b) The finger stops for 48ms and then lifts. The gap exceeds
|
||||
# AssumePointerMoveStopped, so the walk breaks after one sample and
|
||||
# there is no fling -- what stops a "park it and let go" from
|
||||
# flinging at whatever speed the finger arrived with.
|
||||
STOPPED_BEFORE_RELEASE = [(0, 0.0), (4, 40.0), (8, 90.0), (12, 150.0), (60, 152.0)]
|
||||
|
||||
# 5. `sense.rs`'s own `drag_gesture_tests`: what `DragGesture` feeds for a
|
||||
# press and two move frames, which is the fewest a fit can use.
|
||||
TWO_MOVE_FRAMES = [(0, 0.0), (8, 100.0), (16, 220.0)]
|
||||
# ... and one move frame, which Compose cannot fit either.
|
||||
ONE_MOVE_FRAME = [(0, 0.0), (8, 100.0)]
|
||||
|
||||
# The phone: 1080x2424 at content_scale 2.55.
|
||||
PHONE_DENSITY = 2.55
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user