use std::time::{Duration, Instant};
use tracing::debug;
const SLEEP_MARGIN: Duration = Duration::from_micros(300);
const REPORT_INTERVAL: Duration = Duration::from_secs(1);
const WARMUP_FRAMES: u64 = 10;
#[derive(Debug)]
pub(super) struct FramePacer {
last_frame: Instant,
}
impl FramePacer {
pub(super) fn new() -> Self {
Self {
last_frame: Instant::now(),
}
}
pub(super) fn wait_for_next_frame(&mut self, fps_limit: f32) {
if fps_limit.is_infinite() {
self.last_frame = Instant::now();
return;
}
let interval = Duration::from_secs_f32(1.0 / fps_limit.max(1.0));
let deadline = self.last_frame + interval;
if let Some(remaining) = deadline.checked_duration_since(Instant::now()) {
if remaining > SLEEP_MARGIN {
std::thread::sleep(remaining - SLEEP_MARGIN);
}
while Instant::now() < deadline {
std::thread::yield_now();
}
}
self.last_frame = Instant::now();
}
}
#[derive(Debug)]
pub(super) struct FrameStats {
window_start: Instant,
window_frames: u32,
last_frame: Instant,
max_frame: Duration,
total_frames: u64,
}
impl FrameStats {
pub(super) fn new() -> Self {
let now = Instant::now();
Self {
window_start: now,
window_frames: 0,
last_frame: now,
max_frame: Duration::ZERO,
total_frames: 0,
}
}
pub(super) fn record_frame(&mut self) -> Option<f32> {
let now = Instant::now();
self.total_frames += 1;
if self.total_frames > WARMUP_FRAMES {
self.max_frame = self.max_frame.max(now - self.last_frame);
}
self.last_frame = now;
self.window_frames += 1;
let elapsed = now - self.window_start;
if elapsed < REPORT_INTERVAL {
return None;
}
let fps = self.window_frames as f32 / elapsed.as_secs_f32();
let max_frame_ms = self.max_frame.as_secs_f32() * 1000.0;
debug!("frame_stats: fps={fps:.1} max_frame_ms={max_frame_ms:.3}");
self.window_start = now;
self.window_frames = 0;
self.max_frame = Duration::ZERO;
Some(fps)
}
}