First commit. Version 0.1.0

This commit is contained in:
2026-05-10 09:58:23 +02:00
parent af105b7f7d
commit bbab5e238d
635 changed files with 53627 additions and 175 deletions

150
benches/lookup.rs Normal file
View File

@@ -0,0 +1,150 @@
//! 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,
}
} ).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 );