forked from 1b-agent/opencadstudio-upstream
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:
1 parent
baba5bccd3
commit
dcc05f310d
16 files changed
+327
-43
No files matched your search
@@ -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();
|
||||
|
||||
|
||||
@@ -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()
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ mod draw_panel;
|
||||
mod modify_panel;
|
||||
mod color_dropdown;
|
||||
use widgets::{StyleContext, *};
|
||||
pub(crate) use widgets::{REDO_HISTORY_ID, UNDO_HISTORY_ID};
|
||||
mod collapse;
|
||||
use collapse::{CollapsePanels, Panel};
|
||||
pub use collapse::CollapseMode;
|
||||
|
||||
@@ -301,8 +301,8 @@ pub(super) const TOP_HIST_GAP: f32 = 4.0;
|
||||
|
||||
// ── Dropdown / combo ID constants ─────────────────────────────────────────
|
||||
|
||||
pub(super) const UNDO_HISTORY_ID: &str = "UNDO_HISTORY";
|
||||
pub(super) const REDO_HISTORY_ID: &str = "REDO_HISTORY";
|
||||
pub(crate) const UNDO_HISTORY_ID: &str = "UNDO_HISTORY";
|
||||
pub(crate) const REDO_HISTORY_ID: &str = "REDO_HISTORY";
|
||||
pub(super) const LAYER_COMBO_ID: &str = "LAYER_COMBO";
|
||||
/// Dropdown id for the tab-bar panel-density selector.
|
||||
pub(super) const COLLAPSE_MODE_ID: &str = "COLLAPSE_MODE";
|
||||
|
||||
+28
-20
@@ -38,13 +38,16 @@ const ST_ANNO_AUTO_ADD: &[u8] = include_bytes!("../../../assets/icons/add_scale.
|
||||
const ST_VP_SCALE_SYNC: &[u8] = include_bytes!("../../../assets/icons/sync.svg");
|
||||
|
||||
pub struct StatusMenuData<'a> {
|
||||
pub layout_names: Vec<String>,
|
||||
/// Cached layout names (shared `Arc`, moved in — never deep-cloned).
|
||||
pub layout_names: Arc<Vec<String>>,
|
||||
pub polar_custom_input: &'a str,
|
||||
pub scale_is_model: bool,
|
||||
pub current_scale_name: String,
|
||||
pub scale_list: Vec<(String, f32, f64)>,
|
||||
/// Cached scale picker list (shared `Arc`, moved in).
|
||||
pub scale_list: Arc<Vec<(String, f32, f64)>>,
|
||||
pub has_selection: bool,
|
||||
pub selection_types: Vec<String>,
|
||||
/// Entity-type names in the current layout (shared `Arc`, moved in).
|
||||
pub selection_types: Arc<Vec<String>>,
|
||||
pub selection_filter: &'a rustc_hash::FxHashSet<String>,
|
||||
pub tooltip_hidden: bool,
|
||||
}
|
||||
@@ -72,10 +75,9 @@ impl StatusBar {
|
||||
otrack: bool,
|
||||
isometric_drafting: bool,
|
||||
iso_plane: crate::app::settings::IsoPlane,
|
||||
layouts: Vec<String>,
|
||||
layouts: Arc<Vec<String>>,
|
||||
block_tabs: Vec<String>,
|
||||
reorderable_layouts: Vec<String>,
|
||||
current_layout: String,
|
||||
current_layout: &'a str,
|
||||
active_block: Option<String>,
|
||||
// Start/welcome view has no drawing to own layouts.
|
||||
is_start: bool,
|
||||
@@ -172,7 +174,7 @@ impl StatusBar {
|
||||
t!("Model and layout list"),
|
||||
tooltip_hidden,
|
||||
),
|
||||
statusbar_menu::layout_entries(&layout_names, ¤t_layout),
|
||||
statusbar_menu::layout_entries(&layout_names, current_layout),
|
||||
200.0,
|
||||
)
|
||||
};
|
||||
@@ -231,7 +233,7 @@ impl StatusBar {
|
||||
scale_is_model,
|
||||
¤t_scale_name,
|
||||
viewport_scale,
|
||||
scale_list,
|
||||
&scale_list,
|
||||
),
|
||||
if scale_is_model { 150.0 } else { 120.0 },
|
||||
)
|
||||
@@ -378,7 +380,7 @@ impl StatusBar {
|
||||
if vis(StatusPill::Space) {
|
||||
pills.push(
|
||||
tip(
|
||||
space_mode_btn(¤t_layout, in_mspace),
|
||||
space_mode_btn(current_layout, in_mspace),
|
||||
t!("PAPER: double-click viewport to enter MSPACE\nMODEL: click to switch to Model Space"),
|
||||
)
|
||||
.into(),
|
||||
@@ -501,7 +503,7 @@ impl StatusBar {
|
||||
tooltip_hidden,
|
||||
),
|
||||
crate::ui::popup::selection_filter_popup::menu_entries(
|
||||
selection_types,
|
||||
&selection_types,
|
||||
selection_filter,
|
||||
),
|
||||
180.0,
|
||||
@@ -571,16 +573,22 @@ impl StatusBar {
|
||||
let mut left: Vec<Element<'_, Message>> = Vec::new();
|
||||
left.push(menu_btn);
|
||||
if show_layout_tabs {
|
||||
let reorderable_layouts: Arc<[String]> = reorderable_layouts.into();
|
||||
for name in layouts {
|
||||
let is_active = active_block.is_none() && name == current_layout;
|
||||
// Paper layouts only: `layouts[0]` is always "Model". Derived once
|
||||
// from the shared `Arc` and cloned (cheap refcount bump) per tab.
|
||||
let reorderable_layouts: Arc<[String]> =
|
||||
Arc::from(layouts.get(1..).unwrap_or(&[]));
|
||||
// Shared once per frame: block tabs carry no reorder set, so reuse
|
||||
// one empty Arc (refcount bump) instead of allocating per tab.
|
||||
let empty_reorderable: Arc<[String]> = Arc::from([] as [String; 0]);
|
||||
for name in layouts.iter() {
|
||||
let is_active = active_block.is_none() && name.as_str() == current_layout;
|
||||
let renaming = rename_state
|
||||
.filter(|(orig, _)| *orig == name)
|
||||
.filter(|(orig, _)| orig.as_str() == name.as_str())
|
||||
.map(|(_, edit)| edit.as_str());
|
||||
let switch_msg = Message::LayoutSwitch(name.clone());
|
||||
let switch_msg = Message::LayoutSwitch(name.to_owned());
|
||||
left.push(
|
||||
space_tab(
|
||||
name,
|
||||
name.to_owned(),
|
||||
is_active,
|
||||
renaming,
|
||||
!is_start,
|
||||
@@ -591,16 +599,16 @@ impl StatusBar {
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
for name in block_tabs {
|
||||
for name in block_tabs.iter() {
|
||||
let is_active = active_block.as_deref() == Some(name.as_str());
|
||||
let switch_msg = Message::BlockEditSwitch(name.clone());
|
||||
let switch_msg = Message::BlockEditSwitch(name.to_owned());
|
||||
left.push(
|
||||
space_tab(
|
||||
name,
|
||||
name.to_owned(),
|
||||
is_active,
|
||||
None,
|
||||
!is_start,
|
||||
Arc::from(Vec::<String>::new()),
|
||||
empty_reorderable.clone(),
|
||||
switch_msg,
|
||||
"SB_BLOCK_TAB",
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user