use std::time::{Duration, Instant}; pub struct FpsLimit { on_deck: Option, last_output_time: Option, min_interval: Duration, } impl FpsLimit { pub fn new(fps: u32) -> Self { Self { on_deck: None, last_output_time: None, min_interval: Duration::from_secs_f64(1.0 / fps as f64), } } /// Feed a new frame. Returns: /// - Some(()) if enough time elapsed since the last output — proceed to encode current frame /// - None if too close to the last output — drop current frame pub fn on_new_frame(&mut self, frame: T, timestamp: Instant) -> Option { let ready = match self.last_output_time { None => true, Some(last) => timestamp.duration_since(last) >= self.min_interval, }; if ready { self.last_output_time = Some(timestamp); self.on_deck = Some(frame); self.on_deck.take() } else { let _ = self.on_deck.replace(frame); None } } pub fn flush(&mut self) -> Option { self.on_deck.take() } } #[cfg(test)] mod tests { use super::*; #[test] fn first_frame_passes_immediately() { let mut limiter: FpsLimit = FpsLimit::new(30); let now = Instant::now(); let result = limiter.on_new_frame(1u32, now); assert_eq!(result, Some(1)); } #[test] fn frames_too_close_are_dropped() { let mut limiter: FpsLimit = FpsLimit::new(30); let now = Instant::now(); limiter.on_new_frame(1, now); let result = limiter.on_new_frame(2, now + Duration::from_millis(1)); assert!(result.is_none()); } #[test] fn frames_far_enough_pass() { let mut limiter: FpsLimit = FpsLimit::new(30); let now = Instant::now(); limiter.on_new_frame(1, now); let result = limiter.on_new_frame(2, now + Duration::from_millis(34)); assert_eq!(result, Some(2)); } #[test] fn high_fps_input_downsampled_correctly() { let mut limiter: FpsLimit = FpsLimit::new(30); let base = Instant::now(); let mut outputs = Vec::new(); for i in 0..10u32 { let t = base + Duration::from_millis(i as u64 * 16); if let Some(f) = limiter.on_new_frame(i, t) { outputs.push(f); } } assert!(outputs.len() >= 3, "expected at least 3 outputs, got {} ({:?})", outputs.len(), outputs); assert_eq!(outputs[0], 0); } #[test] fn flush_returns_last_buffered() { let mut limiter: FpsLimit = FpsLimit::new(30); let now = Instant::now(); limiter.on_new_frame(1, now); limiter.on_new_frame(2, now + Duration::from_millis(1)); assert_eq!(limiter.flush(), Some(2)); assert_eq!(limiter.flush(), None); } }