Skip to main content

ferroui_native/
ferro_native_render_timer.rs

1//! The render timer over the native display link.
2
3use crate::interop::*;
4use ferroui_base::rendering::{IRenderTimer, RenderTimerTick};
5use ferroui_microcom::ComPtr;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::{Arc, Mutex, PoisonError};
8use std::time::Instant;
9
10/// What the timer shares with the callback the display link invokes.
11struct Shared {
12    stopwatch: Instant,
13    tick: Mutex<Option<RenderTimerTick>>,
14}
15
16/// The callback registered with the native timer; runs on the display link
17/// thread.
18struct TickCallback(Arc<Shared>);
19
20impl IFrnActionCallbackImpl for TickCallback {
21    fn run(&self) {
22        crate::callback_base::guard((), || {
23            let tick = self.0.tick.lock().unwrap_or_else(PoisonError::into_inner).clone();
24            if let Some(tick) = tick {
25                tick(self.0.stopwatch.elapsed());
26            }
27        })
28    }
29}
30
31/// A render timer that ticks with the display refresh.
32pub struct FerroNativeRenderTimer {
33    platform_render_timer: ComPtr<IFrnPlatformRenderTimer>,
34    shared: Arc<Shared>,
35    registered: AtomicBool,
36}
37
38// SAFETY: the render loop changes the tick from whichever thread adds or
39// removes its last task, never concurrently. The native timer only wraps a
40// display link: starting, stopping and querying it are thread-safe Core Video
41// calls, and reference counting of native objects is atomic.
42unsafe impl Send for FerroNativeRenderTimer {}
43// SAFETY: see `Send`.
44unsafe impl Sync for FerroNativeRenderTimer {}
45
46impl FerroNativeRenderTimer {
47    pub fn new(platform_render_timer: ComPtr<IFrnPlatformRenderTimer>) -> Self {
48        Self {
49            platform_render_timer,
50            shared: Arc::new(Shared { stopwatch: Instant::now(), tick: Mutex::new(None) }),
51            registered: AtomicBool::new(false),
52        }
53    }
54
55    fn ensure_registered(&self) {
56        if !self.registered.swap(true, Ordering::SeqCst) {
57            let callback = IFrnActionCallback::from_impl(TickCallback(self.shared.clone()));
58            let registration_result = self.platform_render_timer.register_tick(Some(&callback));
59            if registration_result != 0 {
60                panic!(
61                    "The native backend was not able to start the render timer. Native error code is: {registration_result}"
62                );
63            }
64        }
65    }
66}
67
68impl IRenderTimer for FerroNativeRenderTimer {
69    fn tick(&self) -> Option<RenderTimerTick> {
70        self.shared.tick.lock().unwrap_or_else(PoisonError::into_inner).clone()
71    }
72
73    fn set_tick(&self, value: Option<RenderTimerTick>) {
74        match value {
75            Some(value) => {
76                *self.shared.tick.lock().unwrap_or_else(PoisonError::into_inner) = Some(value);
77                self.ensure_registered();
78                self.platform_render_timer.start();
79            }
80            None => {
81                self.platform_render_timer.stop();
82                *self.shared.tick.lock().unwrap_or_else(PoisonError::into_inner) = None;
83            }
84        }
85    }
86
87    fn runs_in_background(&self) -> bool {
88        self.platform_render_timer.runs_in_background()
89    }
90}