Do not recount face coverage on every confirm, and count it without listing
Every click on the Identity screen's face grid — confirm, reject, split, rename, merge — redrew the whole screen, and the redraw recomputed the coverage line. That line lists every repair's outstanding images to count them: six scans of the images table with a correlated EXISTS over the 8 KB face rows, an ORDER BY the job's visiting order, a Target with its path per row, and a thumbnail-index query per image with faces. On the reference library (24k images, 19k faces) that was ~200 ms of the ~540 ms each click cost, spent computing a figure a confirm cannot change. `refresh` now takes what changed: `Changed::Identities` re-reads the rail and the grid and leaves the coverage line alone; `Changed::Library` — an open, a sweep ending or stopped, the face data deleted — re-reads it too. For the times it does run, `repairs::counts` counts instead of building and dropping the lists, and the thumbnail store is read once (`ThumbStore::held`) rather than probed once per image in the audit, the outstanding list and the proxy repair. `identity_bench` is the measurement: the reads a click performs and the batch writes, timed against a copy of a real catalog.
This commit is contained in:
+42
-14
@@ -138,6 +138,26 @@ impl IdentityController {
|
||||
}
|
||||
}
|
||||
|
||||
/// What a reload has to re-read, named by what just changed.
|
||||
///
|
||||
/// The coverage line is the expensive half of a redraw: it lists every
|
||||
/// repair's outstanding images to count them, which is several scans of the
|
||||
/// whole `images` table (`crate::repairs::counts`). A confirm, a reject, a
|
||||
/// rename or a merge moves faces between people and cannot change how many
|
||||
/// images have been indexed, so a redraw for one of those must not pay for
|
||||
/// it — that was 200 ms of the half-second every click on the face grid used
|
||||
/// to cost on the reference library.
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Changed {
|
||||
/// Who the faces belong to. The rail and the grid are re-read; the
|
||||
/// coverage line is left as it was.
|
||||
Identities,
|
||||
/// Which faces exist: a sweep finished or was stopped, the face data was
|
||||
/// deleted, the screen was opened onto a catalog another device may have
|
||||
/// indexed. Everything is re-read, the coverage line included.
|
||||
Library,
|
||||
}
|
||||
|
||||
/// Push the people rail and the face grid into the window.
|
||||
///
|
||||
/// **Draws with the portraits it already has and cuts the rest afterwards.**
|
||||
@@ -154,6 +174,7 @@ pub fn refresh(
|
||||
store: Option<Rc<ThumbStore>>,
|
||||
model_id: &str,
|
||||
eyes: bool,
|
||||
changed: Changed,
|
||||
) {
|
||||
let borrow = catalog.borrow();
|
||||
let Some(cat) = borrow.as_ref() else {
|
||||
@@ -274,7 +295,9 @@ pub fn refresh(
|
||||
|
||||
window.set_identity_picked(ctl.picked.borrow().len() as i32);
|
||||
drop(borrow);
|
||||
refresh_coverage(window, catalog, store.as_deref(), model_id, eyes);
|
||||
if changed == Changed::Library {
|
||||
refresh_coverage(window, catalog, store.as_deref(), model_id, eyes);
|
||||
}
|
||||
|
||||
// Last, so a portrait cannot delay anything above it.
|
||||
if let Some(store) = store {
|
||||
@@ -628,8 +651,10 @@ pub fn wire<S, M, P>(
|
||||
// The availability closure is an argument rather than named in the
|
||||
// body: macro hygiene would bind the name to this scope's `Rc`, which the
|
||||
// first `move` closure would then take with it.
|
||||
// The last argument is a `Changed` variant, named bare so the call
|
||||
// stays on one line.
|
||||
macro_rules! reload {
|
||||
($w:expr, $ctl:expr, $catalog:expr, $store:expr, $settings:expr, $eyes:expr) => {
|
||||
($w:expr, $ctl:expr, $catalog:expr, $store:expr, $settings:expr, $eyes:expr, $changed:ident) => {
|
||||
refresh(
|
||||
&$w,
|
||||
&$ctl,
|
||||
@@ -637,6 +662,7 @@ pub fn wire<S, M, P>(
|
||||
$store(),
|
||||
&model_id(&$settings),
|
||||
$eyes(),
|
||||
Changed::$changed,
|
||||
)
|
||||
};
|
||||
}
|
||||
@@ -662,7 +688,7 @@ pub fn wire<S, M, P>(
|
||||
// A fact about the filesystem, so it is re-checked on every open
|
||||
// rather than cached: the user may have just put the models there.
|
||||
w.set_identity_model_missing(models_present().is_none());
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available, Library);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -702,7 +728,7 @@ pub fn wire<S, M, P>(
|
||||
// The offer was about the person being navigated away from. Left
|
||||
// up, its "Merge" would fold whoever is selected *now*.
|
||||
clear_merge_offer(&w, &ctl);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available, Identities);
|
||||
reset_name_field(&w);
|
||||
});
|
||||
}
|
||||
@@ -748,7 +774,7 @@ pub fn wire<S, M, P>(
|
||||
Err(e) => log::warn!("identity: looking for a namesake: {e}"),
|
||||
}
|
||||
}
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available, Identities);
|
||||
// `rename` trims; the field should show what was actually stored
|
||||
// rather than the spacing the user happened to type.
|
||||
reset_name_field(&w);
|
||||
@@ -786,7 +812,7 @@ pub fn wire<S, M, P>(
|
||||
}
|
||||
}
|
||||
clear_merge_offer(&w, &ctl);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available, Identities);
|
||||
reset_name_field(&w);
|
||||
});
|
||||
}
|
||||
@@ -832,7 +858,7 @@ pub fn wire<S, M, P>(
|
||||
log::warn!("identity: confirm: {e}");
|
||||
}
|
||||
}
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available, Identities);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -853,7 +879,7 @@ pub fn wire<S, M, P>(
|
||||
log::warn!("identity: reject: {e}");
|
||||
}
|
||||
}
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available, Identities);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -897,7 +923,7 @@ pub fn wire<S, M, P>(
|
||||
Err(e) => log::warn!("identity: confirm all: {e}"),
|
||||
}
|
||||
}
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available, Identities);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -931,7 +957,7 @@ pub fn wire<S, M, P>(
|
||||
}
|
||||
}
|
||||
ctl.clear_picks();
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available, Identities);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1091,6 +1117,7 @@ pub fn wire<S, M, P>(
|
||||
store_tick(),
|
||||
&model_id(&settings_tick),
|
||||
eyes_tick(),
|
||||
Changed::Identities,
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -1268,6 +1295,7 @@ pub fn wire<S, M, P>(
|
||||
store_tick(),
|
||||
&model_id(&settings_tick),
|
||||
eyes_tick(),
|
||||
Changed::Library,
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -1306,7 +1334,7 @@ pub fn wire<S, M, P>(
|
||||
a.finish("stopped");
|
||||
}
|
||||
w.set_identity_indexing(false);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available, Library);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1351,7 +1379,7 @@ pub fn wire<S, M, P>(
|
||||
ctl.selected.set(None);
|
||||
ctl.clear_picks();
|
||||
}
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available, Identities);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1365,7 +1393,7 @@ pub fn wire<S, M, P>(
|
||||
window.on_identity_toggle_show_ignored(move || {
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
ctl.show_ignored.set(!ctl.show_ignored.get());
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available, Identities);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1387,7 +1415,7 @@ pub fn wire<S, M, P>(
|
||||
ctl.selected.set(None);
|
||||
ctl.clear_picks();
|
||||
ctl.covers.borrow_mut().clear();
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available);
|
||||
reload!(w, ctl, catalog, store, settings, eyes_available, Library);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user