Skip to main content

ferroui_native/
dispatcher_impl.rs

1//! The dispatcher platform implementation over the native run loop.
2
3use crate::interop::*;
4use ferroui_base::threading::{
5    CancellationToken, CancellationTokenSource, DispatcherImplEvent, IControlledDispatcherImpl, IDispatcherImpl,
6    IDispatcherImplWithExplicitBackgroundProcessing, IDispatcherImplWithPendingInput, IDispatcherSignal,
7};
8use ferroui_microcom::ComPtr;
9use std::any::Any;
10use std::cell::{Cell, RefCell};
11use std::panic::resume_unwind;
12use std::rc::{Rc, Weak};
13use std::sync::{Arc, Mutex, PoisonError};
14use std::thread::{self, ThreadId};
15use std::time::Instant;
16
17ferroui_base::ferro_thread_local! {
18    /// The dispatcher implementation that belongs to this thread; what
19    /// callbacks hand their panics to.
20    static CURRENT: RefCell<Weak<DispatcherImpl>> = const { RefCell::new(Weak::new()) };
21}
22
23/// The cross-thread wake-up handle.
24struct NativeSignal(ComPtr<IFrnPlatformThreadingInterface>);
25
26// SAFETY: the only member used through this handle is `Signal`, which the
27// native side implements for any thread (it takes a lock, queues a block on
28// the main queue and wakes the main run loop). Reference counting of native
29// objects is atomic.
30unsafe impl Send for NativeSignal {}
31// SAFETY: see `Send`.
32unsafe impl Sync for NativeSignal {}
33
34impl IDispatcherSignal for NativeSignal {
35    fn signal(&self) {
36        self.0.signal();
37    }
38}
39
40/// The native loop cancellation, shared with the cancellation callback that
41/// may run on any thread.
42struct LoopCancellation(Mutex<LoopCancellationState>);
43
44struct LoopCancellationState {
45    exited: bool,
46    cancel: Option<ComPtr<IFrnLoopCancellation>>,
47}
48
49// SAFETY: `IFrnLoopCancellation::Cancel` may be called from any thread (off
50// the main thread the native side re-posts itself to the main queue), and
51// the state is only touched under the mutex.
52unsafe impl Send for LoopCancellation {}
53// SAFETY: see `Send`.
54unsafe impl Sync for LoopCancellation {}
55
56impl LoopCancellation {
57    fn cancel(&self) {
58        let state = self.0.lock().unwrap_or_else(PoisonError::into_inner);
59        if !state.exited {
60            if let Some(cancel) = &state.cancel {
61                cancel.cancel();
62            }
63        }
64    }
65
66    /// Marks the loop as exited and hands the native object back so that it
67    /// is released on the loop thread.
68    fn exit(&self) -> Option<ComPtr<IFrnLoopCancellation>> {
69        let mut state = self.0.lock().unwrap_or_else(PoisonError::into_inner);
70        state.exited = true;
71        state.cancel.take()
72    }
73}
74
75/// One nested run of the native loop.
76struct RunLoopFrame {
77    exception: RefCell<Option<Box<dyn Any + Send>>>,
78    cancellation_token_source: CancellationTokenSource,
79}
80
81/// The dispatcher platform implementation of the macOS backend.
82pub struct DispatcherImpl {
83    native: ComPtr<IFrnPlatformThreadingInterface>,
84    signal: Arc<NativeSignal>,
85    loop_thread: Cell<Option<ThreadId>>,
86    clock: Instant,
87    managed_frames: RefCell<Vec<Rc<RunLoopFrame>>>,
88    signaled: DispatcherImplEvent,
89    timer: DispatcherImplEvent,
90    ready_for_background_processing: DispatcherImplEvent,
91}
92
93/// Receives the events of the native threading interface.
94struct Events {
95    parent: Weak<DispatcherImpl>,
96}
97
98impl IFrnPlatformThreadingInterfaceEventsImpl for Events {
99    fn signaled(&self) {
100        crate::callback_base::guard((), || {
101            if let Some(parent) = self.parent.upgrade() {
102                parent.signaled.raise();
103            }
104        })
105    }
106
107    fn timer(&self) {
108        crate::callback_base::guard((), || {
109            if let Some(parent) = self.parent.upgrade() {
110                parent.timer.raise();
111            }
112        })
113    }
114
115    fn ready_for_background_processing(&self) {
116        crate::callback_base::guard((), || {
117            if let Some(parent) = self.parent.upgrade() {
118                parent.ready_for_background_processing.raise();
119            }
120        })
121    }
122}
123
124impl DispatcherImpl {
125    /// Creates the implementation for the thread that owns `native` (the
126    /// calling thread) and subscribes to the native events.
127    pub fn new(native: ComPtr<IFrnPlatformThreadingInterface>) -> Rc<DispatcherImpl> {
128        let this = Rc::new(DispatcherImpl {
129            signal: Arc::new(NativeSignal(native.clone())),
130            native,
131            loop_thread: Cell::new(None),
132            clock: Instant::now(),
133            managed_frames: RefCell::new(Vec::new()),
134            signaled: DispatcherImplEvent::new(),
135            timer: DispatcherImplEvent::new(),
136            ready_for_background_processing: DispatcherImplEvent::new(),
137        });
138        let events = IFrnPlatformThreadingInterfaceEvents::from_impl(Events { parent: Rc::downgrade(&this) });
139        this.native.set_events(Some(&events));
140        CURRENT.with(|current| *current.borrow_mut() = Rc::downgrade(&this));
141        this
142    }
143
144    /// The implementation that belongs to the calling thread, if any.
145    pub(crate) fn current() -> Option<Rc<DispatcherImpl>> {
146        CURRENT.try_with(|current| current.borrow().upgrade()).ok().flatten()
147    }
148
149    /// The timer interval the native side is asked for: `-1` clears the
150    /// timer, otherwise the time left until `due_time_in_ms`, at least one
151    /// millisecond.
152    fn timer_interval(due_time_in_ms: Option<i64>, now: i64) -> i32 {
153        match due_time_in_ms {
154            None => -1,
155            Some(due_time) => (due_time - now).max(1).min((i32::MAX - 10) as i64) as i32,
156        }
157    }
158
159    /// Stops the innermost run loop and makes it re-raise `capture`, the
160    /// payload of a panic caught in a native callback.
161    pub fn propagate_callback_exception(&self, capture: Box<dyn Any + Send>) {
162        let frame = self.managed_frames.borrow().last().cloned();
163        let Some(frame) = frame else {
164            debug_assert!(false, "We should never get here");
165            return;
166        };
167
168        *frame.exception.borrow_mut() = Some(capture);
169        frame.cancellation_token_source.cancel();
170    }
171}
172
173impl IDispatcherImpl for DispatcherImpl {
174    fn current_thread_is_loop_thread(&self) -> bool {
175        if let Some(loop_thread) = self.loop_thread.get() {
176            return thread::current().id() == loop_thread;
177        }
178        if !self.native.get_current_thread_is_loop_thread() {
179            return false;
180        }
181        self.loop_thread.set(Some(thread::current().id()));
182        true
183    }
184
185    fn signal(&self) {
186        self.native.signal();
187    }
188
189    fn signal_handle(&self) -> Arc<dyn IDispatcherSignal> {
190        self.signal.clone()
191    }
192
193    fn signaled(&self) -> &DispatcherImplEvent {
194        &self.signaled
195    }
196
197    fn timer(&self) -> &DispatcherImplEvent {
198        &self.timer
199    }
200
201    fn now(&self) -> i64 {
202        self.clock.elapsed().as_millis() as i64
203    }
204
205    fn update_timer(&self, due_time_in_ms: Option<i64>) {
206        self.native.update_timer(Self::timer_interval(due_time_in_ms, self.now()));
207    }
208
209    fn as_pending_input(&self) -> Option<&dyn IDispatcherImplWithPendingInput> {
210        Some(self)
211    }
212
213    fn as_explicit_background_processing(&self) -> Option<&dyn IDispatcherImplWithExplicitBackgroundProcessing> {
214        Some(self)
215    }
216
217    fn as_controlled(&self) -> Option<&dyn IControlledDispatcherImpl> {
218        Some(self)
219    }
220}
221
222impl IDispatcherImplWithPendingInput for DispatcherImpl {
223    fn can_query_pending_input(&self) -> bool {
224        false
225    }
226
227    fn has_pending_input(&self) -> bool {
228        false
229    }
230}
231
232impl IDispatcherImplWithExplicitBackgroundProcessing for DispatcherImpl {
233    fn ready_for_background_processing(&self) -> &DispatcherImplEvent {
234        &self.ready_for_background_processing
235    }
236
237    fn request_background_processing(&self) {
238        self.native.request_background_processing();
239    }
240}
241
242impl IControlledDispatcherImpl for DispatcherImpl {
243    fn run_loop(&self, token: CancellationToken) {
244        if token.is_cancellation_requested() {
245            return;
246        }
247
248        // The frame's source is linked to `token` and can also be canceled
249        // by `propagate_callback_exception`.
250        let frame = Rc::new(RunLoopFrame {
251            exception: RefCell::new(None),
252            cancellation_token_source: CancellationTokenSource::new(),
253        });
254        let linked = frame.cancellation_token_source.clone();
255        let link_registration = token.register(move || linked.cancel());
256
257        let cancel = self.native.create_loop_cancellation();
258        let shared = Arc::new(LoopCancellation(Mutex::new(LoopCancellationState { exited: false, cancel: cancel.clone() })));
259        let on_cancel = shared.clone();
260        let cancel_registration = frame.cancellation_token_source.token().register(move || on_cancel.cancel());
261
262        self.managed_frames.borrow_mut().push(frame.clone());
263        self.native.run_loop(cancel.as_deref());
264
265        // finally
266        drop(shared.exit());
267        self.managed_frames.borrow_mut().pop();
268        cancel_registration.dispose();
269        link_registration.dispose();
270        let exception = frame.exception.borrow_mut().take();
271        if let Some(exception) = exception {
272            resume_unwind(exception);
273        }
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn timer_interval_matches_the_reference_clamping() {
283        assert_eq!(DispatcherImpl::timer_interval(None, 100), -1);
284        assert_eq!(DispatcherImpl::timer_interval(Some(150), 100), 50);
285        // Due or overdue timers fire as soon as possible, never "now".
286        assert_eq!(DispatcherImpl::timer_interval(Some(100), 100), 1);
287        assert_eq!(DispatcherImpl::timer_interval(Some(10), 100), 1);
288        assert_eq!(DispatcherImpl::timer_interval(Some(i64::MAX), 0), i32::MAX - 10);
289    }
290}