bench(navigation): add viewport navigation and spatial candidate benchmarks

- Add Section 19 benchmarks covering camera pan viewport uniform updates,
  spatial hover candidate search, and viewport data assembly scaling
- Add navigation telemetry probes in src/perf.rs (active only when
  PERF=1) to track frame interval, batched input moves, and layout-draw gap
This commit is contained in:
Karim Jerbi committed 2026-10-08 10:24:06 +01:00
1 parent af41825a33
commit 7d008c4914
6 files changed
+220 -4

No files matched your search

+64
View File
@@ -10,6 +10,11 @@ const MAX_LINES: usize = 500;
static UI_ENABLED: AtomicBool = AtomicBool::new(false);
static ENV_ENABLED: OnceLock<bool> = OnceLock::new();
static LINES: OnceLock<Mutex<VecDeque<String>>> = OnceLock::new();
static POINTER_MOVES: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
static SCROLL_ZOOMS: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
static UPDATE_TIME_US: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
static LAST_FRAME_INSTANT: Mutex<Option<iced::time::Instant>> = Mutex::new(None);
static VIEW_FINISH_INSTANT: Mutex<Option<iced::time::Instant>> = Mutex::new(None);
#[cfg(not(target_arch = "wasm32"))]
static STDERR_TX: OnceLock<std::sync::mpsc::SyncSender<String>> = OnceLock::new();
@@ -17,6 +22,65 @@ fn lines() -> &'static Mutex<VecDeque<String>> {
LINES.get_or_init(|| Mutex::new(VecDeque::with_capacity(MAX_LINES)))
}
/// Record a pointer move event received in update.
pub fn note_pointer_move() {
if enabled() {
POINTER_MOVES.fetch_add(1, Ordering::Relaxed);
}
}
/// Record a zoom event received in update.
pub fn note_scroll_zoom() {
if enabled() {
SCROLL_ZOOMS.fetch_add(1, Ordering::Relaxed);
}
}
/// Accumulate time spent handling an update message in this batch.
pub fn note_update_time(duration_us: u64) {
if enabled() {
UPDATE_TIME_US.fetch_add(duration_us, Ordering::Relaxed);
}
}
/// Mark when view() finishes, to time the layout/draw gap until prepare().
pub fn note_view_finish() {
if enabled() {
let mut guard = VIEW_FINISH_INSTANT.lock().unwrap_or_else(|e| e.into_inner());
*guard = Some(iced::time::Instant::now());
}
}
/// Measure the gap between view() finish and prepare() start.
pub fn take_view_to_prepare_ms() -> Option<f64> {
if !enabled() {
return None;
}
let mut guard = VIEW_FINISH_INSTANT.lock().unwrap_or_else(|e| e.into_inner());
guard.take().map(|t| t.elapsed().as_secs_f64() * 1000.0)
}
/// Record a completed frame render and report frame-to-frame interval, batched moves, and update time.
pub fn note_frame_render(encode_ms: f64) {
if !enabled() {
return;
}
let moves = POINTER_MOVES.swap(0, Ordering::Relaxed);
let zooms = SCROLL_ZOOMS.swap(0, Ordering::Relaxed);
let update_us = UPDATE_TIME_US.swap(0, Ordering::Relaxed);
let mut guard = LAST_FRAME_INSTANT.lock().unwrap_or_else(|e| e.into_inner());
let now = iced::time::Instant::now();
let prev = guard.replace(now);
if let Some(prev) = prev {
let interval_ms = (now - prev).as_secs_f64() * 1000.0;
let update_ms = update_us as f64 / 1000.0;
record(format_args!(
"[perf] frame-interval {:>7.1}ms moves_batched={:<3} zooms={:<2} update_batch={:.2}ms encode={:.2}ms",
interval_ms, moves, zooms, update_ms, encode_ms
));
}
}
/// True when tracing was requested with `PERF=1` or the in-app panel is open.
pub fn enabled() -> bool {
UI_ENABLED.load(Ordering::Relaxed)