From 122b10143ce748dc7a6747d14fbaecd6cec38c00 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 5 Nov 2026 07:16:11 -0800 Subject: [PATCH] Layers: a document opened after a close keeps its thumbnail jobs Closing a document told the worker to forget every row whose document was not open at that moment. The worker keeps that test until it is idle, so while a job still ran, a document opened afterward had its rows dropped and they stayed without thumbnails. The worker now forgets only the documents that closed. --- crates/ui-egui/src/panels/layers/thumbs.rs | 40 +++++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/crates/ui-egui/src/panels/layers/thumbs.rs b/crates/ui-egui/src/panels/layers/thumbs.rs index 49ce4b61b..4fcdc03e4 100644 --- a/crates/ui-egui/src/panels/layers/thumbs.rs +++ b/crates/ui-egui/src/panels/layers/thumbs.rs @@ -266,18 +266,17 @@ impl Thumbs { return; } let open: HashSet = uids.iter().copied().collect(); - let closed = self.open_docs.as_ref().is_some_and(|before| before.iter().any(|uid| !open.contains(uid))); + let closed: HashSet = self.open_docs.as_ref().map(|before| before.difference(&open).copied().collect()).unwrap_or_default(); let stale = self.entries.keys().chain(self.pending.keys()).any(|key| !open.contains(&key.0)) || self.showing.is_some_and(|uid| !open.contains(&uid)); self.entries.retain(|key, _| open.contains(&key.0)); self.pending.retain(|key, _| open.contains(&key.0)); self.showing = self.showing.filter(|uid| open.contains(uid)); - if closed { + if !closed.is_empty() { // Images finished before now are dropped with the rest of the queue: the open - // documents' rows are asked for again. + // documents' rows are asked for again. Documents opened later keep their jobs. if let Some(w) = &self.worker { - let open = open.clone(); - w.forget(move |key| !open.contains(&key.0)); + w.forget(move |key| closed.contains(&key.0)); if w.poll().is_none() { self.worker = None; } @@ -536,6 +535,37 @@ mod tests { assert_eq!(done.key.0, next, "no worker keeps the closed document's image"); } + #[test] + fn a_document_opened_while_a_job_runs_after_a_close_gets_its_thumbnails() { + let ctx = egui::Context::default(); + let Some(worker) = ThumbWorker::start(&ctx, "test") else { return }; + let mut thumbs = Thumbs { worker: Some(worker), worker_started: true, ..Default::default() }; + thumbs.retain_open(&[1, 2]); + // A job of document 2 is running. + let (started, running) = std::sync::mpsc::channel::<()>(); + let (release, held) = std::sync::mpsc::channel::<()>(); + let blocker = (2, NodeId(1)); + let job: crate::thumb_worker::JobFn = Box::new(move |_| { + started.send(()).ok(); + held.recv().ok(); + None + }); + assert!(thumbs.worker.as_ref().is_some_and(|w| w.submit(blocker, (blocker, Weak::new(), 1), job))); + running.recv().expect("the job starts"); + // Document 1 closes, then document 3 opens and one of its rows is queued. + thumbs.retain_open(&[2]); + thumbs.retain_open(&[2, 3]); + let opened = (3, NodeId(2)); + assert!(thumbs.worker.as_ref().is_some_and(|w| w.submit(opened, (opened, Weak::new(), 1), Box::new(|_| None)))); + // A job of document 2 queued after it marks the end of the queue. + let last = (2, NodeId(3)); + assert!(thumbs.worker.as_ref().is_some_and(|w| w.submit(last, (last, Weak::new(), 1), Box::new(|_| None)))); + release.send(()).ok(); + let worker = thumbs.worker.as_ref().expect("the worker is kept"); + let order: Vec<(u64, NodeId)> = (0..3).map_while(|_| worker.wait().map(|d| d.key.0)).take_while(|key| *key != last).collect(); + assert_eq!(order, [blocker, opened], "document 3's job comes back before the last one"); + } + /// An open zigzag of `anchors` anchors. fn line(anchors: usize) -> PathData { let pts: Vec = (0..anchors).map(|i| Point::new(i as f64 * 0.01, (i % 2) as f64 * 50.0)).collect();