use std::time::{Duration, Instant}; pub struct FpsLimit { on_deck: Option<(T, Instant)>, min_interval: Duration, } impl FpsLimit { pub fn new(fps: u32) -> Self { Self { on_deck: None, min_interval: Duration::from_secs_f64(1.0 / fps as f64), } } /// Feed a new frame. Returns: /// - Some(previous_frame) if enough time elapsed since previous frame /// - None if frame is buffered (first frame) or previous is dropped (too close) pub fn on_new_frame(&mut self, frame: T, timestamp: Instant) -> Option { let old = self.on_deck.replace((frame, timestamp)); match old { None => None, // First frame — buffer it Some((old_frame, old_ts)) => { if timestamp.duration_since(old_ts) >= self.min_interval { Some(old_frame) // Enough time — output previous } else { None // Too close — discard previous, keep new } } } } /// Flush the last buffered frame at end of stream pub fn flush(&mut self) -> Option { self.on_deck.take().map(|(frame, _ts)| frame) } } #[cfg(test)] mod tests { use super::*; #[test] fn first_frame_is_buffered() { let mut limiter: FpsLimit = FpsLimit::new(30); let now = Instant::now(); let result = limiter.on_new_frame(1u32, now); assert!(result.is_none()); } #[test] fn frames_too_close_drops_old() { 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_output_old() { 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(40)); assert_eq!(result, Some(1)); } #[test] fn flush_returns_last_buffered() { let mut limiter: FpsLimit = FpsLimit::new(30); let now = Instant::now(); limiter.on_new_frame(1, now); assert_eq!(limiter.flush(), Some(1)); assert_eq!(limiter.flush(), None); } }