diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 62ab2c13c..be766631c 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -3687,8 +3687,8 @@ impl OpenCADStudio { let i = self.active_tab; let types = self.tabs[i].scene.entity_type_names_in_layout(); let f = &mut self.tabs[i].scene.selection_filter; - for t in types { - f.insert(t.to_string()); + for t in types.iter() { + f.insert(t.clone()); } Task::none() } diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 0e6e3c4e3..ec566dc3a 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -1932,9 +1932,8 @@ impl OpenCADStudio { selection_types: tab .scene .entity_type_names_in_layout() - .into_iter() - .map(|name| name.to_string()) - .collect(), + .as_ref() + .clone(), selection_filter: &tab.scene.selection_filter, tooltip_hidden: self.status_menu_tooltip_hidden, }; diff --git a/src/scene/mod.rs b/src/scene/mod.rs index dc16aac6b..abbacd6ca 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -1789,6 +1789,11 @@ pub struct Scene { /// Reverse map: entity_handle → block_record_handle, built from entity_handles lists. /// Keyed by geometry_epoch. Eliminates the O(B) fallback scan in belongs_to_visible_block. entity_block_map_cache: RefCell)>>, + /// Distinct entity type names present in a layout, keyed by + /// (geometry_epoch, layout block). The status bar asks for this on every + /// frame to populate the selection-filter menu, but the answer only + /// changes when entities are added or removed. + layout_type_names_cache: RefCell>)>>, /// Reverse dependencies from layer/style/block definitions to the top-level /// entities whose resident wire runs actually change. Kept independent from /// `geometry_epoch`: a layer colour toggle can reuse the index, invalidate @@ -2024,6 +2029,7 @@ impl Scene { annotation_affects_wires: std::cell::Cell::new(None), model_extents_cache: RefCell::new(None), entity_block_map_cache: RefCell::new(None), + layout_type_names_cache: RefCell::new(None), dependency_index_cache: RefCell::new(None), associative_hatch_source_cache: RefCell::new(None), block_defn_cache: RefCell::new(HashMap::default()), diff --git a/src/scene/selection.rs b/src/scene/selection.rs index f2cc33654..8f83f4ab8 100644 --- a/src/scene/selection.rs +++ b/src/scene/selection.rs @@ -363,16 +363,50 @@ impl Scene { /// Returns the sorted set of entity-type names present in the active /// layout. Used to populate the Quick Select "Object type" dropdown /// with only the types that actually exist in the drawing. - pub fn entity_type_names_in_layout(&self) -> Vec { + /// Distinct entity type names in the current layout, for the + /// selection-filter menu. + /// + /// The status bar rebuilds on every frame, so this used to run once per + /// mouse move over the canvas: a full clone of the layout's handle list + /// (1.16 M handles, ~9 MB, on the reproducer), then a lookup, a `String` + /// allocation and a `BTreeSet` insert for each one — about 107 ms of a + /// 110 ms frame, to produce roughly twenty names. + /// + /// The answer only changes when entities are added or removed, so it is + /// cached against `geometry_epoch` and the layout block. The scan itself + /// now borrows the handle list instead of cloning it and collects + /// `&str`, allocating only for the names it actually returns. + pub fn entity_type_names_in_layout(&self) -> std::sync::Arc> { use crate::entities::traits::entity_type_name; - let mut names: std::collections::BTreeSet = - std::collections::BTreeSet::new(); - for h in self.current_layout_entity_handles() { - if let Some(e) = self.document.get_entity(h) { - names.insert(entity_type_name(e).to_string()); + let block = self.current_layout_block_handle(); + { + let cache = self.layout_type_names_cache.borrow(); + if let Some((epoch, cached_block, names)) = cache.as_ref() { + if *epoch == self.geometry_epoch && *cached_block == block { + return std::sync::Arc::clone(names); + } } } - names.into_iter().collect() + let mut names: std::collections::BTreeSet<&str> = + std::collections::BTreeSet::new(); + if let Some(record) = self + .document + .block_records + .iter() + .find(|record| record.handle == block) + { + for &handle in &record.entity_handles { + if let Some(entity) = self.document.get_entity(handle) { + names.insert(entity_type_name(entity)); + } + } + } + let names: std::sync::Arc> = std::sync::Arc::new( + names.into_iter().map(str::to_string).collect(), + ); + *self.layout_type_names_cache.borrow_mut() = + Some((self.geometry_epoch, block, std::sync::Arc::clone(&names))); + names } /// True when `handle`'s entity type is allowed by the selection filter.