forked from 1b-agent/opencadstudio-upstream
perf(scene): make geometry edits incremental
Avoid full document, cache, and GPU rebuilds for entity deltas so add, delete, history, selection, and live polyline edits stay responsive on large drawings.
This commit is contained in:
1 parent
73b8dbe9d4
commit
05b044f367
32 files changed
+1936
-516
No files matched your search
+24
-14
@@ -199,7 +199,10 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.invalidate_dim_block_recorded(pending.handle);
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.bump_entities(&[(
|
||||
pending.handle,
|
||||
crate::scene::ChangeKind::Modified,
|
||||
)]);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_selected_grips();
|
||||
self.refresh_properties();
|
||||
@@ -534,7 +537,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
crate::scene::model::object::GripMenuAction::RemoveLeader,
|
||||
);
|
||||
}
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(h, crate::scene::ChangeKind::Modified)]);
|
||||
self.refresh_selected_grips();
|
||||
}
|
||||
// Cancel an in-progress grip drag: restore the edited
|
||||
@@ -649,7 +654,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
self.tabs[i]
|
||||
.layers
|
||||
.sync_with_viewports(&doc_layers, vp_info);
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
// Per-viewport layer visibility changes the resident
|
||||
// assembly, not any entity's tessellated geometry.
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
}
|
||||
@@ -1022,7 +1029,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.invalidate_dim_block_recorded(popup.handle);
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.bump_entities(&[(popup.handle, crate::scene::ChangeKind::Modified)]);
|
||||
self.tabs[i].dirty = true;
|
||||
self.refresh_selected_grips();
|
||||
self.refresh_properties();
|
||||
@@ -1534,7 +1543,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
.get(&value)
|
||||
.map(|br| br.name.clone());
|
||||
if let Some(canon) = canon {
|
||||
let mut changed = false;
|
||||
let mut changes = Vec::new();
|
||||
for &handle in &handles {
|
||||
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||
continue;
|
||||
@@ -1544,14 +1553,15 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
{
|
||||
if ins.block_name != canon {
|
||||
ins.block_name = canon.clone();
|
||||
changed = true;
|
||||
changes.push((
|
||||
handle,
|
||||
crate::scene::ChangeKind::Modified,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
// The picked block may not be in the defn cache
|
||||
// yet — rebuild block definitions too.
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
if !changes.is_empty() {
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
}
|
||||
}
|
||||
} else if field == "plot_style" {
|
||||
@@ -1804,7 +1814,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
if protected {
|
||||
return;
|
||||
}
|
||||
let mut changed = false;
|
||||
let mut changes = Vec::new();
|
||||
for &handle in handles {
|
||||
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||
continue;
|
||||
@@ -1814,12 +1824,12 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
{
|
||||
if ins.block_name != canon {
|
||||
ins.block_name = canon.clone();
|
||||
changed = true;
|
||||
changes.push((handle, crate::scene::ChangeKind::Modified));
|
||||
}
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
if !changes.is_empty() {
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1922,9 +1922,9 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
}
|
||||
|
||||
self.tabs[i].dirty = true;
|
||||
// The paper sheet fill is cached; bump geometry so the new
|
||||
// sheet size re-tessellates and shows immediately.
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
// The paper sheet fill is cached by epoch, while document
|
||||
// entity tessellation is unaffected by the paper size.
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
self.command_line.push_info(&format!(
|
||||
"Page setup: {w:.1}×{h:.1} mm area={plot_area} \
|
||||
center={center} rot={rotation}°"
|
||||
|
||||
+33
-4
@@ -39,6 +39,22 @@ fn is_modal_blocked_key_msg(msg: &Message) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
fn perf_message_label(msg: &Message) -> &'static str {
|
||||
match msg {
|
||||
Message::ViewportLeftPress | Message::PanePress(_) => "pointer-down",
|
||||
Message::ViewportLeftRelease | Message::PaneRelease(_) => "pointer-up",
|
||||
Message::ViewportMove(_) | Message::PaneMove(_, _) => "pointer-move",
|
||||
Message::CommandFinalize => "command-finalize",
|
||||
Message::CommandEscape => "command-escape",
|
||||
Message::Undo | Message::UndoMany(_) => "undo",
|
||||
Message::Redo | Message::RedoMany(_) => "redo",
|
||||
Message::DeleteSelected => "delete",
|
||||
Message::HoverDwellTick => "hover-dwell",
|
||||
Message::InteractionIndexReady { .. } => "interaction-index-ready",
|
||||
_ => "other",
|
||||
}
|
||||
}
|
||||
|
||||
const VIEWCUBE_HIT_SIZE: f32 = VIEWCUBE_DRAW_PX;
|
||||
|
||||
fn format_size(bytes: u64) -> String {
|
||||
@@ -139,6 +155,8 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
pub fn update(&mut self, msg: Message) -> Task<Message> {
|
||||
let perf_started = crate::perf::enabled().then(Instant::now);
|
||||
let perf_label = perf_message_label(&msg);
|
||||
// A modal dialog must capture the keyboard the same way it already
|
||||
// captures the mouse. Otherwise keystrokes from the global key
|
||||
// subscription leak past the modal into the command line and fire as
|
||||
@@ -185,6 +203,15 @@ impl OpenCADStudio {
|
||||
self.snapper.clear_tracking();
|
||||
self.otrack_active = None;
|
||||
}
|
||||
if let Some(started) = perf_started {
|
||||
let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
|
||||
if elapsed_ms >= 5.0 {
|
||||
crate::perf_record!(
|
||||
"[perf] update {:>7.1}ms message={perf_label}",
|
||||
elapsed_ms,
|
||||
);
|
||||
}
|
||||
}
|
||||
task
|
||||
}
|
||||
|
||||
@@ -223,7 +250,7 @@ impl OpenCADStudio {
|
||||
crate::scene::text::web_font::insert(script, Some(bytes));
|
||||
crate::scene::text::ttf_glyph::clear_fallback_cache();
|
||||
for tab in self.tabs.iter_mut() {
|
||||
tab.scene.bump_geometry();
|
||||
tab.scene.invalidate_text_geometry_dependencies();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -965,7 +992,9 @@ impl OpenCADStudio {
|
||||
&self.tabs[idx].scene.document.header.code_page,
|
||||
) {
|
||||
crate::scene::text::ttf_glyph::clear_fallback_cache();
|
||||
self.tabs[idx].scene.bump_geometry();
|
||||
self.tabs[idx]
|
||||
.scene
|
||||
.invalidate_text_geometry_dependencies();
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
@@ -2103,7 +2132,7 @@ impl OpenCADStudio {
|
||||
if let Some(tab) = self.tabs.get_mut(self.active_tab) {
|
||||
tab.scene.annotation_scale = scale;
|
||||
util::sync_annotation_scale_header(&mut tab.scene);
|
||||
tab.scene.bump_geometry();
|
||||
tab.scene.invalidate_annotation_dependencies();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
@@ -2341,7 +2370,7 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.document.header.annotation_scale_value = p / d;
|
||||
}
|
||||
}
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.invalidate_annotation_dependencies();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ impl OpenCADStudio {
|
||||
let next = if self.point_size_relative { -mag } else { mag };
|
||||
self.push_undo_snapshot(i, "PDSIZE");
|
||||
self.tabs[i].scene.document.header.point_display_size = next;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.invalidate_point_dependencies();
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
self.push_undo_snapshot(i, "PDMODE");
|
||||
self.tabs[i].scene.document.header.point_display_mode = next;
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.invalidate_point_dependencies();
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ impl OpenCADStudio {
|
||||
self.tabs[i].wireframe = wf;
|
||||
self.ribbon.set_wireframe(wf);
|
||||
self.tabs[i].visual_style = label.into();
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
}
|
||||
|
||||
/// Project a pane-local cursor onto the active drawing plane and return a
|
||||
@@ -405,7 +405,7 @@ impl OpenCADStudio {
|
||||
// viewport, the picker drives that viewport entity's own
|
||||
// render mode; the model-layout tab style is untouched.
|
||||
if self.tabs[i].scene.set_active_viewport_render_mode(mode) {
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
self.command_line
|
||||
.push_output(&format!("Viewport visual style: {label}"));
|
||||
return Task::none();
|
||||
@@ -423,7 +423,7 @@ impl OpenCADStudio {
|
||||
self.tabs[i].visual_style = label.into();
|
||||
// Re-upload face3d fills on the next frame — the render
|
||||
// pipeline keys its upload cache off `geometry_epoch`.
|
||||
self.tabs[i].scene.bump_geometry();
|
||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||
self.command_line
|
||||
.push_output(&format!("Visual style: {label}"));
|
||||
Task::none()
|
||||
|
||||
Reference in new issue
Block a user