Layers: the thumbnail drag test measures renders by a test clock

row_thumbnails_wait_for_the_end_of_a_drag expected every row's
thumbnail to render in the first pass. The UI thread renders row
thumbnails for at most 8 ms of wall-clock time per pass, so on a busy
machine the first pass rendered only some rows, the next pass rendered
the rest, and the test failed at "and stay".

The thumbnail cache now takes a clock in tests (as the app's
now_override does for idle scheduling). The drag test gives every
render no time, and a new test gives each render 5 ms and checks that
three rows render two in the first pass and one in the next. The 8 ms
budget and the release build are unchanged.
This commit is contained in:
dotCooCoo
2026-11-11 19:58:59 -08:00
parent 765bd30783
commit ff3067c7f7
2 changed files with 47 additions and 2 deletions
+32
View File
@@ -272,12 +272,44 @@ fn closing_a_document_releases_only_its_layer_thumbnails() {
});
}
/// From now on each reading of this thread's render budget clock is `ms` later than the one before.
/// A render reads it before and after, so each row thumbnail rendered takes `ms` of the budget.
fn clock_steps(ms: f64) {
let mut now = 0.0;
THUMBS.with(|c| {
c.borrow_mut().clock = Some(Box::new(move || {
now += ms;
now
}))
});
}
/// Rendering on the UI thread stops for the pass once the thumbnails rendered in it took the
/// budget, and the rest render in the next passes.
#[test]
fn row_thumbnails_past_the_render_budget_wait_for_the_next_pass() {
let (mut app, _, _) = two_rects();
let ctx = egui::Context::default();
let renders = || THUMBS.with(|c| c.borrow().renders);
// Each render takes 5 ms: after two renders the pass has used 10 ms, past the 8 ms budget.
clock_steps(5.0);
let mut per_pass = vec![];
for _ in 0..4 {
let before = renders();
frame(&mut app, &ctx, vec![], false);
per_pass.push(renders() - before);
}
assert_eq!(per_pass, [2, 1, 0, 0], "the layer's and two objects' thumbnails, two in the first pass");
}
#[test]
fn row_thumbnails_wait_for_the_end_of_a_drag() {
let (mut app, _, _) = two_rects();
app.session.execute("select.all", &json!({})).unwrap();
let ctx = egui::Context::default();
let renders = || THUMBS.with(|c| c.borrow().renders);
// Renders take no time, so every row's thumbnail fits in a pass however busy the machine is.
clock_steps(0.0);
frame(&mut app, &ctx, vec![], false);
let first = renders();
assert!(first >= 1, "the rows' thumbnails render at once");
+15 -2
View File
@@ -65,6 +65,10 @@ pub(super) struct Thumbs {
spent: (u64, f64, u32),
/// Thumbnails rendered on the UI thread (tests count them).
pub(super) renders: u64,
/// A test's clock (ms) for the render budget, replacing [`crate::now_ms`] so a test decides how
/// long each render takes (production always leaves this `None`).
#[cfg(test)]
pub(super) clock: Option<Box<dyn FnMut() -> f64>>,
}
thread_local! {
@@ -258,6 +262,15 @@ impl Thumbs {
self.entries.keys().any(|key| key.0 == uid)
}
/// The time (ms) a render on the UI thread is measured by: [`crate::now_ms`], or a test's clock.
fn now_ms(&mut self) -> f64 {
#[cfg(test)]
if let Some(clock) = self.clock.as_mut() {
return clock();
}
crate::now_ms()
}
/// Release thumbnails and queued jobs for documents that have closed, keeping the cache for
/// other open documents even when the panels are hidden or another document is active. When a
/// document closes, the worker drops its jobs, the one running included, and the images it
@@ -416,9 +429,9 @@ impl Thumbs {
thread_local! {
static RENDERER: std::cell::RefCell<vectorcraft_render::Renderer> = std::cell::RefCell::new(vectorcraft_render::Renderer::new());
}
let t0 = crate::now_ms();
let t0 = self.now_ms();
let img = RENDERER.with(|r| render_thumb(&mut r.borrow_mut(), src.doc, n, px));
let ms = crate::now_ms() - t0;
let ms = self.now_ms() - t0;
self.spent.1 += ms;
self.spent.2 += 1;
self.renders += 1;