iris: count text layouts, so "a delta shapes one block" is measured rather than argued

take_counters gains a fourth counter, text shapes, bumped in
Painter::render_text -- which TextView::render only reaches on a cache
miss, so it counts shapes and not requests. A draw counter cannot stand
in for it in either direction: a widget can be redrawn without
re-shaping (the layout is memoized by width) and re-shaped without any
extra draw, and re-shaping is the whole thing the per-block transcript
row exists to avoid.

With it, a_delta_into_a_long_reply_redraws_the_same_widgets_as_a_short_one
asserts the number docs/DECISIONS.md's 2026-09-06 entry actually claims:
one delta into a 100-paragraph reply shapes exactly one text layout, the
same as into a one-paragraph one. Before the split that was necessarily
O(message), since the reply was one buffer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Fable 5.1 committed 2026-09-06 18:32:31 -04:00
1 parent 155d899e55
commit c3cfc67bb3
6 files changed
+43 -15

No files matched your search

+4
View File
@@ -191,6 +191,10 @@ impl<'a> Painter<'a> {
width: Option<f32>,
) -> RenderedText {
let density = self.state.density;
// Counted here rather than in `TextView::render`, which returns
// its memoized layout without reaching this -- so this counts
// shapes, not requests. `UiRenderState::take_counters`.
self.state.shape_count += 1;
let ui = self.rsc.ui_mut();
ui.text
.render(buffer, attrs, width, &mut ui.textures, density)
+14 -3
View File
@@ -55,6 +55,9 @@ pub struct UiRenderState {
draw_count: u64,
region_mut_count: u64,
mov_count: u64,
/// Text layouts actually computed -- bumped by `Painter::render_text`,
/// which `TextView::render` only reaches on a cache miss.
pub(super) shape_count: u64,
}
/// A move chain more than this deep would mean something else is wrong
@@ -76,17 +79,25 @@ impl UiRenderState {
draw_count: 0,
region_mut_count: 0,
mov_count: 0,
shape_count: 0,
}
}
/// Reads and zeroes the (draws, region_mut rewrites, move_offsets
/// writes) counters -- call once per frame before `update()` to
/// measure exactly that frame, per LAYOUT.md section 8.
pub fn take_counters(&mut self) -> (u64, u64, u64) {
/// writes, text shapes) counters -- call once per frame before
/// `update()` to measure exactly that frame, per LAYOUT.md section 8.
///
/// The fourth is the one a draw count cannot stand in for: a widget
/// can be redrawn without re-shaping (`TextView::render` memoizes by
/// width) and re-shaped without any extra draw, and it is re-shaping
/// that the per-block transcript row exists to avoid -- see
/// `transcript_ui`'s `a_delta_into_a_long_reply_shapes_one_block`.
pub fn take_counters(&mut self) -> (u64, u64, u64, u64) {
(
std::mem::take(&mut self.draw_count),
std::mem::take(&mut self.region_mut_count),
std::mem::take(&mut self.mov_count),
std::mem::take(&mut self.shape_count),
)
}