From b4ae4cad4e3a21d5f79e27c81d7b2d03740ec30a Mon Sep 17 00:00:00 2001 From: Panos <25664832+mechpanos@users.noreply.github.com> Date: Tue, 8 Sep 2026 11:25:10 +0300 Subject: [PATCH] perf: measure an edit from the input to the frame that answers it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drawing a point in a large drawing feels slow, and nothing currently measures the thing the user actually waits for. `update` times the message handler, `wires-build` and friends time the rebuild, but the span between them — press to the frame that shows the new geometry — has no number. Navigation already has exactly that plumbing: `record_nav_perf` stamps an instant when the input arrives, `prepare` and `encode` pick it up, and `nav-prepare` / `nav-render` report `input`, `build`, `prepare` and `elapsed` from the original event. `NavPerfOp::Edit` reuses it. `update` snapshots `geometry_epoch` before dispatch and stamps the sample when it moved, so any message that changed geometry — a drawn point, a grip commit, an undo — reports its full latency rather than only the handler's share. Diagnostic only: `record_nav_perf` sets a pending sample and nothing else, so no LOD or settle behaviour changes. Nothing is optimised here on purpose. Four call sites feed the snapper along different branches, and picking one to instrument before knowing which dominates is how the earlier rounds of this work were wasted. Co-Authored-By: Claude Opus 5 --- src/app/update/mod.rs | 14 ++++++++++++++ src/scene/mod.rs | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index b80f9ee7c..c612c61d0 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -265,6 +265,13 @@ impl OpenCADStudio { self.control_observe_user_message(&msg); let perf_started = crate::perf::enabled().then(Instant::now); let perf_label = perf_message_label(&msg); + // Geometry epoch before dispatch. When it moves, this message drew or + // changed something, and the latency the user feels runs from here to + // the frame that shows it — not to the end of this handler. Stamping + // the nav-perf sample makes `nav-prepare` / `nav-render` report that + // span, the same way they already do for pan and zoom. + let perf_epoch_before = perf_started + .map(|_| self.tabs[self.active_tab].scene.geometry_epoch); // 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 @@ -324,6 +331,13 @@ impl OpenCADStudio { self.otrack_active = None; self.otrack_kind = None; } + if let (Some(started), Some(before)) = (perf_started, perf_epoch_before) { + let tab = &self.tabs[self.active_tab]; + if tab.scene.geometry_epoch != before { + tab.scene + .record_nav_perf(crate::scene::NavPerfOp::Edit, started); + } + } if let Some(started) = perf_started { let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0; if elapsed_ms >= 5.0 { diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 07d219156..4d20b37a3 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -1432,6 +1432,10 @@ pub(crate) enum NavPerfOp { Pan, Zoom, Rotate, + /// A message that changed geometry. Reported through the same line as + /// navigation because the question is the same one: how long from the + /// input to the frame that answers it. + Edit, } impl NavPerfOp { @@ -1440,6 +1444,7 @@ impl NavPerfOp { Self::Pan => "pan", Self::Zoom => "zoom", Self::Rotate => "rotate", + Self::Edit => "edit", } } }