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
@@ -317,6 +317,8 @@ impl OpenCADStudio {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.clear();
|
||||
self.tabs[i].scene.document = doc;
|
||||
self.tabs[i].scene.bump_layout_epoch();
|
||||
self.tabs[i].scene.bump_scale_epoch();
|
||||
self.tabs[i].scene.load_named_parameters_from_document();
|
||||
self.tabs[i]
|
||||
.scene
|
||||
|
||||
@@ -850,6 +850,8 @@ impl OpenCADStudio {
|
||||
.swap_structure(std::mem::replace(stored, acadrust::CadDocument::new()));
|
||||
*stored = inverse;
|
||||
self.tabs[i].scene.invalidate_dependency_index();
|
||||
self.tabs[i].scene.bump_layout_epoch();
|
||||
self.tabs[i].scene.bump_scale_epoch();
|
||||
}
|
||||
StructureSnapshot::Layers(entries) => {
|
||||
let names: Vec<String> =
|
||||
@@ -911,12 +913,31 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
StructureSnapshot::Objects(entries) => {
|
||||
let mut layout_touched = false;
|
||||
let mut scale_touched = false;
|
||||
for entry in entries {
|
||||
let value = if undo {
|
||||
entry.before.clone()
|
||||
} else {
|
||||
entry.after.clone()
|
||||
};
|
||||
// Either direction can add/remove a layout: undoing
|
||||
// an insert removes it (`before` is `None`), redoing
|
||||
// re-inserts it — so both sides must be inspected.
|
||||
layout_touched |= matches!(
|
||||
entry.before,
|
||||
Some(acadrust::objects::ObjectType::Layout(_))
|
||||
) || matches!(
|
||||
entry.after,
|
||||
Some(acadrust::objects::ObjectType::Layout(_))
|
||||
);
|
||||
scale_touched |= matches!(
|
||||
entry.before,
|
||||
Some(acadrust::objects::ObjectType::Scale(_))
|
||||
) || matches!(
|
||||
entry.after,
|
||||
Some(acadrust::objects::ObjectType::Scale(_))
|
||||
);
|
||||
if let Some(object) = value {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
@@ -927,6 +948,12 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.document.objects.remove(&entry.handle);
|
||||
}
|
||||
}
|
||||
if layout_touched {
|
||||
self.tabs[i].scene.bump_layout_epoch();
|
||||
}
|
||||
if scale_touched {
|
||||
self.tabs[i].scene.bump_scale_epoch();
|
||||
}
|
||||
}
|
||||
StructureSnapshot::Styles {
|
||||
before,
|
||||
|
||||
@@ -1697,6 +1697,8 @@ impl OpenCADStudio {
|
||||
self.install_native_edit_guard(i, &path, opened_fingerprint);
|
||||
self.tabs[i].scene.material_base_dir = path.parent().map(std::path::Path::to_path_buf);
|
||||
self.tabs[i].scene.document = doc;
|
||||
self.tabs[i].scene.bump_layout_epoch();
|
||||
self.tabs[i].scene.bump_scale_epoch();
|
||||
// Load parameters first so imported dimensional constraints
|
||||
// can resolve their named driving values.
|
||||
self.tabs[i].scene.load_named_parameters_from_document();
|
||||
|
||||
@@ -6657,7 +6657,7 @@ impl OpenCADStudio {
|
||||
.unwrap_or(1);
|
||||
let name = format!("Layout{n}");
|
||||
self.push_undo_snapshot(i, "LAYOUT NEW");
|
||||
match self.tabs[i].scene.document.add_layout(&name) {
|
||||
match self.tabs[i].scene.add_layout(&name) {
|
||||
Ok(_) => {
|
||||
self.tabs[i].dirty = true;
|
||||
self.layout_manager_selected = name.clone();
|
||||
|
||||
@@ -6028,7 +6028,7 @@ properties={:.1}ms picked={}",
|
||||
idx += 1;
|
||||
};
|
||||
self.push_undo_snapshot(i, "LAYOUT");
|
||||
match self.tabs[i].scene.document.add_layout(&new_name) {
|
||||
match self.tabs[i].scene.add_layout(&new_name) {
|
||||
Ok(_) => {
|
||||
let layout_flags = i16::from(
|
||||
self.tabs[i]
|
||||
|
||||
+36
-11
@@ -15,6 +15,7 @@ use iced::widget::{
|
||||
use iced::window;
|
||||
use iced::{keyboard, Background, Border, Color, Element, Fill, Length, Subscription, Task, Theme};
|
||||
use iced_aw::ContextMenu;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod controls;
|
||||
mod modal;
|
||||
@@ -2165,8 +2166,17 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
let last_coord = self.last_point.map(to_readout);
|
||||
let coords_mode = tab.scene.document.header.coords_mode;
|
||||
let picking = tab.active_cmd.is_some();
|
||||
let layout_names = tab.scene.layout_names();
|
||||
let block_tabs = tab
|
||||
// Cached Arcs are moved (refcount bump only), never deep-cloned:
|
||||
// `StatusBar::view` / `StatusMenuData` take ownership of the
|
||||
// `Arc`s and borrow them internally. `current_layout` is the
|
||||
// one true borrow — it anchors to `tab`, which outlives the
|
||||
// call — because a call-site slice over an `Arc` temporary
|
||||
// cannot outlive this block (E0515).
|
||||
let layout_names = tab.scene.cached_layout_names();
|
||||
let scale_list = tab.scene.cached_scale_picker_list();
|
||||
let selection_types = tab.scene.entity_type_names_in_layout();
|
||||
let current_scale_name = tab.scene.displayed_annotation_scale_name();
|
||||
let block_tabs: Vec<String> = tab
|
||||
.block_edits
|
||||
.iter()
|
||||
.map(|session| session.block_name.clone())
|
||||
@@ -2185,13 +2195,13 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
),
|
||||
);
|
||||
let status_menu_data = crate::ui::statusbar::StatusMenuData {
|
||||
layout_names: layout_names.clone(),
|
||||
layout_names: Arc::clone(&layout_names),
|
||||
polar_custom_input: &self.polar_custom_input,
|
||||
scale_is_model: is_model,
|
||||
current_scale_name: tab.scene.displayed_annotation_scale_name(),
|
||||
scale_list: tab.scene.scale_picker_list(),
|
||||
current_scale_name,
|
||||
scale_list,
|
||||
has_selection: !tab.scene.selected.is_empty(),
|
||||
selection_types: tab.scene.entity_type_names_in_layout().as_ref().clone(),
|
||||
selection_types,
|
||||
selection_filter: &tab.scene.selection_filter,
|
||||
tooltip_hidden: self.status_menu_tooltip_hidden,
|
||||
};
|
||||
@@ -2204,10 +2214,9 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
self.snapper.otrack_enabled,
|
||||
self.isometric_drafting,
|
||||
self.iso_plane,
|
||||
layout_names.clone(),
|
||||
layout_names,
|
||||
block_tabs,
|
||||
layout_names.into_iter().skip(1).collect(),
|
||||
tab.scene.current_layout.clone(),
|
||||
&tab.scene.current_layout,
|
||||
active_block,
|
||||
tab.is_start,
|
||||
self.layout_rename_state.as_ref(),
|
||||
@@ -2266,11 +2275,27 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
.width(Fill)
|
||||
.height(Fill);
|
||||
|
||||
// History labels are built only when their dropdown is actually open;
|
||||
// otherwise an empty slice short-circuits the overlay gate
|
||||
// (`dropdown_overlay` returns `None` for empty labels anyway).
|
||||
let open_dropdown = self.ribbon.open_dropdown.as_deref();
|
||||
let undo_labels: Vec<String> =
|
||||
if open_dropdown == Some(crate::ui::ribbon::UNDO_HISTORY_ID) {
|
||||
history_dropdown_labels(&self.tabs[self.active_tab].history.undo_stack)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let redo_labels: Vec<String> =
|
||||
if open_dropdown == Some(crate::ui::ribbon::REDO_HISTORY_ID) {
|
||||
history_dropdown_labels(&self.tabs[self.active_tab].history.redo_stack)
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let dropdown_layer: Element<'_, Message> = self
|
||||
.ribbon
|
||||
.dropdown_overlay(
|
||||
&history_dropdown_labels(&self.tabs[self.active_tab].history.undo_stack),
|
||||
&history_dropdown_labels(&self.tabs[self.active_tab].history.redo_stack),
|
||||
&undo_labels,
|
||||
&redo_labels,
|
||||
self.win_size,
|
||||
self.tabs[self.active_tab].is_start,
|
||||
&self.recent_colors,
|
||||
|
||||
Reference in new issue
Block a user