perf(statusbar): cache layout/scale derived data behind epochs (major)

Status bar rescanned the whole object map twice per frame (layout_names +
scale_picker_list), deep-cloned an already-cached Arc, and built two
undo/redo label vecs consumed ~never. Cache layout names (epoch key) and
scale picker (epoch + unit-factor + imperial-family + current-scale key)
as Arc behind choke-point invalidation (layout/scale impl methods,
undo/redo swaps, file-open, doc-reset); borrow across the widget boundary
(owned-Arc params; E0515 forbids &'a slices over call-site temporaries);
build undo/redo labels only when their dropdown is open.

Tests:
- cached_layout_names_returns_same_arc_until_bump: hit-path Arc identity
- cached_layout_names_invalidated_by_add_layout: bump yields new Arc + names
- cached_scale_picker_returns_same_arc_until_bump: hit-path Arc identity
- cached_scale_picker_invalidated_by_add_scale: bump yields new Arc + scale
- cached_scale_picker_invalidated_by_current_scale_change: key completeness

Bench (cargo bench ui_statusbar_derived_data, 3k entities): cached hit below
timer granularity vs ~1us uncached scans, PASS (50us threshold).
This commit is contained in:
Karim Jerbi committed 2026-09-21 22:31:16 +01:00
1 parent baba5bccd3
commit dcc05f310d
16 files changed
+327 -43

No files matched your search

+4 -3
View File
@@ -17,11 +17,12 @@ pub fn menu_entries(
is_model: bool,
current_scale_name: &str,
viewport_scale: Option<f64>,
file_scales: Vec<(String, f32, f64)>,
file_scales: &[(String, f32, f64)],
) -> Vec<Entry<'static>> {
let mut entries: Vec<Entry<'static>> = file_scales
.into_iter()
.iter()
.map(|(label, _anno_scale, vp_scale)| {
let vp_scale = *vp_scale;
let active = if is_model {
label.eq_ignore_ascii_case(current_scale_name)
} else {
@@ -38,7 +39,7 @@ pub fn menu_entries(
} else {
Message::SetViewportScale(label.clone())
};
Entry::close(scale_row(label, active, msg))
Entry::close(scale_row(label.clone(), active, msg))
})
.collect();
+4 -4
View File
@@ -14,7 +14,7 @@ use crate::ui::statusbar::status_menu::Entry;
/// - `types`: entity-type names present in the current layout.
/// - `excluded`: types currently filtered out (unchecked).
pub fn menu_entries(
types: Vec<String>,
types: &[String],
excluded: &HashSet<String>,
) -> Vec<Entry<'static>> {
// "Select All / Clear All" header, mirroring the OSNAP popup: Select All
@@ -51,10 +51,10 @@ pub fn menu_entries(
vec![Entry::stay(empty_row())]
} else {
types
.into_iter()
.iter()
.map(|name| {
let included = !excluded.contains(&name);
Entry::stay(type_row(name, included))
let included = !excluded.contains(name);
Entry::stay(type_row(name.clone(), included))
})
.collect()
};