Thumbnails: count the renders, not only the thumbnails held (#969)

Follow-up to #1011, as asked on #1038: a counter of thumbnails actually
rendered, and tests that use it.

`Textures::thumb_count` is the number of thumbnails *held*. It settles at the
rows in view whether the pane rendered them once or a thousand times, so it
cannot see a pane that renders the same rows over and over -- which is what
#969 was: the flashing white was the rendering, not the holding.

`Textures::thumb_renders` counts the renders, and a settled pane is one that
has stopped asking for them.

The two new tests assert what the held count cannot:

- every thumbnail held was rendered, and not many more than that were rendered
  and thrown away (measured: 8 held, 8 rendered at rest);
- ten more frames of the same view render nothing at all;
- after a 40,000 pt scroll of an 800-slide deck, 24 renders in total -- the
  rows it landed on and the ones it passed over, not the deck -- and then it
  settles again.

With the cache made to thrash (its 600-entry limit cut to 4, so the rows in
view are evicted and re-rendered every frame), the merged test for the Slide
Sorter still passes -- the held count settles at 4 -- while this one fails with
"255 renders for 4 thumbnails". That is the hole the counter closes.
This commit is contained in:
Hamza
2026-10-22 19:20:07 +05:00
parent a2435383b1
commit 42e89b347d
2 changed files with 68 additions and 0 deletions
+57
View File
@@ -74,3 +74,60 @@ fn the_sorter_of_a_huge_deck_renders_only_the_slides_in_view() {
h.run_steps(10);
assert_eq!(h.state().textures.thumb_count(), settled);
}
/// What the count of thumbnails *held* cannot see: a pane that renders the same rows over and
/// over. The cache settles at the rows in view either way, so `thumb_count` is the same whether
/// the pane rendered them once or a thousand times — and the flashing white of #969 was the
/// rendering, not the holding. `thumb_renders` counts the renders, so a settled pane is one that
/// has stopped asking for them.
///
/// A follow-up to #1011, as skylartaylor asked on #1038.
#[test]
fn a_settled_thumbnail_pane_renders_nothing_more() {
let mut h = harness(800);
h.run_steps(60);
let (held, rendered) = (h.state().textures.thumb_count(), h.state().textures.thumb_renders());
// Every thumbnail held was rendered, and the pane did not render many it then threw away:
// this is the assertion a count of held thumbnails cannot make, and it comes first so that a
// thrashing pane is reported as thrashing rather than as holding too few.
assert!(rendered >= held as u64, "{rendered} renders for {held} thumbnails");
assert!(rendered < held as u64 * 2, "{rendered} renders for {held} thumbnails: the pane is re-rendering");
assert!(held > 3 && held < 60, "{held} thumbnails held");
// Ten more frames of the same view: not one more render.
h.run_steps(10);
assert_eq!(h.state().textures.thumb_renders(), rendered, "a settled pane renders nothing more");
assert_eq!(h.state().textures.thumb_count(), held);
// Scrolled far down: the slides now in view are rendered, and only those -- the renders go up
// by about a screen's worth, not by the whole deck.
h.hover_at(egui::pos2(100.0, 450.0));
h.event(egui::Event::MouseWheel {
unit: egui::MouseWheelUnit::Point,
delta: egui::vec2(0.0, -40_000.0),
modifiers: egui::Modifiers::NONE,
phase: egui::TouchPhase::Move,
});
h.run_steps(60);
let after = h.state().textures.thumb_renders();
// Measured: 8 held and 8 rendered at rest, 24 rendered after the scroll -- the rows it
// landed on plus the ones it passed over, not the 800 slides of the deck.
assert!(after > rendered, "the slides now in view are rendered: {after} vs {rendered}");
assert!(after < 100, "{after} renders after one scroll of an 800-slide deck (was {rendered})");
// And it settles again.
h.run_steps(10);
assert_eq!(h.state().textures.thumb_renders(), after, "settled again");
}
/// The same for the Slide Sorter: it settles, and stays settled.
#[test]
fn a_settled_sorter_renders_nothing_more() {
let mut h = harness(800);
h.state_mut().ui.view = crate::ViewMode::Sorter;
h.run_steps(60);
let (held, rendered) = (h.state().textures.thumb_count(), h.state().textures.thumb_renders());
assert!(rendered >= held as u64 && rendered < held as u64 * 2, "{rendered} renders for {held} thumbnails");
assert!(held > 3 && held < 80, "{held} thumbnails held");
h.run_steps(10);
assert_eq!(h.state().textures.thumb_renders(), rendered, "a settled sorter renders nothing more");
}
+11
View File
@@ -47,6 +47,10 @@ pub struct Textures {
/// Canvas renders so far (tests, performance checks).
pub canvas_renders: u64,
thumbs: HashMap<usize, Entry>,
/// Thumbnails actually rendered, over the life of this cache. The number *held*
/// ([`Textures::thumb_count`]) cannot see a pane that renders the same rows over and over --
/// the cache thrash behind #969 -- because the count settles while the work does not.
thumb_renders: u64,
/// SmartArt gallery pictures by layout, style and colours (and, for hover previews, the
/// outline's hash).
smartart: HashMap<String, TextureHandle>,
@@ -345,6 +349,12 @@ impl Textures {
self.thumbs.len()
}
/// How many thumbnails this cache has rendered, ever. A pane that is done asks for no more:
/// the number stops climbing, which is what tells a thrashing pane from a settled one.
pub fn thumb_renders(&self) -> u64 {
self.thumb_renders
}
/// Force the canvas to re-render next time (fonts loaded, theme changed in place…).
pub fn invalidate(&mut self) {
self.canvas = None;
@@ -384,6 +394,7 @@ impl Textures {
let (tw, th) = fit_size((width_px, h), max_side(ctx));
let scale = tw as f64 / p.slide_size.width.max(1.0);
let img = deckcraft_render::render_slide(p, index, &RenderOpts { scale, size: Some((tw, th)), threads: 0, ..Default::default() });
self.thumb_renders += 1;
let tex = upload_editor(ctx, format!("thumb-{index}"), to_color_image(&img, false));
// Drop the old entry for this slide address; entries for slides that no longer exist age out.
let region = key.want;