perf: make edit history incremental

Replace full-document snapshots with Arc first-touch deltas and batch cache invalidation. Reuse interaction and GPU category caches across unrelated edits.
This commit is contained in:
Hakan Seven committed 2026-07-24 23:25:33 +03:00
1 parent 90714d34b2
commit ea93e169ea
19 files changed
+979 -339

No files matched your search

+7 -9
View File
@@ -168,10 +168,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
}
// A typed grip-menu value reshapes dimensions too — drop a
// stale baked *D block (no-op for non-dims). (#398)
crate::modules::draw::modify::explode::invalidate_dim_block(
&mut self.tabs[i].scene.document,
pending.handle,
);
self.tabs[i]
.scene
.invalidate_dim_block_recorded(pending.handle);
self.tabs[i].scene.bump_geometry();
self.tabs[i].dirty = true;
self.refresh_selected_grips();
@@ -987,10 +986,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
}
// Menu actions reshape dimensions too — drop a stale baked *D
// block so the edit is visible (no-op for non-dims). (#398)
crate::modules::draw::modify::explode::invalidate_dim_block(
&mut self.tabs[i].scene.document,
popup.handle,
);
self.tabs[i]
.scene
.invalidate_dim_block_recorded(popup.handle);
self.tabs[i].scene.bump_geometry();
self.tabs[i].dirty = true;
self.refresh_selected_grips();
@@ -1918,7 +1916,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
self.tabs[i].dirty = true;
} else {
// Nothing changed — drop the snapshot pushed a moment ago.
let _ = self.tabs[i].history.undo_stack.pop();
self.discard_last_undo_entry(i);
}
self.refresh_properties();
Task::none()
+4 -1
View File
@@ -134,6 +134,9 @@ impl OpenCADStudio {
}
}
let task = self.update_inner(msg);
// Close the document-level first-touch transaction started by
// push_undo_snapshot at this message boundary.
self.finish_all_pending_history();
// After every message, mirror the active command step's prompt so
// its history line stays pinned (non-fading) until the step changes.
let prompt = self.tabs[self.active_tab]
@@ -2228,7 +2231,7 @@ impl OpenCADStudio {
// Stash the erased entities so OOPS can restore them.
self.oops_cache = handles
.iter()
.filter_map(|h| self.tabs[i].scene.document.get_entity(*h).cloned())
.filter_map(|h| self.tabs[i].scene.document.get_entity_arc(*h))
.collect();
self.tabs[i].scene.erase_entities(&handles);
self.tabs[i].dirty = true;
+7 -16
View File
@@ -1932,29 +1932,20 @@ impl OpenCADStudio {
// once, dropping the overlay preview.
if let Some(h) = self.grip_preview_handle.take() {
// Undo entry for the drag (#332): the pre-drag backup
// swaps in, the PRE state is snapshotted, then the
// dragged result swaps back. Without this a grip edit
// (e.g. reshaping a polyline vertex) was un-undoable —
// the drag mutated the document without any history.
// and live dragged image form a one-entity Arc delta. The old
// swap/full-document snapshot made a vertex drag O(document).
if let Some(orig) = self.grip_original.take() {
if let Some(e) = self.tabs[i].scene.document.get_entity_mut(h) {
let dragged = std::mem::replace(e, orig);
let snap = self.capture_history_snapshot(i, "GRIP");
if let Some(e2) = self.tabs[i].scene.document.get_entity_mut(h) {
*e2 = dragged;
}
self.tabs[i].history.undo_stack.push(snap);
self.tabs[i].history.redo_stack.clear();
self.tabs[i].dirty = true;
}
self.push_single_entity_history(i, "GRIP", h, std::sync::Arc::new(orig));
self.tabs[i].dirty = true;
}
self.grip_text_verts = Vec::new();
self.grip_text_slide = false;
self.tabs[i].scene.hidden.remove(&h);
self.tabs[i].scene.clear_preview_wire();
// Only the dragged entity changed — re-tessellate just it.
self.tabs[i].scene.mark_entity_dirty(h);
self.tabs[i].scene.bump_geometry_no_blocks();
self.tabs[i]
.scene
.bump_entities(&[(h, crate::scene::ChangeKind::Modified)]);
}
// Placement confirmed — keep the just-added leader.
self.grip_add_provisional = None;