use std::hint::black_box;
use criterion::{BenchmarkId, Criterion, criterion_group, criterion_main};
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Color, Style};
use ratatui::text::{Line, Span};
use tuika::components::{Scroll, ScrollState};
use tuika::{Event, Key, KeyCode, RenderCtx, Surface, Theme, View};
const WIDTH: u16 = 80;
const HEIGHT: u16 = 24;
const SIZES: &[(&str, usize)] = &[("small", 100), ("medium", 10_000), ("large", 100_000)];
fn transcript(rows: usize) -> Vec<Line<'static>> {
(0..rows)
.map(|i| {
Line::from(vec![
Span::styled(format!("{i:>6} │ "), Style::default().fg(Color::DarkGray)),
Span::styled(
"the quick brown fox jumps over the lazy dog once more",
Style::default().fg(Color::Gray),
),
])
})
.collect()
}
fn scroll_at(rows: usize, top: bool) -> Scroll {
let mut state = ScrollState::new();
if top {
state.jump_to_top();
} else {
state.clamp(rows, HEIGHT as usize);
}
Scroll::new(transcript(rows), &state)
}
fn paint_frame(scroll: &Scroll, buffer: &mut Buffer, area: Rect, ctx: &RenderCtx) {
let mut surface = Surface::new(buffer, area);
scroll.render(black_box(area), &mut surface, ctx);
}
fn render(c: &mut Criterion) {
let theme = Theme::default();
let ctx = RenderCtx::new(&theme);
let area = Rect::new(0, 0, WIDTH, HEIGHT);
let mut group = c.benchmark_group("scroll/render");
for &(name, rows) in SIZES {
let scroll = scroll_at(rows, false);
let mut buffer = Buffer::empty(area);
group.bench_function(name, |b| {
b.iter(|| paint_frame(&scroll, &mut buffer, area, &ctx));
});
}
group.finish();
}
fn render_offset(c: &mut Criterion) {
let theme = Theme::default();
let ctx = RenderCtx::new(&theme);
let area = Rect::new(0, 0, WIDTH, HEIGHT);
let rows = 100_000;
let mut group = c.benchmark_group("scroll/render_offset");
for &(name, top) in &[("top", true), ("bottom", false)] {
let scroll = scroll_at(rows, top);
let mut buffer = Buffer::empty(area);
group.bench_function(name, |b| {
b.iter(|| paint_frame(&scroll, &mut buffer, area, &ctx));
});
}
group.finish();
}
fn paging(c: &mut Criterion) {
let mut group = c.benchmark_group("scroll/paging");
let page_down = Event::Key(Key::new(KeyCode::PageDown));
for &(name, rows) in SIZES {
group.bench_with_input(BenchmarkId::from_parameter(name), &rows, |b, &rows| {
b.iter(|| {
let mut state = ScrollState::new();
state.jump_to_top();
while !state.is_stuck_to_bottom() {
state.handle(black_box(&page_down), rows, HEIGHT as usize);
}
black_box(state.offset())
});
});
}
group.finish();
}
fn frame(c: &mut Criterion) {
let theme = Theme::default();
let ctx = RenderCtx::new(&theme);
let area = Rect::new(0, 0, WIDTH, HEIGHT);
let h = HEIGHT as usize;
let mut group = c.benchmark_group("scroll/frame");
for &(name, rows) in SIZES {
let content = transcript(rows);
let mut state = ScrollState::new();
state.clamp(rows, h); let offset = state.offset();
let mut buffer = Buffer::empty(area);
group.bench_with_input(BenchmarkId::new("full", name), &content, |b, content| {
b.iter(|| {
let scroll = Scroll::new(content.clone(), &state);
paint_frame(&scroll, &mut buffer, area, &ctx);
});
});
group.bench_with_input(
BenchmarkId::new("windowed", name),
&content,
|b, content| {
b.iter(|| {
let end = (offset + h).min(content.len());
let window = content[offset..end].to_vec();
let scroll = Scroll::windowed(window, content.len(), &state);
paint_frame(&scroll, &mut buffer, area, &ctx);
});
},
);
}
group.finish();
}
criterion_group!(benches, render, render_offset, paging, frame);
criterion_main!(benches);