From d43da778b66d5e7cc85edd335ec2beeade24d16d Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Tue, 13 Oct 2026 16:29:21 +0200 Subject: [PATCH] perf(app): refresh only the resume lists when a playing episode saves its place A position save every five seconds marked the whole library view stale, which re-grouped franchises, re-picked shelves and re-arranged the grid on every save. Only the history and Continue watching lists move then, unless the grid itself is filtered or sorted by watching. --- crates/pstr-app/src/app.rs | 43 +++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/crates/pstr-app/src/app.rs b/crates/pstr-app/src/app.rs index 09cdc81..9daac28 100644 --- a/crates/pstr-app/src/app.rs +++ b/crates/pstr-app/src/app.rs @@ -404,6 +404,9 @@ pub struct LibraryView { pub resumable: Vec, /// The library or what is known about it changed under the lists. stale: bool, + /// Only watch state changed: the shelves and franchises still stand, + /// and the grid does unless it is filtered or sorted by watching. + progressed: bool, } impl LibraryView { @@ -435,6 +438,7 @@ impl Default for LibraryView { history: Vec::new(), resumable: Vec::new(), stale: false, + progressed: false, } } } @@ -460,7 +464,19 @@ impl LibraryView { use pstr_core::library::Title; let built_with = (self.filter, self.sort, self.grouped, inputs.names); - if !self.stale && self.query == inputs.query && self.built_with == Some(built_with) { + let unchanged = self.query == inputs.query && self.built_with == Some(built_with); + // A playing episode saves its position every few seconds. Rebuilding + // franchises, shelves and the arranged grid for each save is most of + // a frame on a big library, for lists that did not move. + let watch_shapes_grid = matches!(self.filter, Filter::Watching | Filter::Unwatched) + || self.sort == Sort::Recent; + if !self.stale && self.progressed && unchanged && !watch_shapes_grid { + self.history = ui::history::entries(library); + self.resumable = Self::resumable(library); + self.progressed = false; + return; + } + if !self.stale && !self.progressed && unchanged { return; } let metadata = inputs.metadata; @@ -537,16 +553,27 @@ impl LibraryView { .collect(); self.matches = entries.into_iter().map(|(lead, _)| lead).collect(); self.built_with = Some(built_with); - if self.stale { + if self.stale || self.progressed { self.history = ui::history::entries(library); - self.resumable = library - .continue_watching() - .into_iter() - .filter_map(index_of) - .collect(); + self.resumable = Self::resumable(library); } self.query = inputs.query.to_owned(); self.stale = false; + self.progressed = false; + } + + /// The "Continue watching" shelf, as indices into the library. + fn resumable(library: &Library) -> Vec { + library + .continue_watching() + .into_iter() + .filter_map(|title| { + library + .titles + .iter() + .position(|candidate| std::ptr::eq(candidate, title)) + }) + .collect() } } @@ -1084,7 +1111,7 @@ impl App { state, } => { if self.library.set_watch(&share_id, &link_id, state) { - self.view.stale = true; + self.view.progressed = true; } } Event::Error(text) => {