hojicha_runtime/program/
fps_limiter.rs1use std::time::{Duration, Instant};
4
5#[derive(Debug, Clone)]
7pub struct FpsLimiter {
8 max_fps: u16,
9 last_render: Instant,
10 frame_duration: Duration,
11}
12
13impl FpsLimiter {
14 pub fn new(max_fps: u16) -> Self {
16 let frame_duration = if max_fps > 0 {
17 Duration::from_secs(1) / max_fps as u32
18 } else {
19 Duration::ZERO
20 };
21
22 Self {
23 max_fps,
24 last_render: Instant::now(),
25 frame_duration,
26 }
27 }
28
29 pub fn should_render(&self) -> bool {
31 if self.max_fps == 0 {
32 return true;
34 }
35
36 self.last_render.elapsed() >= self.frame_duration
37 }
38
39 pub fn mark_rendered(&mut self) {
41 self.last_render = Instant::now();
42 }
43
44 pub fn time_until_next_frame(&self) -> Duration {
46 if self.max_fps == 0 {
47 return Duration::ZERO;
48 }
49
50 let elapsed = self.last_render.elapsed();
51 if elapsed >= self.frame_duration {
52 Duration::ZERO
53 } else {
54 self.frame_duration - elapsed
55 }
56 }
57
58 pub fn set_max_fps(&mut self, max_fps: u16) {
60 self.max_fps = max_fps;
61 self.frame_duration = if max_fps > 0 {
62 Duration::from_secs(1) / max_fps as u32
63 } else {
64 Duration::ZERO
65 };
66 }
67
68 pub fn max_fps(&self) -> u16 {
70 self.max_fps
71 }
72
73 pub fn frame_duration(&self) -> Duration {
75 self.frame_duration
76 }
77
78 pub fn actual_fps(&self) -> f64 {
80 let elapsed = self.last_render.elapsed();
81 if elapsed.as_secs_f64() > 0.0 {
82 1.0 / elapsed.as_secs_f64()
83 } else {
84 0.0
85 }
86 }
87
88 pub fn reset(&mut self) {
90 self.last_render = Instant::now();
91 }
92}
93
94impl Default for FpsLimiter {
95 fn default() -> Self {
96 Self::new(60) }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use std::thread;
104
105 #[test]
106 fn test_fps_limiter_creation() {
107 let limiter = FpsLimiter::new(60);
108 assert_eq!(limiter.max_fps(), 60);
109 assert_eq!(limiter.frame_duration(), Duration::from_secs(1) / 60);
110 }
111
112 #[test]
113 fn test_fps_limiter_no_limit() {
114 let limiter = FpsLimiter::new(0);
115 assert_eq!(limiter.max_fps(), 0);
116 assert_eq!(limiter.frame_duration(), Duration::ZERO);
117 assert!(limiter.should_render());
118 assert_eq!(limiter.time_until_next_frame(), Duration::ZERO);
119 }
120
121 #[test]
122 fn test_fps_limiter_should_render() {
123 let mut limiter = FpsLimiter::new(30); limiter.mark_rendered();
127
128 assert!(!limiter.should_render());
130
131 thread::sleep(Duration::from_millis(40)); assert!(limiter.should_render());
136 }
137
138 #[test]
139 fn test_fps_limiter_time_until_next_frame() {
140 let mut limiter = FpsLimiter::new(60);
141 limiter.mark_rendered();
142
143 let time_until = limiter.time_until_next_frame();
144 assert!(time_until <= Duration::from_secs(1) / 60);
145
146 thread::sleep(Duration::from_millis(20));
148
149 let time_until = limiter.time_until_next_frame();
150 assert_eq!(time_until, Duration::ZERO);
151 }
152
153 #[test]
154 fn test_fps_limiter_set_max_fps() {
155 let mut limiter = FpsLimiter::new(60);
156 assert_eq!(limiter.max_fps(), 60);
157
158 limiter.set_max_fps(30);
159 assert_eq!(limiter.max_fps(), 30);
160 assert_eq!(limiter.frame_duration(), Duration::from_secs(1) / 30);
161
162 limiter.set_max_fps(0);
163 assert_eq!(limiter.max_fps(), 0);
164 assert_eq!(limiter.frame_duration(), Duration::ZERO);
165 }
166
167 #[test]
168 fn test_fps_limiter_reset() {
169 let mut limiter = FpsLimiter::new(60);
170
171 limiter.mark_rendered();
173 thread::sleep(Duration::from_millis(20));
174
175 limiter.reset();
177
178 assert!(!limiter.should_render());
180 }
181
182 #[test]
183 fn test_fps_limiter_default() {
184 let limiter = FpsLimiter::default();
185 assert_eq!(limiter.max_fps(), 60);
186 }
187
188 #[test]
189 fn test_fps_limiter_various_fps_values() {
190 let fps_values = vec![1, 24, 30, 60, 120, 144, 240];
191
192 for fps in fps_values {
193 let limiter = FpsLimiter::new(fps);
194 assert_eq!(limiter.max_fps(), fps);
195 assert_eq!(
196 limiter.frame_duration(),
197 Duration::from_secs(1) / fps as u32
198 );
199 }
200 }
201
202 #[test]
203 fn test_fps_limiter_actual_fps() {
204 let mut limiter = FpsLimiter::new(60);
205
206 limiter.mark_rendered();
208 thread::sleep(Duration::from_millis(200)); let actual_fps = limiter.actual_fps();
212 assert!(
213 actual_fps > 2.0 && actual_fps < 10.0,
214 "Expected FPS between 2-10, got {}",
215 actual_fps
216 );
217 }
218
219 #[test]
220 fn test_fps_limiter_high_fps() {
221 let mut limiter = FpsLimiter::new(240);
222 assert_eq!(limiter.max_fps(), 240);
223
224 let expected_duration = Duration::from_secs(1) / 240;
226 assert_eq!(limiter.frame_duration(), expected_duration);
227
228 limiter.mark_rendered();
229 assert!(!limiter.should_render());
230
231 thread::sleep(expected_duration + Duration::from_millis(1));
233 assert!(limiter.should_render());
234 }
235
236 #[test]
237 fn test_fps_limiter_low_fps() {
238 let mut limiter = FpsLimiter::new(1);
239 assert_eq!(limiter.max_fps(), 1);
240
241 assert_eq!(limiter.frame_duration(), Duration::from_secs(1));
243
244 limiter.mark_rendered();
245 assert!(!limiter.should_render());
246
247 thread::sleep(Duration::from_millis(500));
249 assert!(!limiter.should_render());
250
251 thread::sleep(Duration::from_millis(600));
253 assert!(limiter.should_render());
254 }
255}