1use std::any::Any;
2use std::panic::{catch_unwind, AssertUnwindSafe};
3use std::sync::atomic::{AtomicBool, Ordering};
4use std::sync::{Arc, Mutex, MutexGuard, PoisonError, Weak};
5use std::time::Duration;
6
7use super::{IRenderLoop, IRenderLoopTask, IRenderTimer, RenderTimerTick};
8use crate::logging::{LogArea, LogEventLevel, Logger};
9use crate::threading::Dispatcher;
10
11pub struct RenderLoop;
13
14impl RenderLoop {
15 pub fn from_timer(timer: Arc<dyn IRenderTimer>) -> Arc<dyn IRenderLoop> {
17 DefaultRenderLoop::new(timer)
18 }
19}
20
21#[derive(Default)]
23struct TimerState {
24 running: bool,
25 wakeup_pending: bool,
26}
27
28pub struct DefaultRenderLoop {
36 items: Mutex<Vec<Arc<dyn IRenderLoopTask>>>,
37 items_copy: Mutex<Vec<Arc<dyn IRenderLoopTask>>>,
39 tick: RenderTimerTick,
40 timer: Arc<dyn IRenderTimer>,
41 timer_lock: Mutex<TimerState>,
42 in_tick: AtomicBool,
43 has_items: AtomicBool,
44}
45
46fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
47 mutex.lock().unwrap_or_else(PoisonError::into_inner)
48}
49
50fn same_task(a: &Arc<dyn IRenderLoopTask>, b: &Arc<dyn IRenderLoopTask>) -> bool {
51 std::ptr::eq(Arc::as_ptr(a) as *const (), Arc::as_ptr(b) as *const ())
53}
54
55fn panic_message(payload: &(dyn Any + Send)) -> &str {
56 if let Some(message) = payload.downcast_ref::<&'static str>() {
57 message
58 } else if let Some(message) = payload.downcast_ref::<String>() {
59 message
60 } else {
61 "unknown error"
62 }
63}
64
65struct InTickGuard<'a>(&'a AtomicBool);
67
68impl Drop for InTickGuard<'_> {
69 fn drop(&mut self) {
70 self.0.store(false, Ordering::SeqCst);
71 }
72}
73
74impl DefaultRenderLoop {
75 pub fn new(timer: Arc<dyn IRenderTimer>) -> Arc<DefaultRenderLoop> {
77 Arc::new_cyclic(|weak_self: &Weak<DefaultRenderLoop>| {
78 let this = weak_self.clone();
79 DefaultRenderLoop {
80 items: Mutex::new(Vec::new()),
81 items_copy: Mutex::new(Vec::new()),
82 tick: Arc::new(move |time| {
83 if let Some(this) = this.upgrade() {
84 this.timer_tick(time);
85 }
86 }),
87 timer,
88 timer_lock: Mutex::new(TimerState::default()),
89 in_tick: AtomicBool::new(false),
90 has_items: AtomicBool::new(false),
91 }
92 })
93 }
94
95 pub fn timer(&self) -> &Arc<dyn IRenderTimer> {
97 &self.timer
98 }
99
100 fn timer_tick(&self, _time: Duration) {
101 if self.in_tick.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_ok() {
102 let _in_tick = InTickGuard(&self.in_tick);
103
104 if let Err(error) = catch_unwind(AssertUnwindSafe(|| self.timer_tick_core())) {
105 if let Some(logger) = Logger::try_get(LogEventLevel::Error, LogArea::VISUAL) {
106 logger.log_with_values(
107 Some(self as &dyn Any),
108 "Exception in render loop: {Error}",
109 &[&panic_message(&*error)],
110 );
111 }
112 }
113 }
114 }
115
116 fn timer_tick_core(&self) {
117 {
121 let mut state = lock(&self.timer_lock);
122 if !state.running {
123 return;
124 }
125 state.wakeup_pending = false;
126 }
127
128 let mut items_copy = lock(&self.items_copy);
129 {
130 let items = lock(&self.items);
131 items_copy.clear();
132 items_copy.extend(items.iter().cloned());
133 }
134
135 let mut wants_next_tick = false;
136 for item in items_copy.iter() {
137 wants_next_tick |= item.render();
138 }
139
140 items_copy.clear();
141 drop(items_copy);
142
143 if !wants_next_tick {
144 let mut state = lock(&self.timer_lock);
145 if !state.running {
146 } else if state.wakeup_pending {
148 state.wakeup_pending = false;
149 } else {
150 state.running = false;
151 self.timer.set_tick(None);
152 }
153 }
154 }
155}
156
157impl IRenderLoop for DefaultRenderLoop {
158 fn add(&self, i: Arc<dyn IRenderLoopTask>) {
159 Dispatcher::ui_thread().verify_access();
160
161 let should_start = {
162 let mut items = lock(&self.items);
163 items.push(i);
164 items.len() == 1
165 };
166
167 if should_start {
168 self.has_items.store(true, Ordering::SeqCst);
169 self.wakeup();
170 }
171 }
172
173 fn remove(&self, i: &Arc<dyn IRenderLoopTask>) {
174 Dispatcher::ui_thread().verify_access();
175
176 let should_stop = {
177 let mut items = lock(&self.items);
178 if let Some(index) = items.iter().position(|item| same_task(item, i)) {
179 items.remove(index);
180 }
181 items.is_empty()
182 };
183
184 if should_stop {
185 self.has_items.store(false, Ordering::SeqCst);
186 let mut state = lock(&self.timer_lock);
187 if state.running {
188 state.running = false;
189 state.wakeup_pending = false;
190 self.timer.set_tick(None);
191 }
192 }
193 }
194
195 fn runs_in_background(&self) -> bool {
196 self.timer.runs_in_background()
197 }
198
199 fn wakeup(&self) {
200 let mut state = lock(&self.timer_lock);
201 if self.has_items.load(Ordering::SeqCst) && !state.running {
202 state.running = true;
203 self.timer.set_tick(Some(self.tick.clone()));
204 } else {
205 state.wakeup_pending = true;
206 }
207 }
208
209 fn request_frame_out_of_turn(&self) {
210 self.timer.request_tick_out_of_turn();
213 }
214}
215
216impl Drop for DefaultRenderLoop {
217 fn drop(&mut self) {
218 let state = self.timer_lock.get_mut().unwrap_or_else(PoisonError::into_inner);
222 if state.running {
223 state.running = false;
224 self.timer.set_tick(None);
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232 use std::sync::atomic::AtomicI32;
233
234 #[derive(Default)]
236 struct ManualRenderTimer {
237 tick: Mutex<Option<RenderTimerTick>>,
238 set_count: AtomicI32,
239 clear_count: AtomicI32,
240 out_of_turn_count: AtomicI32,
241 runs_in_background: bool,
242 }
243
244 impl ManualRenderTimer {
245 fn trigger_tick(&self) {
246 let tick = lock(&self.tick).clone();
247 if let Some(tick) = tick {
248 tick(Duration::ZERO);
249 }
250 }
251
252 fn is_running(&self) -> bool {
253 lock(&self.tick).is_some()
254 }
255 }
256
257 impl IRenderTimer for ManualRenderTimer {
258 fn tick(&self) -> Option<RenderTimerTick> {
259 lock(&self.tick).clone()
260 }
261
262 fn set_tick(&self, value: Option<RenderTimerTick>) {
263 match value {
264 Some(_) => self.set_count.fetch_add(1, Ordering::SeqCst),
265 None => self.clear_count.fetch_add(1, Ordering::SeqCst),
266 };
267 *lock(&self.tick) = value;
268 }
269
270 fn runs_in_background(&self) -> bool {
271 self.runs_in_background
272 }
273
274 fn request_tick_out_of_turn(&self) {
275 self.out_of_turn_count.fetch_add(1, Ordering::SeqCst);
276 }
277 }
278
279 type OnRender = Box<dyn Fn() + Send + Sync>;
280
281 #[derive(Default)]
282 struct Task {
283 renders: AtomicI32,
284 wants_next_tick: AtomicBool,
285 on_render: Mutex<Option<OnRender>>,
286 }
287
288 impl Task {
289 fn new(wants_next_tick: bool) -> Arc<Task> {
290 Arc::new(Task { wants_next_tick: AtomicBool::new(wants_next_tick), ..Task::default() })
291 }
292
293 fn renders(&self) -> i32 {
294 self.renders.load(Ordering::SeqCst)
295 }
296 }
297
298 impl IRenderLoopTask for Task {
299 fn render(&self) -> bool {
300 self.renders.fetch_add(1, Ordering::SeqCst);
301 if let Some(on_render) = &*lock(&self.on_render) {
302 on_render();
303 }
304 self.wants_next_tick.load(Ordering::SeqCst)
305 }
306 }
307
308 fn setup() -> (crate::threading::UnitTestDispatcherScope, Arc<ManualRenderTimer>, Arc<DefaultRenderLoop>) {
309 let scope = Dispatcher::unit_test_scope();
310 let timer = Arc::new(ManualRenderTimer::default());
311 let render_loop = DefaultRenderLoop::new(timer.clone());
312 (scope, timer, render_loop)
313 }
314
315 #[test]
316 fn from_timer_reports_where_the_timer_runs() {
317 let background = Arc::new(ManualRenderTimer { runs_in_background: true, ..Default::default() });
318 assert!(RenderLoop::from_timer(background).runs_in_background());
319 assert!(!RenderLoop::from_timer(Arc::new(ManualRenderTimer::default())).runs_in_background());
320 }
321
322 #[test]
323 fn a_frame_out_of_turn_is_asked_of_the_timer_and_not_rendered_by_the_caller() {
324 let (_scope, timer, render_loop) = setup();
326 let task = Task::new(true);
327 render_loop.add(task.clone());
328
329 render_loop.request_frame_out_of_turn();
330 assert_eq!(timer.out_of_turn_count.load(Ordering::SeqCst), 1);
331 assert_eq!(task.renders(), 0);
332 }
333
334 #[test]
335 fn timer_is_not_started_without_tasks() {
336 let (_scope, timer, render_loop) = setup();
337 assert!(!timer.is_running());
338 render_loop.wakeup();
339 assert!(!timer.is_running());
340 assert_eq!(timer.set_count.load(Ordering::SeqCst), 0);
341 }
342
343 #[test]
344 fn first_task_starts_the_timer_and_last_removal_stops_it() {
345 let (_scope, timer, render_loop) = setup();
346 let first = Task::new(true);
347 let second = Task::new(true);
348 let first_handle: Arc<dyn IRenderLoopTask> = first.clone();
349 let second_handle: Arc<dyn IRenderLoopTask> = second.clone();
350
351 render_loop.add(first_handle.clone());
352 assert!(timer.is_running());
353 render_loop.add(second_handle.clone());
354 assert_eq!(timer.set_count.load(Ordering::SeqCst), 1);
355
356 timer.trigger_tick();
357 assert_eq!((first.renders(), second.renders()), (1, 1));
358
359 render_loop.remove(&first_handle);
360 assert!(timer.is_running());
361 timer.trigger_tick();
362 assert_eq!((first.renders(), second.renders()), (1, 2));
363
364 render_loop.remove(&first_handle);
366 assert!(timer.is_running());
367
368 render_loop.remove(&second_handle);
369 assert!(!timer.is_running());
370 assert_eq!(timer.clear_count.load(Ordering::SeqCst), 1);
371
372 render_loop.add(first_handle);
374 assert!(timer.is_running());
375 assert_eq!(timer.set_count.load(Ordering::SeqCst), 2);
376 }
377
378 #[test]
379 fn timer_keeps_running_while_a_task_wants_the_next_tick() {
380 let (_scope, timer, render_loop) = setup();
381 let idle = Task::new(false);
382 let busy = Task::new(true);
383 render_loop.add(idle.clone());
384 render_loop.add(busy.clone());
385
386 timer.trigger_tick();
387 timer.trigger_tick();
388 assert!(timer.is_running());
389 assert_eq!((idle.renders(), busy.renders()), (2, 2));
390
391 busy.wants_next_tick.store(false, Ordering::SeqCst);
392 timer.trigger_tick();
393 assert!(!timer.is_running());
394 assert_eq!((idle.renders(), busy.renders()), (3, 3));
395 }
396
397 #[test]
398 fn timer_stops_when_no_task_wants_the_next_tick_and_wakeup_restarts_it() {
399 let (_scope, timer, render_loop) = setup();
400 let task = Task::new(false);
401 render_loop.add(task.clone());
402 assert!(timer.is_running());
403
404 timer.trigger_tick();
405 assert_eq!(task.renders(), 1);
406 assert!(!timer.is_running());
407 assert_eq!(timer.clear_count.load(Ordering::SeqCst), 1);
408
409 render_loop.wakeup();
410 assert!(timer.is_running());
411 assert_eq!(timer.set_count.load(Ordering::SeqCst), 2);
412 timer.trigger_tick();
413 assert_eq!(task.renders(), 2);
414 assert!(!timer.is_running());
415 }
416
417 #[test]
418 fn wakeup_before_a_tick_is_consumed_by_that_tick() {
419 let (_scope, timer, render_loop) = setup();
420 let task = Task::new(false);
421 render_loop.add(task.clone());
422
423 render_loop.wakeup();
425 timer.trigger_tick();
426 assert!(!timer.is_running());
427 }
428
429 #[test]
430 fn wakeup_during_a_tick_keeps_the_timer_running_for_one_more_tick() {
431 let (_scope, timer, render_loop) = setup();
432 let task = Task::new(false);
433 let weak_loop = Arc::downgrade(&render_loop);
434 let wake = Arc::new(AtomicBool::new(true));
435 let w = wake.clone();
436 *lock(&task.on_render) = Some(Box::new(move || {
437 if w.load(Ordering::SeqCst) {
438 weak_loop.upgrade().unwrap().wakeup();
439 }
440 }));
441 render_loop.add(task.clone());
442
443 timer.trigger_tick();
444 assert!(timer.is_running());
445 assert_eq!(timer.set_count.load(Ordering::SeqCst), 1);
447
448 wake.store(false, Ordering::SeqCst);
449 timer.trigger_tick();
450 assert!(!timer.is_running());
451 assert_eq!(task.renders(), 2);
452 }
453
454 #[test]
455 fn late_ticks_after_the_timer_was_stopped_are_dropped() {
456 let (_scope, timer, render_loop) = setup();
457 let task = Task::new(true);
458 let handle: Arc<dyn IRenderLoopTask> = task.clone();
459 render_loop.add(handle.clone());
460 let late_tick = timer.tick().unwrap();
461 render_loop.remove(&handle);
462 assert!(!timer.is_running());
463
464 late_tick(Duration::ZERO);
465 assert_eq!(task.renders(), 0);
466 }
467
468 #[test]
469 fn tick_is_not_reentrant() {
470 let (_scope, timer, render_loop) = setup();
471 let task = Task::new(true);
472 let nested_timer = timer.clone();
473 *lock(&task.on_render) = Some(Box::new(move || nested_timer.trigger_tick()));
474 render_loop.add(task.clone());
475
476 timer.trigger_tick();
477 assert_eq!(task.renders(), 1);
478 timer.trigger_tick();
479 assert_eq!(task.renders(), 2);
480 }
481
482 #[test]
483 fn tick_is_skipped_while_another_thread_is_ticking() {
484 let (_scope, timer, render_loop) = setup();
485 let task = Task::new(true);
486 let (entered_tx, entered_rx) = std::sync::mpsc::channel::<()>();
487 let (release_tx, release_rx) = std::sync::mpsc::channel::<()>();
488 let entered_tx = Mutex::new(entered_tx);
489 let release_rx = Mutex::new(release_rx);
490 let block = Arc::new(AtomicBool::new(true));
491 let b = block.clone();
492 *lock(&task.on_render) = Some(Box::new(move || {
493 if b.swap(false, Ordering::SeqCst) {
494 lock(&entered_tx).send(()).unwrap();
495 lock(&release_rx).recv().unwrap();
496 }
497 }));
498 render_loop.add(task.clone());
499
500 let background_timer = timer.clone();
501 let background = std::thread::spawn(move || background_timer.trigger_tick());
502 entered_rx.recv().unwrap();
503
504 timer.trigger_tick();
506 assert_eq!(task.renders(), 1);
507
508 release_tx.send(()).unwrap();
509 background.join().unwrap();
510 timer.trigger_tick();
511 assert_eq!(task.renders(), 2);
512 }
513
514 #[test]
515 fn tasks_can_be_removed_while_rendering() {
516 let (_scope, timer, render_loop) = setup();
517 let first = Task::new(true);
518 let second = Task::new(true);
519 let second_handle: Arc<dyn IRenderLoopTask> = second.clone();
520 let weak_loop = Arc::downgrade(&render_loop);
521 let to_remove = second_handle.clone();
522 *lock(&first.on_render) = Some(Box::new(move || weak_loop.upgrade().unwrap().remove(&to_remove)));
523 render_loop.add(first.clone());
524 render_loop.add(second_handle);
525
526 timer.trigger_tick();
529 assert_eq!((first.renders(), second.renders()), (1, 1));
530 timer.trigger_tick();
531 assert_eq!((first.renders(), second.renders()), (2, 1));
532 }
533
534 #[test]
535 fn a_panicking_task_does_not_break_the_loop() {
536 let (_scope, timer, render_loop) = setup();
537 let task = Task::new(true);
538 let fail = Arc::new(AtomicBool::new(true));
539 let f = fail.clone();
540 *lock(&task.on_render) = Some(Box::new(move || {
541 if f.swap(false, Ordering::SeqCst) {
542 panic!("render failed");
543 }
544 }));
545 render_loop.add(task.clone());
546
547 timer.trigger_tick();
548 assert_eq!(task.renders(), 1);
549 assert!(timer.is_running());
550
551 timer.trigger_tick();
552 assert_eq!(task.renders(), 2);
553 }
554
555 #[test]
556 fn dropping_a_running_loop_stops_the_timer() {
557 let (_scope, timer, render_loop) = setup();
558 render_loop.add(Task::new(true));
559 assert!(timer.is_running());
560 drop(render_loop);
561 assert!(!timer.is_running());
562 }
563}