From 801882314fd85e95c8dc1d67b30fb493f5e56e73 Mon Sep 17 00:00:00 2001 From: Karim Jerbi Date: Mon, 28 Sep 2026 06:58:47 +0100 Subject: [PATCH] perf(explode): batch erasure, section map, pre-sized vectors EXPLODE apply 504ms -> 61ms (-88%), geometry 70.2ms -> 58.1ms (-17%) on the 2,500-compound fixture. --- src/modules/draw/modify/explode.rs | 15 +++++++------- src/scene/selection.rs | 32 +++++++++++++++--------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/modules/draw/modify/explode.rs b/src/modules/draw/modify/explode.rs index 4274418ea..34261f377 100644 --- a/src/modules/draw/modify/explode.rs +++ b/src/modules/draw/modify/explode.rs @@ -155,8 +155,9 @@ pub fn apply_explode_replacements( replacements: Vec<(Handle, Vec)>, ) -> usize { let exploded = replacements.len(); - for (handle, pieces) in replacements { - scene.erase_entities(&[handle]); + let handles: Vec = replacements.iter().map(|(h, _)| *h).collect(); + scene.erase_entities(&handles); + for (_, pieces) in replacements { for piece in pieces { scene.add_entity(piece); } @@ -171,7 +172,7 @@ fn explode_polyline(p: &Polyline) -> Vec { } let closed = p.flags.is_closed(); let n_segs = if closed { n } else { n - 1 }; - let mut result = Vec::new(); + let mut result = Vec::with_capacity(n_segs); for i in 0..n_segs { let v0 = &p.vertices[i]; let v1 = &p.vertices[(i + 1) % n]; @@ -194,7 +195,7 @@ fn explode_polyline3d(p: &codec::entities::Polyline3D) -> Vec { } let closed = p.is_closed(); let n_segs = if closed { n } else { n - 1 }; - let mut result = Vec::new(); + let mut result = Vec::with_capacity(n_segs); for i in 0..n_segs { let v0 = &p.vertices[i]; let v1 = &p.vertices[(i + 1) % n]; @@ -224,7 +225,7 @@ fn explode_polyline2d(p: &Polyline2D) -> Vec { let normal = Vector3::new(normal.x, normal.y, normal.z); let plane = crate::entities::curve::ocs_plane(normal.clone(), elevation); - let mut result = Vec::new(); + let mut result = Vec::with_capacity(n_segs); for i in 0..n_segs { let v0 = &p.vertices[i]; let v1 = &p.vertices[(i + 1) % n]; @@ -303,7 +304,7 @@ fn explode_lwpolyline(p: &LwPolyline) -> Vec { let elevation = p.elevation; let n_segs = if p.is_closed { n } else { n - 1 }; - let mut result = Vec::new(); + let mut result = Vec::with_capacity(n_segs); for i in 0..n_segs { let v0 = &p.vertices[i]; let v1 = &p.vertices[(i + 1) % n]; @@ -389,7 +390,7 @@ fn explode_mline(ml: &MLine) -> Vec { let closed = ml.flags.contains(codec::entities::MLineFlags::CLOSED); let scale = ml.scale_factor; let n_segs = if closed { n } else { n - 1 }; - let mut result = Vec::new(); + let mut result = Vec::with_capacity(n_segs * 3); // Helper: build a Line from two Vector3 positions. let make_line = |common: &codec::entities::EntityCommon, diff --git a/src/scene/selection.rs b/src/scene/selection.rs index 097c28bc3..8a536fa01 100644 --- a/src/scene/selection.rs +++ b/src/scene/selection.rs @@ -979,6 +979,18 @@ impl Scene { fn remove_entities(&mut self, handles: &[Handle], respect_layer_locks: bool) { let erase_handles = self.handles_expanded_for_leader_annotations(handles); + // One pass over document.objects instead of a scan per erased handle. + let mut managers_by_section: HashMap> = HashMap::default(); + for (handle, object) in self.document.objects.iter() { + if let ObjectType::ClassObject(object) = object { + if let codec::objects::ClassObjectData::SectionManager(manager) = &object.data { + for §ion in &manager.sections { + managers_by_section.entry(section).or_default().push(*handle); + } + } + } + } + let mut handle_set: HashSet = HashSet::default(); let mut erased: Vec<(Handle, ChangeKind)> = Vec::new(); let mut selection_changed = false; @@ -996,22 +1008,10 @@ impl Scene { }) if !data.settings_handle.is_null() => Some(data.settings_handle), _ => None, }); - let section_managers: Vec = self - .document - .objects - .iter() - .filter_map(|(handle, object)| match object { - ObjectType::ClassObject(object) => match &object.data { - codec::objects::ClassObjectData::SectionManager(manager) - if manager.sections.contains(&h) => - { - Some(*handle) - } - _ => None, - }, - _ => None, - }) - .collect(); + let section_managers: Vec = managers_by_section + .get(&h) + .cloned() + .unwrap_or_default(); // Delta-undo: capture the removed entity so an undo can re-insert it. if self.is_recording_undo() { let before = self.document.get_entity_arc(h);