use std::collections::VecDeque;
use std::time::Duration;
const DEFAULT_MAX_SAMPLES: usize = 500;
fn format_ms(ms: f64) -> String {
let duration = Duration::from_secs_f64(ms.max(0.0) / 1000.0);
format!("{duration:?}")
}
pub struct PerfCapture {
samples: VecDeque<Duration>,
max_samples: usize,
}
impl Default for PerfCapture {
fn default() -> Self {
Self::new(DEFAULT_MAX_SAMPLES)
}
}
impl PerfCapture {
pub fn new(max_samples: usize) -> Self {
Self {
samples: VecDeque::with_capacity(max_samples),
max_samples,
}
}
pub fn record(&mut self, duration: Duration) {
if self.samples.len() >= self.max_samples {
self.samples.pop_front();
}
self.samples.push_back(duration);
}
pub fn samples(&self) -> &VecDeque<Duration> {
&self.samples
}
pub fn max_samples(&self) -> usize {
self.max_samples
}
pub fn set_max_samples(&mut self, max: usize) {
self.max_samples = max;
while self.samples.len() > max {
self.samples.pop_front();
}
}
}
pub struct PerfView<'a> {
id: egui::Id,
title: &'a str,
capture: &'a mut PerfCapture,
}
impl<'a> PerfView<'a> {
pub fn new(title: &'a str, capture: &'a mut PerfCapture) -> Self {
let id = egui::Id::new(title);
Self { id, title, capture }
}
pub fn with_id(mut self, id: egui::Id) -> Self {
self.id = id;
self
}
pub fn show(&mut self, ui: &mut egui::Ui) {
let state_id = self.id.with("state");
let mut max_samples = ui
.memory(|m| m.data.get_temp::<usize>(state_id))
.unwrap_or(self.capture.max_samples());
let text_color = ui.visuals().text_color();
let points: egui_plot::PlotPoints = self
.capture
.samples()
.iter()
.enumerate()
.map(|(i, d)| [i as f64, d.as_secs_f64() * 1000.0])
.collect();
let line = egui_plot::Line::new(self.title, points)
.color(text_color)
.fill(0.0)
.fill_alpha(0.3);
let plot = egui_plot::Plot::new(self.id)
.height(ui.available_height())
.width(ui.available_width())
.allow_boxed_zoom(false)
.allow_drag(false)
.allow_scroll(false)
.show_axes(false)
.show_grid(false)
.set_margin_fraction(egui::Vec2::ZERO)
.include_y(0.0)
.label_formatter(|_, point| format_ms(point.y));
ui.scope(|ui| {
ui.style_mut().visuals.widgets.noninteractive.bg_stroke = egui::Stroke::NONE;
ui.style_mut().visuals.extreme_bg_color = ui.style().visuals.window_fill;
let plot_response = plot.show(ui, |plot_ui| {
plot_ui.line(line);
});
plot_response.response.context_menu(|ui| {
ui.label("Max Samples");
ui.horizontal(|ui| {
for &n in &[100, 250, 500, 1000, 2000] {
if ui
.selectable_label(max_samples == n, format!("{n}"))
.clicked()
{
max_samples = n;
self.capture.set_max_samples(n);
ui.close();
}
}
});
});
});
ui.memory_mut(|m| m.data.insert_temp(state_id, max_samples));
}
}