ferroui_base/rendering/
ui_thread_render_timer.rs1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::rc::Rc;
4use std::sync::atomic::{AtomicU64, Ordering};
5use std::sync::{Arc, Mutex, PoisonError};
6use std::time::Duration;
7
8use super::{
9 DefaultRenderTimer, DefaultRenderTimerImpl, IRenderTimer, RenderTimerSubscription, RenderTimerTick,
10};
11use crate::reactive::IDisposable;
12use crate::threading::{Dispatcher, DispatcherPriority, DispatcherTimer};
13
14#[derive(Default)]
17struct Clock {
18 started_at: Mutex<Option<i64>>,
19}
20
21impl Clock {
22 fn elapsed(&self, dispatcher: &Dispatcher) -> Duration {
23 let now = dispatcher.now();
24 let mut started_at = self.started_at.lock().unwrap_or_else(PoisonError::into_inner);
25 let started_at = *started_at.get_or_insert(now);
26 Duration::from_millis(now.saturating_sub(started_at).max(0) as u64)
27 }
28}
29
30crate::ferro_thread_local! {
31 static INSTANCES: RefCell<HashMap<u64, Rc<DispatcherTimer>>> = RefCell::new(HashMap::new());
35}
36
37static NEXT_INSTANCE_ID: AtomicU64 = AtomicU64::new(1);
38
39const MIN_INTERVAL: Duration = Duration::from_millis(1);
40
41struct RescheduleOnExit<'a> {
44 timer: &'a DispatcherTimer,
45 clock: &'a Clock,
46 next_tick_at: Duration,
47}
48
49impl Drop for RescheduleOnExit<'_> {
50 fn drop(&mut self) {
51 let after_tick = self.clock.elapsed(self.timer.dispatcher());
52 let interval = match self.next_tick_at.checked_sub(after_tick) {
53 Some(interval) if interval >= MIN_INTERVAL => interval,
54 _ => MIN_INTERVAL,
56 };
57 self.timer.set_interval(interval);
58 }
59}
60
61struct TimerInstance {
62 id: u64,
63 dispatcher: Arc<Dispatcher>,
64}
65
66impl TimerInstance {
67 fn new(frames_per_second: i32, clock: Arc<Clock>, tick: RenderTimerTick) -> TimerInstance {
68 let timer = DispatcherTimer::with_priority(DispatcherPriority::RENDER);
69 let dispatcher = timer.dispatcher().clone();
70 clock.elapsed(&dispatcher);
72
73 let interval = match Duration::try_from_secs_f64(1.0 / f64::from(frames_per_second)) {
74 Ok(interval) => interval,
75 Err(_) => panic!("frames_per_second must be a positive number."),
76 };
77
78 timer.tick(move |timer| {
79 let ticked_at = clock.elapsed(timer.dispatcher());
80 let _reschedule = RescheduleOnExit { timer, clock: &clock, next_tick_at: ticked_at + interval };
81 tick(ticked_at);
82 });
83 timer.set_interval(Duration::ZERO);
86 timer.start();
87
88 let id = NEXT_INSTANCE_ID.fetch_add(1, Ordering::Relaxed);
89 INSTANCES.with(|instances| instances.borrow_mut().insert(id, timer));
90 TimerInstance { id, dispatcher }
91 }
92
93 fn stop(id: u64) {
94 let timer = INSTANCES.try_with(|instances| instances.borrow_mut().remove(&id)).ok().flatten();
95 if let Some(timer) = timer {
96 timer.stop();
97 }
98 }
99}
100
101impl IDisposable for TimerInstance {
102 fn dispose(&self) {
103 let id = self.id;
104 if self.dispatcher.check_access() {
105 Self::stop(id);
106 } else {
107 self.dispatcher.post(move || Self::stop(id), DispatcherPriority::SEND);
109 }
110 }
111}
112
113struct UiThreadRenderTimerImpl {
114 clock: Arc<Clock>,
115}
116
117impl DefaultRenderTimerImpl for UiThreadRenderTimerImpl {
118 fn runs_in_background(&self) -> bool {
119 false
120 }
121
122 fn start_core(&self, timer: &DefaultRenderTimer, tick: RenderTimerTick) -> RenderTimerSubscription {
123 Arc::new(TimerInstance::new(timer.frames_per_second(), self.clock.clone(), tick))
124 }
125}
126
127pub struct UiThreadRenderTimer {
134 base: DefaultRenderTimer,
135}
136
137impl UiThreadRenderTimer {
138 pub fn new(frames_per_second: i32) -> Self {
143 Self {
144 base: DefaultRenderTimer::with_impl(
145 frames_per_second,
146 Box::new(UiThreadRenderTimerImpl { clock: Arc::new(Clock::default()) }),
147 ),
148 }
149 }
150
151 pub fn frames_per_second(&self) -> i32 {
153 self.base.frames_per_second()
154 }
155}
156
157impl IRenderTimer for UiThreadRenderTimer {
158 fn tick(&self) -> Option<RenderTimerTick> {
159 self.base.tick()
160 }
161
162 fn set_tick(&self, value: Option<RenderTimerTick>) {
163 self.base.set_tick(value);
164 }
165
166 fn runs_in_background(&self) -> bool {
167 self.base.runs_in_background()
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use crate::threading::{DispatcherImplEvent, IDispatcherImpl};
175 use std::sync::atomic::AtomicI64;
176 use std::thread::{self, ThreadId};
177
178 struct ManualDispatcherImpl {
181 loop_thread: ThreadId,
182 now: Arc<AtomicI64>,
183 next_timer: Mutex<Option<i64>>,
184 signaled: DispatcherImplEvent,
185 timer: DispatcherImplEvent,
186 }
187
188 impl ManualDispatcherImpl {
189 fn new() -> Arc<Self> {
190 Arc::new(Self {
191 loop_thread: thread::current().id(),
192 now: Arc::new(AtomicI64::new(0)),
193 next_timer: Mutex::new(None),
194 signaled: DispatcherImplEvent::new(),
195 timer: DispatcherImplEvent::new(),
196 })
197 }
198
199 fn due_time(&self) -> Option<i64> {
201 let timers = Dispatcher::snapshot_timers_for_unit_tests();
202 assert!(timers.len() <= 1);
203 timers.first().map(|timer| timer.due_time_in_ms())
204 }
205
206 fn fire_timer_at(&self, now: i64) {
208 self.now.store(now, Ordering::SeqCst);
209 self.timer.raise();
210 }
211 }
212
213 struct Forward(Arc<ManualDispatcherImpl>);
216
217 impl IDispatcherImpl for Forward {
218 fn current_thread_is_loop_thread(&self) -> bool {
219 self.0.current_thread_is_loop_thread()
220 }
221 fn signal(&self) {
222 self.0.signal()
223 }
224 fn signal_handle(&self) -> std::sync::Arc<dyn crate::threading::IDispatcherSignal> {
225 self.0.signal_handle()
226 }
227 fn signaled(&self) -> &DispatcherImplEvent {
228 self.0.signaled()
229 }
230 fn timer(&self) -> &DispatcherImplEvent {
231 self.0.timer()
232 }
233 fn now(&self) -> i64 {
234 self.0.now()
235 }
236 fn update_timer(&self, due_time_in_ms: Option<i64>) {
237 self.0.update_timer(due_time_in_ms)
238 }
239 }
240
241 impl IDispatcherImpl for ManualDispatcherImpl {
242 fn current_thread_is_loop_thread(&self) -> bool {
243 thread::current().id() == self.loop_thread
244 }
245
246 fn signal(&self) {}
247
248 fn signal_handle(&self) -> std::sync::Arc<dyn crate::threading::IDispatcherSignal> {
249 struct NoSignal;
250 impl crate::threading::IDispatcherSignal for NoSignal {
251 fn signal(&self) {}
252 }
253 std::sync::Arc::new(NoSignal)
254 }
255
256 fn signaled(&self) -> &DispatcherImplEvent {
257 &self.signaled
258 }
259
260 fn timer(&self) -> &DispatcherImplEvent {
261 &self.timer
262 }
263
264 fn now(&self) -> i64 {
265 self.now.load(Ordering::SeqCst)
266 }
267
268 fn update_timer(&self, due_time_in_ms: Option<i64>) {
269 *self.next_timer.lock().unwrap() = due_time_in_ms;
270 }
271 }
272
273 #[test]
274 fn ticks_on_the_dispatcher_with_the_dispatcher_clock() {
275 let _scope = Dispatcher::unit_test_scope();
276 let platform = ManualDispatcherImpl::new();
277 let dispatcher = Dispatcher::new(Some(std::rc::Rc::new(Forward(platform.clone()))));
278 platform.now.store(1000, Ordering::SeqCst);
279
280 let timer = UiThreadRenderTimer::new(50);
281 assert!(!timer.runs_in_background());
282 assert_eq!(timer.frames_per_second(), 50);
283
284 let ticks = Arc::new(Mutex::new(Vec::new()));
285 let t = ticks.clone();
286 let tick_now = platform.now.clone();
287 let work = Arc::new(AtomicI64::new(5));
288 let w = work.clone();
289 timer.set_tick(Some(Arc::new(move |time| {
290 t.lock().unwrap().push(time);
291 tick_now.fetch_add(w.load(Ordering::SeqCst), Ordering::SeqCst);
293 })));
294 assert_eq!(DispatcherTimer::active_timers_count(), 1);
295 assert!(ticks.lock().unwrap().is_empty());
296
297 dispatcher.run_jobs(None);
299 assert_eq!(*ticks.lock().unwrap(), [Duration::ZERO]);
300 assert_eq!(platform.due_time(), Some(1005 + 15));
302
303 dispatcher.run_jobs(None);
304 assert_eq!(ticks.lock().unwrap().len(), 1);
305
306 platform.fire_timer_at(1021);
307 dispatcher.run_jobs(None);
308 assert_eq!(*ticks.lock().unwrap(), [Duration::ZERO, Duration::from_millis(21)]);
309 assert_eq!(platform.due_time(), Some(1026 + 15));
310
311 work.store(100, Ordering::SeqCst);
313 platform.fire_timer_at(1042);
314 dispatcher.run_jobs(None);
315 assert_eq!(ticks.lock().unwrap().len(), 3);
316 assert_eq!(platform.due_time(), Some(1142 + 1));
317
318 timer.set_tick(None);
319 assert_eq!(DispatcherTimer::active_timers_count(), 0);
320 assert_eq!(platform.due_time(), None);
321 platform.fire_timer_at(2000);
322 dispatcher.run_jobs(None);
323 assert_eq!(ticks.lock().unwrap().len(), 3);
324
325 work.store(0, Ordering::SeqCst);
327 timer.set_tick(Some(Arc::new({
328 let t = ticks.clone();
329 move |time| t.lock().unwrap().push(time)
330 })));
331 dispatcher.run_jobs(None);
332 assert_eq!(ticks.lock().unwrap()[3], Duration::from_millis(1000));
333 timer.set_tick(None);
334 assert_eq!(DispatcherTimer::active_timers_count(), 0);
335 }
336
337 #[test]
338 fn can_be_stopped_from_another_thread() {
339 let _scope = Dispatcher::unit_test_scope();
340 let platform = ManualDispatcherImpl::new();
341 let dispatcher = Dispatcher::new(Some(std::rc::Rc::new(Forward(platform.clone()))));
342
343 let timer = Arc::new(UiThreadRenderTimer::new(60));
344 timer.set_tick(Some(Arc::new(|_| {})));
345 assert_eq!(DispatcherTimer::active_timers_count(), 1);
346
347 let background = timer.clone();
348 thread::spawn(move || background.set_tick(None)).join().unwrap();
349 assert!(timer.tick().is_none());
350
351 dispatcher.run_jobs(None);
353 assert_eq!(DispatcherTimer::active_timers_count(), 0);
354 }
355}