ferroui_base/rendering/
sleep_loop_render_timer.rs1#![cfg(not(target_family = "wasm"))]
4
5use std::sync::atomic::{AtomicBool, AtomicI32, Ordering};
6use std::sync::{Arc, Mutex, PoisonError};
7use std::thread;
8use std::time::{Duration, Instant};
9
10use super::auto_reset_event::AutoResetEvent;
11use super::{IRenderTimer, RenderTimerTick};
12
13struct Shared {
15 tick: Mutex<Option<RenderTimerTick>>,
16 stopped: AtomicBool,
17 wake_event: AutoResetEvent,
18 st: Instant,
19 desired_fps: AtomicI32,
20 released: AtomicBool,
22}
23
24pub struct SleepLoopRenderTimer {
27 shared: Arc<Shared>,
28 thread_started: Mutex<bool>,
29}
30
31impl SleepLoopRenderTimer {
32 pub fn new(fps: i32) -> Self {
35 let timer = Self {
36 shared: Arc::new(Shared {
37 tick: Mutex::new(None),
38 stopped: AtomicBool::new(true),
39 wake_event: AutoResetEvent::new(false),
40 st: Instant::now(),
41 desired_fps: AtomicI32::new(0),
42 released: AtomicBool::new(false),
43 }),
44 thread_started: Mutex::new(false),
45 };
46 timer.set_desired_fps(fps);
47 timer
48 }
49
50 pub fn desired_fps(&self) -> i32 {
51 self.shared.desired_fps.load(Ordering::SeqCst)
52 }
53
54 pub fn set_desired_fps(&self, value: i32) {
57 if value < 1 {
58 panic!("value ('{value}') must be greater than or equal to '1'. (Parameter 'value')");
59 }
60 self.shared.desired_fps.store(value, Ordering::SeqCst);
61 }
62}
63
64impl Shared {
65 fn loop_proc(&self) {
66 let mut last_tick = self.st.elapsed();
67 loop {
68 if self.stopped.load(Ordering::SeqCst) {
69 self.wake_event.wait_one();
70 }
71 if self.released.load(Ordering::SeqCst) {
72 return;
73 }
74
75 let now = self.st.elapsed();
76 let tick_interval = Duration::from_secs_f64(1.0 / f64::from(self.desired_fps.load(Ordering::SeqCst)));
77 if let Some(time_till_next_tick) = (last_tick + tick_interval).checked_sub(now) {
79 if time_till_next_tick.as_secs_f64() * 1000.0 > 1.0 {
80 self.wake_event.wait_one_timeout(time_till_next_tick);
81 }
82 }
83 if self.released.load(Ordering::SeqCst) {
84 return;
85 }
86 let now = self.st.elapsed();
87 last_tick = now;
88
89 let tick = self.tick.lock().unwrap_or_else(PoisonError::into_inner).clone();
90 if let Some(tick) = tick {
91 tick(now);
92 }
93 }
94 }
95}
96
97impl IRenderTimer for SleepLoopRenderTimer {
98 fn tick(&self) -> Option<RenderTimerTick> {
99 self.shared.tick.lock().unwrap_or_else(PoisonError::into_inner).clone()
100 }
101
102 fn set_tick(&self, value: Option<RenderTimerTick>) {
103 match value {
104 Some(value) => {
105 *self.shared.tick.lock().unwrap_or_else(PoisonError::into_inner) = Some(value);
106 self.shared.stopped.store(false, Ordering::SeqCst);
107 let mut thread_started = self.thread_started.lock().unwrap_or_else(PoisonError::into_inner);
108 if !*thread_started {
109 *thread_started = true;
110 let shared = self.shared.clone();
111 thread::Builder::new()
112 .name("SleepLoopRenderTimer".to_string())
113 .spawn(move || shared.loop_proc())
114 .expect("failed to start the render timer thread");
115 } else {
116 self.shared.wake_event.set();
117 }
118 }
119 None => {
120 self.shared.stopped.store(true, Ordering::SeqCst);
121 *self.shared.tick.lock().unwrap_or_else(PoisonError::into_inner) = None;
122 }
123 }
124 }
125
126 fn runs_in_background(&self) -> bool {
127 true
128 }
129}
130
131impl Drop for SleepLoopRenderTimer {
132 fn drop(&mut self) {
133 self.shared.released.store(true, Ordering::SeqCst);
134 self.shared.stopped.store(true, Ordering::SeqCst);
135 self.shared.wake_event.set();
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use std::sync::mpsc;
143
144 #[test]
145 #[should_panic(expected = "must be greater than or equal to '1'")]
146 fn fps_must_be_positive() {
147 SleepLoopRenderTimer::new(0);
148 }
149
150 #[test]
151 fn ticks_with_increasing_timestamps_until_stopped() {
152 let timer = SleepLoopRenderTimer::new(500);
153 assert!(timer.runs_in_background());
154 assert_eq!(timer.desired_fps(), 500);
155 timer.set_desired_fps(1000);
156 assert_eq!(timer.desired_fps(), 1000);
157 assert!(timer.tick().is_none());
158
159 let (sender, receiver) = mpsc::channel();
160 let sender = Mutex::new(sender);
161 timer.set_tick(Some(Arc::new(move |time| {
162 let _ = sender.lock().unwrap().send(time);
163 })));
164 assert!(timer.tick().is_some());
165
166 let first = receiver.recv_timeout(Duration::from_secs(10)).unwrap();
167 let second = receiver.recv_timeout(Duration::from_secs(10)).unwrap();
168 assert!(second > first);
169
170 timer.set_tick(None);
171 assert!(timer.tick().is_none());
172 loop {
175 match receiver.recv_timeout(Duration::from_secs(10)) {
176 Ok(_) => {}
177 Err(mpsc::RecvTimeoutError::Disconnected) => break,
178 Err(mpsc::RecvTimeoutError::Timeout) => panic!("the timer did not stop"),
179 }
180 }
181
182 let (sender, receiver) = mpsc::channel();
184 let sender = Mutex::new(sender);
185 timer.set_tick(Some(Arc::new(move |time| {
186 let _ = sender.lock().unwrap().send(time);
187 })));
188 assert!(receiver.recv_timeout(Duration::from_secs(10)).unwrap() > second);
189 }
190}