warden_vision/utils/
fps.rs1use std::collections::VecDeque;
5use std::time::Instant;
6
7#[derive(Debug, Clone)]
10pub struct FPSMonitor {
11 timestamps: VecDeque<Instant>,
12 window_size: usize,
13}
14
15impl FPSMonitor {
16 pub fn new() -> Self {
18 Self::with_window_size(30)
19 }
20
21 pub fn with_window_size(window_size: usize) -> Self {
26 assert!(window_size > 0, "window_size must be at least 1");
27 Self {
28 timestamps: VecDeque::with_capacity(window_size),
29 window_size,
30 }
31 }
32
33 pub fn tick(&mut self) {
35 self.timestamps.push_back(Instant::now());
36 while self.timestamps.len() > self.window_size {
37 self.timestamps.pop_front();
38 }
39 }
40
41 pub fn fps(&self) -> f32 {
46 if self.timestamps.len() < 2 {
47 return 0.0;
48 }
49 let first = *self.timestamps.front().expect("checked len >= 2 above");
50 let last = *self.timestamps.back().expect("checked len >= 2 above");
51 let elapsed = last.duration_since(first).as_secs_f32();
52 if elapsed <= 0.0 {
53 return 0.0;
54 }
55 (self.timestamps.len() - 1) as f32 / elapsed
56 }
57
58 pub fn reset(&mut self) {
60 self.timestamps.clear();
61 }
62}
63
64impl Default for FPSMonitor {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73 use std::thread::sleep;
74 use std::time::Duration;
75
76 #[test]
77 fn fps_is_zero_before_two_ticks() {
78 let mut monitor = FPSMonitor::new();
79 assert_eq!(monitor.fps(), 0.0);
80 monitor.tick();
81 assert_eq!(monitor.fps(), 0.0);
82 }
83
84 #[test]
85 fn fps_is_positive_and_finite_after_ticks_with_elapsed_time() {
86 let mut monitor = FPSMonitor::new();
87 for _ in 0..5 {
88 monitor.tick();
89 sleep(Duration::from_millis(1));
90 }
91 let fps = monitor.fps();
92 assert!(fps > 0.0);
93 assert!(fps.is_finite());
94 }
95
96 #[test]
97 fn reset_clears_the_window() {
98 let mut monitor = FPSMonitor::new();
99 monitor.tick();
100 monitor.tick();
101 monitor.reset();
102 assert_eq!(monitor.fps(), 0.0);
103 }
104
105 #[test]
106 #[should_panic]
107 fn zero_window_size_panics() {
108 FPSMonitor::with_window_size(0);
109 }
110
111 #[test]
112 fn window_caps_at_configured_size() {
113 let mut monitor = FPSMonitor::with_window_size(3);
114 for _ in 0..10 {
115 monitor.tick();
116 sleep(Duration::from_millis(1));
117 }
118 let fps = monitor.fps();
121 assert!(fps > 0.0 && fps.is_finite());
122 }
123}