use std::collections::VecDeque;
use std::time::{Duration, Instant};
const MAX_SAMPLES: usize = 60;
pub struct FrameTimer {
target_frame_time: Duration,
fps_cap_enabled: bool,
frame_start: Instant,
last_frame_start: Instant,
frame_times: VecDeque<Duration>,
physics_accumulator: f32,
}
impl FrameTimer {
pub fn new() -> Self {
let now = Instant::now();
Self {
target_frame_time: Duration::from_secs_f64(1.0 / 60.0),
fps_cap_enabled: false,
frame_start: now,
last_frame_start: now,
frame_times: VecDeque::with_capacity(MAX_SAMPLES),
physics_accumulator: 0.0,
}
}
pub fn set_fps_cap(&mut self, enabled: bool) {
self.fps_cap_enabled = enabled;
}
pub fn begin_frame(&mut self) -> Duration {
self.last_frame_start = self.frame_start;
self.frame_start = Instant::now();
self.frame_start.duration_since(self.last_frame_start)
}
pub fn end_frame(&mut self) {
let elapsed = self.frame_start.elapsed();
self.frame_times.push_back(elapsed);
if self.frame_times.len() > MAX_SAMPLES {
self.frame_times.pop_front();
}
if self.fps_cap_enabled && elapsed < self.target_frame_time {
std::thread::sleep(self.target_frame_time - elapsed);
}
}
pub fn current_fps(&self) -> f32 {
if self.frame_times.is_empty() {
return 0.0;
}
let total: Duration = self.frame_times.iter().sum();
let avg_frame_time = total.as_secs_f32() / self.frame_times.len() as f32;
if avg_frame_time > 0.0 {
1.0 / avg_frame_time
} else {
0.0
}
}
pub fn physics_accumulator(&self) -> f32 {
self.physics_accumulator
}
pub fn add_physics_time(&mut self, delta: f32) {
self.physics_accumulator += delta;
}
pub fn consume_physics_step(&mut self, dt: f32) {
self.physics_accumulator -= dt;
}
pub fn clamp_accumulator(&mut self, max_steps: u32, dt: f32) {
let max_time = (max_steps as f32) * dt;
if self.physics_accumulator > max_time {
self.physics_accumulator = max_time;
}
}
pub fn target_frame_time(&self) -> Duration {
self.target_frame_time
}
}
impl Default for FrameTimer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frame_timer_creation() {
let timer = FrameTimer::new();
assert_eq!(timer.current_fps(), 0.0); }
#[test]
fn test_fps_cap_toggle() {
let mut timer = FrameTimer::new();
assert!(!timer.fps_cap_enabled);
timer.set_fps_cap(true);
assert!(timer.fps_cap_enabled);
timer.set_fps_cap(false);
assert!(!timer.fps_cap_enabled);
}
#[test]
fn test_physics_accumulator() {
let mut timer = FrameTimer::new();
assert_eq!(timer.physics_accumulator(), 0.0);
timer.add_physics_time(0.016);
assert!((timer.physics_accumulator() - 0.016).abs() < 0.0001);
timer.consume_physics_step(0.016);
assert!(timer.physics_accumulator().abs() < 0.0001);
}
#[test]
fn test_accumulator_clamping() {
let mut timer = FrameTimer::new();
timer.add_physics_time(1.0);
let dt = 1.0 / 60.0;
timer.clamp_accumulator(5, dt);
assert!(timer.physics_accumulator() <= 5.0 * dt + 0.0001);
}
}