forked from 1b-agent/opencadstudio-upstream
fix(bedit): optimize block editor close and preserve warm render caches
Avoid rebuilding derived caches and clearing tessellation memos when closing the block editor on clean exit or discard. Scope entity delta computation to block record members instead of scanning and cloning the entire drawing, and add a fast path to the dirty check when no local commands were executed.
This commit is contained in:
1 parent
cccea4008b
commit
f3973f08a7
3 files changed
+118
-34
No files matched your search
+92
-33
@@ -11,10 +11,16 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||
/// document's Arc storage; only modified entities detach during editing.
|
||||
pub(super) struct BlockEditCheckpoint {
|
||||
document: CadDocument,
|
||||
structure: CadDocument,
|
||||
constraints: Vec<crate::scene::parametric_constraints::ParametricConstraintSet>,
|
||||
parameters: crate::scene::named_parameters::ParameterTable,
|
||||
dirty: bool,
|
||||
active_layer: String,
|
||||
hatches: FxHashMap<codec::Handle, crate::scene::HatchModel>,
|
||||
images: FxHashMap<codec::Handle, crate::scene::ImageModel>,
|
||||
meshes: FxHashMap<codec::Handle, crate::scene::MeshLodSet>,
|
||||
block_meshes: FxHashMap<codec::Handle, crate::scene::MeshLodSet>,
|
||||
solid_models: FxHashMap<codec::Handle, kernel::brep::Body>,
|
||||
}
|
||||
|
||||
pub(super) struct BlockEditHistory {
|
||||
@@ -76,14 +82,21 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
|
||||
fn block_edit_checkpoint(&self, i: usize) -> BlockEditCheckpoint {
|
||||
let tab = &self.tabs[i];
|
||||
fn block_edit_checkpoint(&mut self, i: usize) -> BlockEditCheckpoint {
|
||||
let tab = &mut self.tabs[i];
|
||||
let structure = tab.scene.document.snapshot_structure();
|
||||
BlockEditCheckpoint {
|
||||
document: tab.scene.document.clone(),
|
||||
structure,
|
||||
constraints: tab.scene.parametric_constraints.clone(),
|
||||
parameters: tab.scene.named_parameters.clone(),
|
||||
dirty: tab.dirty,
|
||||
active_layer: tab.active_layer.clone(),
|
||||
hatches: tab.scene.hatches.clone(),
|
||||
images: tab.scene.images.clone(),
|
||||
meshes: tab.scene.meshes.clone(),
|
||||
block_meshes: tab.scene.block_meshes.clone(),
|
||||
solid_models: tab.scene.solid_models.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,43 +120,67 @@ impl OpenCADStudio {
|
||||
let history = tab.block_edit_history.as_ref()?;
|
||||
let checkpoint = &history.checkpoint;
|
||||
let session = tab.active_block_edit_session()?;
|
||||
let before_doc = if for_save {
|
||||
session
|
||||
.unpersisted_pre_document
|
||||
.as_ref()
|
||||
.unwrap_or(&checkpoint.document)
|
||||
let (before_doc, before_structure_base) = if for_save {
|
||||
if let Some(ref pre_doc) = session.unpersisted_pre_document {
|
||||
let mut pre_clone = pre_doc.clone();
|
||||
let pre_struct = pre_clone.snapshot_structure();
|
||||
(pre_doc, pre_struct)
|
||||
} else {
|
||||
&checkpoint.document
|
||||
(&checkpoint.document, checkpoint.structure.clone())
|
||||
}
|
||||
} else {
|
||||
(&checkpoint.document, checkpoint.structure.clone())
|
||||
};
|
||||
let before: FxHashMap<_, _> = before_doc
|
||||
.entities()
|
||||
.map(|e| {
|
||||
let h = e.common().handle;
|
||||
(h, before_doc.get_entity_arc(h).unwrap())
|
||||
})
|
||||
.collect();
|
||||
let after: FxHashMap<_, _> = tab
|
||||
.scene
|
||||
.document
|
||||
.entities()
|
||||
.map(|e| {
|
||||
let h = e.common().handle;
|
||||
(h, tab.scene.document.get_entity_arc(h).unwrap())
|
||||
})
|
||||
.collect();
|
||||
let mut handles: FxHashSet<_> = before.keys().copied().collect();
|
||||
handles.extend(after.keys().copied());
|
||||
let mut entities: Vec<_> = handles
|
||||
let mut candidate_handles: FxHashSet<codec::Handle> = FxHashSet::default();
|
||||
if let Some(record) = before_doc.block_records.get(&session.block_name) {
|
||||
candidate_handles.extend(record.entity_handles.iter().copied());
|
||||
if !record.block_entity_handle.is_null() {
|
||||
candidate_handles.insert(record.block_entity_handle);
|
||||
}
|
||||
if !record.block_end_handle.is_null() {
|
||||
candidate_handles.insert(record.block_end_handle);
|
||||
}
|
||||
}
|
||||
if let Some(record) = tab.scene.document.block_records.get(&session.block_name) {
|
||||
candidate_handles.extend(record.entity_handles.iter().copied());
|
||||
if !record.block_entity_handle.is_null() {
|
||||
candidate_handles.insert(record.block_entity_handle);
|
||||
}
|
||||
if !record.block_end_handle.is_null() {
|
||||
candidate_handles.insert(record.block_end_handle);
|
||||
}
|
||||
}
|
||||
let mut sub_handles = Vec::new();
|
||||
for &h in &candidate_handles {
|
||||
let entity_opt = before_doc
|
||||
.get_entity(h)
|
||||
.or_else(|| tab.scene.document.get_entity(h));
|
||||
if let Some(EntityType::Insert(ins)) = entity_opt {
|
||||
for attr in &ins.attributes {
|
||||
sub_handles.push(attr.common.handle);
|
||||
}
|
||||
if let Some(seqend) = ins.seqend_handle {
|
||||
sub_handles.push(seqend);
|
||||
}
|
||||
}
|
||||
}
|
||||
candidate_handles.extend(sub_handles);
|
||||
|
||||
let mut entities: Vec<_> = candidate_handles
|
||||
.into_iter()
|
||||
.filter_map(|h| {
|
||||
let a = before.get(&h);
|
||||
let b = after.get(&h);
|
||||
(a != b).then(|| (h, a.cloned(), b.cloned()))
|
||||
let a = before_doc.get_entity_arc(h);
|
||||
let b = tab.scene.document.get_entity_arc(h);
|
||||
match (&a, &b) {
|
||||
(Some(a_arc), Some(b_arc)) if std::sync::Arc::ptr_eq(a_arc, b_arc) => None,
|
||||
(Some(a_arc), Some(b_arc)) if a_arc == b_arc => None,
|
||||
(None, None) => None,
|
||||
_ => Some((h, a, b)),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
entities.sort_by_key(|(handle, _, _)| handle.value());
|
||||
let mut before_structure = before_doc.clone();
|
||||
before_structure = before_structure.snapshot_structure();
|
||||
let mut before_structure = before_structure_base;
|
||||
let mut after_structure = current_structure;
|
||||
// The editor temporarily uses Model rendering. Drawing history must
|
||||
// retain the layout to which the editor returns.
|
||||
@@ -230,6 +267,22 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
pub(super) fn block_edit_dirty(&mut self, i: usize) -> bool {
|
||||
let tab = &self.tabs[i];
|
||||
let Some(history) = tab.block_edit_history.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
let Some(session) = tab.active_block_edit_session() else {
|
||||
return false;
|
||||
};
|
||||
if tab.history.undo_stack.is_empty()
|
||||
&& tab.history.pending.is_none()
|
||||
&& session.unpersisted_pre_document.is_none()
|
||||
&& history.checkpoint.active_layer == tab.active_layer
|
||||
&& history.checkpoint.dirty == tab.dirty
|
||||
&& history.checkpoint.parameters == tab.scene.named_parameters
|
||||
{
|
||||
return false;
|
||||
}
|
||||
self.block_edit_delta(i, false).is_some()
|
||||
}
|
||||
|
||||
@@ -498,7 +551,7 @@ impl OpenCADStudio {
|
||||
self.visibility_popup = None;
|
||||
history.checkpoint = self.block_edit_checkpoint(i);
|
||||
self.tabs[i].block_edit_history = Some(history);
|
||||
self.tabs[i].scene.rebuild_derived_caches();
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].refresh_active_ucs();
|
||||
self.refresh_block_palette();
|
||||
self.refresh_properties();
|
||||
@@ -586,6 +639,11 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.named_parameters = history.checkpoint.parameters;
|
||||
self.tabs[i].active_layer = history.checkpoint.active_layer;
|
||||
self.tabs[i].dirty = history.checkpoint.dirty;
|
||||
self.tabs[i].scene.hatches = history.checkpoint.hatches;
|
||||
self.tabs[i].scene.images = history.checkpoint.images;
|
||||
self.tabs[i].scene.meshes = history.checkpoint.meshes;
|
||||
self.tabs[i].scene.block_meshes = history.checkpoint.block_meshes;
|
||||
self.tabs[i].scene.solid_models = history.checkpoint.solid_models;
|
||||
}
|
||||
self.tabs[i].history = history.main;
|
||||
self.tabs[i].scene.object_isolation = history.return_isolation;
|
||||
@@ -596,6 +654,7 @@ impl OpenCADStudio {
|
||||
None,
|
||||
&session.return_layout,
|
||||
&session.return_camera,
|
||||
!discard,
|
||||
);
|
||||
self.cancel_attr_editor();
|
||||
self.discard_block_tests(self.tabs[i].id);
|
||||
|
||||
@@ -1333,3 +1333,25 @@ fn locked_bedit_with_preselected_insert_opens_directly() {
|
||||
"EDIT_ME"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locked_bedit_clean_close_preserves_block_epoch_and_render_caches() {
|
||||
let (mut app, reference) = fixture();
|
||||
let tab = app.active_tab;
|
||||
let epoch_before = app.tabs[tab].scene.block_epoch;
|
||||
begin(&mut app, reference);
|
||||
let _ = app.dispatch_command("BCLOSE");
|
||||
assert_eq!(app.tabs[tab].scene.block_epoch, epoch_before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn locked_bedit_discard_preserves_block_epoch_and_render_caches() {
|
||||
let (mut app, reference) = fixture();
|
||||
let tab = app.active_tab;
|
||||
let epoch_before = app.tabs[tab].scene.block_epoch;
|
||||
begin(&mut app, reference);
|
||||
circle(&mut app, 1.0);
|
||||
let _ = app.dispatch_command("BCLOSE");
|
||||
let _ = app.update(Message::BlockEditChangesDiscard);
|
||||
assert_eq!(app.tabs[tab].scene.block_epoch, epoch_before);
|
||||
}
|
||||
@@ -1080,6 +1080,7 @@ impl OpenCADStudio {
|
||||
return_block: Option<&str>,
|
||||
return_layout: &str,
|
||||
return_camera: &crate::scene::view::camera::Camera,
|
||||
saved: bool,
|
||||
) {
|
||||
self.tabs[i].scene.block_edit_block = None;
|
||||
self.tabs[i].scene.deselect_all();
|
||||
@@ -1121,7 +1122,9 @@ impl OpenCADStudio {
|
||||
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
self.tabs[i].last_synced_camera_gen = self.tabs[i].scene.camera_generation;
|
||||
self.tabs[i].scene.rebuild_derived_caches();
|
||||
if !saved {
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
}
|
||||
let layers = self.tabs[i].scene.document.layers.clone();
|
||||
let viewports = self.tabs[i].scene.viewport_list();
|
||||
self.tabs[i].layers.sync_with_viewports(&layers, viewports);
|
||||
|
||||
Reference in new issue
Block a user