From 0bfcc8024e91273900c5fcfebee31e3a7939562b Mon Sep 17 00:00:00 2001 From: quincy-jc Date: Mon, 9 Nov 2026 11:47:32 +1100 Subject: [PATCH] fix(canvas): release closed panels' thumbnails while a visible page renders (#6227) --- crates/ui-egui/src/canvas/cache.rs | 7 +++- crates/ui-egui/src/canvas/tests.rs | 58 ++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/canvas/cache.rs b/crates/ui-egui/src/canvas/cache.rs index bef9d21975..9a0abad953 100644 --- a/crates/ui-egui/src/canvas/cache.rs +++ b/crates/ui-egui/src/canvas/cache.rs @@ -458,7 +458,12 @@ impl DocView { && !self.errors.contains_key(&r.page) }); if visible_mains_pending { - // Caches are kept; demand is re-evaluated next frame once pixels arrive. + // Caches are kept; demand is re-evaluated next frame once pixels arrive. Thumbnails + // nothing shows any more (their panel closed) go now, though: no consumer is waiting. + if self.thumb_demand.is_empty() { + self.thumbs.clear(); + self.stale_thumbs.clear(); + } return queue; } let thumbs = self.thumbnail_requests(info, ppp); diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index 0e86db7914..d8a5259251 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -1428,6 +1428,64 @@ fn thumbnails_release_on_scroll_panel_close_and_request_again_after_eviction() { assert!(v.prepare_render_queue(&info, 1.0).is_empty(), "closing consumers cancels thumbnail work"); } +#[test] +fn closing_the_last_thumbnail_consumer_releases_thumbnails_while_a_visible_page_renders() { + let ctx = egui::Context::default(); + let info = thumbnail_info(1); + let mut v = DocView::new(DocId(1), &info, ViewDefaults::default()); + v.need_thumbnail(0, true); + let first = v.prepare_render_queue(&info, 1.0); + v.last_queue = first.clone(); + assert_eq!(v.receive_result(&ctx, solid_result(first[0], 132, 171)), 132 * 171 * 4); + let main = RenderRequest { scale: 1.0, tag: 1000, ..Default::default() }; + v.begin_render_frame(); + v.need_thumbnail(0, true); + v.frame_queue = vec![main]; + v.frame_visible.insert(0); + assert_eq!(v.prepare_render_queue(&info, 1.0), vec![main]); + assert!(!v.thumbs.is_empty(), "a thumbnail still shown waits for the page"); + v.begin_render_frame(); + v.frame_queue = vec![main]; + v.frame_visible.insert(0); + v.stale_thumbs.insert(0); + assert_eq!(v.prepare_render_queue(&info, 1.0), vec![main]); + assert!(v.thumbs.is_empty(), "nothing shows it any more, whatever the page is doing"); + assert!(v.stale_thumbs.is_empty(), "nor is it remembered as out of date"); + // Shown again once the page is drawn: the thumbnail is asked for afresh. + v.last_queue = vec![main]; + assert!(v.receive_result(&ctx, solid_result(main, 100, 130)) > 0); + v.begin_render_frame(); + v.frame_visible.insert(0); + v.need_thumbnail(0, true); + let queue = v.prepare_render_queue(&info, 1.0); + assert!(queue.iter().any(|r| r.page == 0 && r.tag & THUMB_TAG != 0), "the thumbnail is asked for again: {queue:?}"); +} + +/// While anything still shows a thumbnail, the thumbnails all wait for the visible page, as before: +/// only the last consumer closing lets them go early. +#[test] +fn a_thumbnail_still_shown_keeps_the_others_while_a_visible_page_renders() { + let ctx = egui::Context::default(); + // Pages 0 and 3 are the same size. + let info = thumbnail_info(4); + let mut v = DocView::new(DocId(1), &info, ViewDefaults::default()); + v.need_thumbnail(0, true); + v.need_thumbnail(3, true); + let first = v.prepare_render_queue(&info, 1.0); + v.last_queue = first.clone(); + for r in &first { + assert!(v.receive_result(&ctx, solid_result(*r, 132, 171)) > 0, "thumbnail of page {} taken", r.page); + } + assert_eq!(v.thumbs.len(), 2); + let main = RenderRequest { scale: 1.0, tag: 1000, ..Default::default() }; + v.begin_render_frame(); + v.need_thumbnail(0, true); + v.frame_queue = vec![main]; + v.frame_visible.insert(0); + assert_eq!(v.prepare_render_queue(&info, 1.0), vec![main]); + assert!(v.thumbs.contains_key(&0) && v.thumbs.contains_key(&3), "both wait for the page"); +} + #[test] fn stale_results_are_rejected_before_conversion_after_dpi_invalidation_and_suspend() { let ctx = egui::Context::default();