Files
ltk/benches/lookup.rs
Pedro M. de Echanove Pasquin 4aa3480b64 refactor: split every monolithic module into focused submodules
Each source file that had grown beyond a single concern is replaced by an identically-named directory containing focused submodules. `src/event_loop/mod.rs` (878 lines) becomes a directory with clipboard, context_menu, cursor_shape, drag, focus, handlers, invalidation, overlays_reconcile, repeat, run, surface, text_editing, and tooltip. Every widget, input handler, and theme component follows the same split. Public interfaces are unchanged — only the internal file layout moves.
image bumped from 0.25.2 to 0.25.9.
2026-05-15 23:46:56 +02:00

153 lines
4.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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<LaidOutWidget>` 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<LaidOutWidget<()>>
{
( 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,
cursor: ltk::CursorShape::Default,
tooltip: None,
}
} ).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 );