use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct FpsLimiter {
max_fps: u16,
last_render: Instant,
frame_duration: Duration,
}
impl FpsLimiter {
pub fn new(max_fps: u16) -> Self {
let frame_duration = if max_fps > 0 {
Duration::from_secs(1) / max_fps as u32
} else {
Duration::ZERO
};
Self {
max_fps,
last_render: Instant::now(),
frame_duration,
}
}
pub fn should_render(&self) -> bool {
if self.max_fps == 0 {
return true;
}
self.last_render.elapsed() >= self.frame_duration
}
pub fn mark_rendered(&mut self) {
self.last_render = Instant::now();
}
pub fn time_until_next_frame(&self) -> Duration {
if self.max_fps == 0 {
return Duration::ZERO;
}
let elapsed = self.last_render.elapsed();
if elapsed >= self.frame_duration {
Duration::ZERO
} else {
self.frame_duration - elapsed
}
}
pub fn set_max_fps(&mut self, max_fps: u16) {
self.max_fps = max_fps;
self.frame_duration = if max_fps > 0 {
Duration::from_secs(1) / max_fps as u32
} else {
Duration::ZERO
};
}
pub fn max_fps(&self) -> u16 {
self.max_fps
}
pub fn frame_duration(&self) -> Duration {
self.frame_duration
}
pub fn actual_fps(&self) -> f64 {
let elapsed = self.last_render.elapsed();
if elapsed.as_secs_f64() > 0.0 {
1.0 / elapsed.as_secs_f64()
} else {
0.0
}
}
pub fn reset(&mut self) {
self.last_render = Instant::now();
}
}
impl Default for FpsLimiter {
fn default() -> Self {
Self::new(60) }
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_fps_limiter_creation() {
let limiter = FpsLimiter::new(60);
assert_eq!(limiter.max_fps(), 60);
assert_eq!(limiter.frame_duration(), Duration::from_secs(1) / 60);
}
#[test]
fn test_fps_limiter_no_limit() {
let limiter = FpsLimiter::new(0);
assert_eq!(limiter.max_fps(), 0);
assert_eq!(limiter.frame_duration(), Duration::ZERO);
assert!(limiter.should_render());
assert_eq!(limiter.time_until_next_frame(), Duration::ZERO);
}
#[test]
fn test_fps_limiter_should_render() {
let mut limiter = FpsLimiter::new(30);
limiter.mark_rendered();
assert!(!limiter.should_render());
thread::sleep(Duration::from_millis(40));
assert!(limiter.should_render());
}
#[test]
fn test_fps_limiter_time_until_next_frame() {
let mut limiter = FpsLimiter::new(60);
limiter.mark_rendered();
let time_until = limiter.time_until_next_frame();
assert!(time_until <= Duration::from_secs(1) / 60);
thread::sleep(Duration::from_millis(20));
let time_until = limiter.time_until_next_frame();
assert_eq!(time_until, Duration::ZERO);
}
#[test]
fn test_fps_limiter_set_max_fps() {
let mut limiter = FpsLimiter::new(60);
assert_eq!(limiter.max_fps(), 60);
limiter.set_max_fps(30);
assert_eq!(limiter.max_fps(), 30);
assert_eq!(limiter.frame_duration(), Duration::from_secs(1) / 30);
limiter.set_max_fps(0);
assert_eq!(limiter.max_fps(), 0);
assert_eq!(limiter.frame_duration(), Duration::ZERO);
}
#[test]
fn test_fps_limiter_reset() {
let mut limiter = FpsLimiter::new(60);
limiter.mark_rendered();
thread::sleep(Duration::from_millis(20));
limiter.reset();
assert!(!limiter.should_render());
}
#[test]
fn test_fps_limiter_default() {
let limiter = FpsLimiter::default();
assert_eq!(limiter.max_fps(), 60);
}
#[test]
fn test_fps_limiter_various_fps_values() {
let fps_values = vec![1, 24, 30, 60, 120, 144, 240];
for fps in fps_values {
let limiter = FpsLimiter::new(fps);
assert_eq!(limiter.max_fps(), fps);
assert_eq!(
limiter.frame_duration(),
Duration::from_secs(1) / fps as u32
);
}
}
#[test]
fn test_fps_limiter_actual_fps() {
let mut limiter = FpsLimiter::new(60);
limiter.mark_rendered();
thread::sleep(Duration::from_millis(200));
let actual_fps = limiter.actual_fps();
assert!(
actual_fps > 2.0 && actual_fps < 10.0,
"Expected FPS between 2-10, got {}",
actual_fps
);
}
#[test]
fn test_fps_limiter_high_fps() {
let mut limiter = FpsLimiter::new(240);
assert_eq!(limiter.max_fps(), 240);
let expected_duration = Duration::from_secs(1) / 240;
assert_eq!(limiter.frame_duration(), expected_duration);
limiter.mark_rendered();
assert!(!limiter.should_render());
thread::sleep(expected_duration + Duration::from_millis(1));
assert!(limiter.should_render());
}
#[test]
fn test_fps_limiter_low_fps() {
let mut limiter = FpsLimiter::new(1);
assert_eq!(limiter.max_fps(), 1);
assert_eq!(limiter.frame_duration(), Duration::from_secs(1));
limiter.mark_rendered();
assert!(!limiter.should_render());
thread::sleep(Duration::from_millis(500));
assert!(!limiter.should_render());
thread::sleep(Duration::from_millis(600));
assert!(limiter.should_render());
}
}