mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-26 11:09:23 -06:00
perf(ui): #1136 dialogs count once per library change, not every frame
Create Smart Album ran a full query for its count and Rename Keyword scanned every photo on each frame the dialog was open; Merge Keywords recomputed suggestions. They use Caches::dialog_count (keyed by catalog revision and what is counted, and the minute for relative-date rules) and the cached suggestions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3f3ffa541a
commit
39c33a99d6
@@ -1477,6 +1477,9 @@ pub struct Caches {
|
||||
/// What the open smart-album rule dialog shows besides the rules ([`Caches::rules_view`]).
|
||||
rules_view: Option<(u64, std::sync::Arc<RulesView>)>,
|
||||
pub rules_view_scans: usize,
|
||||
/// A count an open dialog shows ([`Caches::dialog_count`]), by what it counts.
|
||||
dialog_count: Option<(u64, usize)>,
|
||||
pub dialog_count_scans: usize,
|
||||
/// The problems of every saved smart album that has some ([`Caches::smart_album_problems`]).
|
||||
smart_problems: Option<(u64, std::sync::Arc<std::collections::HashMap<lightcraft_catalog::AlbumId, Vec<lightcraft_catalog::rules::Problem>>>)>,
|
||||
pub smart_problem_scans: usize,
|
||||
@@ -1749,6 +1752,21 @@ impl Caches {
|
||||
v
|
||||
}
|
||||
|
||||
/// A count an open dialog shows (photos a filter matches, photos with a keyword…): `count` runs
|
||||
/// again only when the catalog or `what` (what is counted) changes, not every frame.
|
||||
pub(crate) fn dialog_count(&mut self, cat: &lightcraft_catalog::Catalog, what: impl std::hash::Hash, count: impl FnOnce() -> usize) -> usize {
|
||||
let k = key_of((cat.revision, what));
|
||||
if let Some((key, n)) = self.dialog_count
|
||||
&& key == k
|
||||
{
|
||||
return n;
|
||||
}
|
||||
let n = count();
|
||||
self.dialog_count_scans += 1;
|
||||
self.dialog_count = Some((k, n));
|
||||
n
|
||||
}
|
||||
|
||||
/// The problems of every saved smart album that has some (`Catalog::smart_album_problems`),
|
||||
/// for the sidebar's ⚠ marks: worked out again only when the catalog changes.
|
||||
pub fn smart_album_problems(
|
||||
|
||||
@@ -430,7 +430,13 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) {
|
||||
confirm = true;
|
||||
}
|
||||
let rules = app.session.view_rules();
|
||||
let n = app.session.catalog.query(&rules, &Default::default()).len();
|
||||
// counted when the library or the rules change (or, for "in the last…" rules, the minute), not every frame
|
||||
let now = (app.session.clock)();
|
||||
let minute = if rules.depends_on_now() { now.get(..16).unwrap_or(&now) } else { "" };
|
||||
let n = app.caches.dialog_count(&app.session.catalog, ("smartAlbum", format!("{rules:?}"), minute), || {
|
||||
lightcraft_catalog::rules::set_now(Some(now.clone()));
|
||||
app.session.catalog.query(&rules, &Default::default()).len()
|
||||
});
|
||||
ui.label(egui::RichText::new(crate::i18n::tr_format!("Matches: {}", crate::i18n::filter_label(&rules, &app.session.catalog))).color(t.text_label));
|
||||
ui.label(
|
||||
egui::RichText::new(crate::i18n::tr_format!("{n} photo{} now · updates automatically as photos change", if n == 1 { "" } else { "s" }, n = n))
|
||||
@@ -573,7 +579,9 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) {
|
||||
}
|
||||
}
|
||||
Dialog::RenameKeyword { from, to } => {
|
||||
let n = app.session.catalog.photos().filter(|p| p.meta.keywords.iter().any(|k| lightcraft_catalog::keywords::is_under(k, from))).count();
|
||||
let n = app.caches.dialog_count(&app.session.catalog, ("renameKeyword", from.as_str()), || {
|
||||
app.session.catalog.photos().filter(|p| p.meta.keywords.iter().any(|k| lightcraft_catalog::keywords::is_under(k, from))).count()
|
||||
});
|
||||
if dialog_field(ui, "field:keywordName", to, "New name") {
|
||||
confirm = true;
|
||||
}
|
||||
@@ -589,11 +597,11 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context) {
|
||||
confirm = true;
|
||||
}
|
||||
let options: Vec<String> = app
|
||||
.session
|
||||
.catalog
|
||||
.keyword_suggestions(from, into, 12)
|
||||
.into_iter()
|
||||
.caches
|
||||
.suggestions(&app.session.catalog, from, into, 12)
|
||||
.iter()
|
||||
.filter(|k| !from.iter().any(|f| lightcraft_catalog::keywords::is_under(k, f)))
|
||||
.cloned()
|
||||
.collect();
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for k in options {
|
||||
|
||||
@@ -227,3 +227,24 @@ fn containing_keywords_view_lists_parents_too() {
|
||||
}
|
||||
assert!(!has(&h, "keywordChip:beach"), "the chips give way");
|
||||
}
|
||||
|
||||
/// Rename Keyword's "used by n photos" and Create Smart Album's live count scan the library once
|
||||
/// per change to it, not every frame the dialog is open.
|
||||
#[test]
|
||||
fn open_dialogs_count_once_per_library_change() {
|
||||
let (mut h, ids) = two_selected();
|
||||
h.app.ui.dialog = Some(crate::state::Dialog::RenameKeyword { from: "beach".into(), to: "beach".into() });
|
||||
let before = h.app.caches.dialog_count_scans;
|
||||
for _ in 0..10 {
|
||||
h.step();
|
||||
}
|
||||
assert_eq!(h.app.caches.dialog_count_scans, before + 1, "unchanged frames reuse the count");
|
||||
ask(&mut h, "engine.execute", json!({"command": "photo.setMeta", "params": {"ids": [ids[1]], "keywords": ["sea"]}}));
|
||||
h.step();
|
||||
assert_eq!(h.app.caches.dialog_count_scans, before + 2, "a library change counts again");
|
||||
h.app.ui.dialog = Some(crate::state::Dialog::NewSmartAlbum { name: "Beach".into(), parent: None });
|
||||
for _ in 0..10 {
|
||||
h.step();
|
||||
}
|
||||
assert_eq!(h.app.caches.dialog_count_scans, before + 3, "another dialog counts its own photos, once");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user