//! Per-frame dispatch hot path benches. //! //! The renderer rebuilds the widget tree on every frame, then hit-tests //! pointer events against the resulting flat `Vec` and //! looks up handlers by `flat_idx`. Both calls are O(N) in the slice //! length. Run with `cargo bench --bench lookup`. use criterion::{ black_box, criterion_group, criterion_main, BenchmarkId, Criterion }; use ltk::test_support::{ find_handlers, find_widget, find_widget_at, LaidOutWidget, WidgetHandlers }; use ltk::{ Point, Rect }; // ── Fixture builders ────────────────────────────────────────────────────────── /// Build N widgets laid out in a vertical strip. Each widget is 100×30 with a /// 1px gap, so `find_widget_at(x=50, y=k*31+15)` lands inside widget `k`. Half /// of them carry a Button handler so `find_handlers` exercises the /// match-and-clone path on roughly every other lookup. fn build_widgets( n: usize ) -> Vec> { ( 0 .. n ).map( |i| { let rect = Rect { x: 0.0, y: ( i as f32 ) * 31.0, width: 100.0, height: 30.0, }; let handlers = if i % 2 == 0 { WidgetHandlers::Button { on_press: Some( () ), on_long_press: None, on_drag_start: None, on_escape: None, repeating: false } } else { WidgetHandlers::None }; LaidOutWidget { rect, flat_idx: i, id: None, paint_rect: rect, handlers, keyboard_focusable: true, } } ).collect() } // ── Benches ─────────────────────────────────────────────────────────────────── /// Pointer hit testing on a hot path. Three sub-benches: /// - last: hit the last widget (worst case under reverse iteration is the /// *first* widget; the helper iterates from the end to honour /// "topmost wins", so the last widget in slice order is the cheap /// one and the first is the expensive one). /// - first: hit the first widget — full-slice walk. /// - miss: point that lies outside every rect — also a full-slice walk /// but never returns early. fn bench_find_widget_at( c: &mut Criterion ) { let mut group = c.benchmark_group( "find_widget_at" ); for &n in &[ 10usize, 100, 1000 ] { let widgets = build_widgets( n ); // Hit on the last widget in slice order — `find_widget_at` walks in // reverse, so this returns on the first iteration. let last_y = ( n as f32 - 1.0 ) * 31.0 + 15.0; group.bench_with_input( BenchmarkId::new( "hit_last", n ), &n, |b, _| { b.iter( || { let p = Point { x: 50.0, y: last_y }; black_box( find_widget_at( black_box( &widgets ), black_box( p ) ) ) } ); } ); // Hit on the first widget — full reverse walk. group.bench_with_input( BenchmarkId::new( "hit_first", n ), &n, |b, _| { b.iter( || { let p = Point { x: 50.0, y: 15.0 }; black_box( find_widget_at( black_box( &widgets ), black_box( p ) ) ) } ); } ); // Miss — point sits to the right of every rect. group.bench_with_input( BenchmarkId::new( "miss", n ), &n, |b, _| { b.iter( || { let p = Point { x: 9_999.0, y: 9_999.0 }; black_box( find_widget_at( black_box( &widgets ), black_box( p ) ) ) } ); } ); } group.finish(); } /// `flat_idx` lookup. Linear scan; we measure the worst case (last entry) /// since it's what bounds latency on real frames. fn bench_find_widget( c: &mut Criterion ) { let mut group = c.benchmark_group( "find_widget" ); for &n in &[ 10usize, 100, 1000 ] { let widgets = build_widgets( n ); let target = n - 1; group.bench_with_input( BenchmarkId::new( "last", n ), &n, |b, _| { b.iter( || { black_box( find_widget( black_box( &widgets ), black_box( target ) ) ) } ); } ); } group.finish(); } /// Same shape as `find_widget` but exercises the handler-cloning path on hit. /// Useful as a separate measurement because the clone cost grows with the /// handler payload, even though the scan itself does not. fn bench_find_handlers( c: &mut Criterion ) { let mut group = c.benchmark_group( "find_handlers" ); for &n in &[ 10usize, 100, 1000 ] { let widgets = build_widgets( n ); let target = n - 1; group.bench_with_input( BenchmarkId::new( "last", n ), &n, |b, _| { b.iter( || { black_box( find_handlers( black_box( &widgets ), black_box( target ) ) ) } ); } ); } group.finish(); } criterion_group!( benches, bench_find_widget_at, bench_find_widget, bench_find_handlers ); criterion_main!( benches );