irondash_run_loop/platform/linux/
mod.rs

1mod sys;
2
3use std::{
4    cell::{Cell, RefCell},
5    collections::HashMap,
6    os::raw::c_uint,
7    rc::Rc,
8    time::Duration,
9};
10
11use sys::glib::*;
12
13use crate::RunLoop;
14
15use self::sys::libc;
16
17type SourceId = c_uint;
18
19pub type HandleType = usize;
20pub const INVALID_HANDLE: HandleType = 0;
21
22pub struct PlatformRunLoop {
23    context: ContextHolder,
24    main_loop: *mut GMainLoop,
25    next_handle: Cell<HandleType>,
26    timers: Rc<RefCell<HashMap<HandleType, SourceId>>>,
27}
28
29fn context_add_source<F>(context: *mut GMainContext, interval: Duration, func: F) -> SourceId
30where
31    F: FnMut() -> gboolean + 'static,
32{
33    unsafe extern "C" fn trampoline<F: FnMut() -> gboolean + 'static>(func: gpointer) -> gboolean {
34        let func: &RefCell<F> = &*(func as *const RefCell<F>);
35        (*func.borrow_mut())()
36    }
37
38    fn into_raw<F: FnMut() -> gboolean + 'static>(func: F) -> gpointer {
39        let func: Box<RefCell<F>> = Box::new(RefCell::new(func));
40        Box::into_raw(func) as gpointer
41    }
42
43    unsafe extern "C" fn destroy_closure<F: FnMut() -> gboolean + 'static>(ptr: gpointer) {
44        let _ = Box::<RefCell<F>>::from_raw(ptr as *mut _);
45    }
46
47    unsafe {
48        let source = g_timeout_source_new(interval.as_millis() as _);
49        g_source_set_callback(
50            source,
51            Some(trampoline::<F>),
52            into_raw(func),
53            Some(destroy_closure::<F>),
54        );
55        let id = g_source_attach(source, context);
56
57        g_source_unref(source);
58        id
59    }
60}
61
62fn context_invoke<F>(context: *mut GMainContext, func: F)
63where
64    F: FnOnce() + 'static,
65{
66    unsafe extern "C" fn trampoline<F: FnOnce() + 'static>(func: gpointer) -> gboolean {
67        let func: &mut Option<F> = &mut *(func as *mut Option<F>);
68        let func = func
69            .take()
70            .expect("MainContext::invoke() closure called multiple times");
71        func();
72        G_SOURCE_REMOVE
73    }
74    unsafe extern "C" fn destroy_closure<F: FnOnce() + 'static>(ptr: gpointer) {
75        let _ = Box::<Option<F>>::from_raw(ptr as *mut _);
76    }
77    let callback = Box::into_raw(Box::new(Some(func)));
78    unsafe {
79        g_main_context_invoke_full(
80            context,
81            0,
82            Some(trampoline::<F>),
83            callback as gpointer,
84            Some(destroy_closure::<F>),
85        )
86    }
87}
88
89fn context_remove_source(context: *mut GMainContext, source_id: SourceId) {
90    unsafe {
91        let source = g_main_context_find_source_by_id(context, source_id);
92        if !source.is_null() {
93            g_source_destroy(source);
94        }
95    }
96}
97
98static mut FIRST_THREAD: PlatformThreadId = 0;
99
100fn is_main_thread() -> bool {
101    unsafe { FIRST_THREAD == get_system_thread_id() }
102}
103
104#[used]
105#[cfg_attr(
106    any(target_os = "linux", target_os = "android"),
107    link_section = ".init_array"
108)]
109static ON_LOAD: extern "C" fn() = {
110    #[cfg_attr(
111        any(target_os = "linux", target_os = "android"),
112        link_section = ".text.startup"
113    )]
114    extern "C" fn on_load() {
115        unsafe { FIRST_THREAD = get_system_thread_id() };
116    }
117    on_load
118};
119
120#[allow(unused_variables)]
121impl PlatformRunLoop {
122    pub fn new() -> Self {
123        let context = unsafe {
124            let default_context = g_main_context_default();
125            if g_main_context_is_owner(default_context) == GTRUE {
126                ContextHolder::retain(default_context)
127            } else {
128                let thread_context = g_main_context_get_thread_default();
129                if !thread_context.is_null() {
130                    ContextHolder::retain(thread_context)
131                } else if is_main_thread() {
132                    ContextHolder::retain(default_context)
133                } else {
134                    ContextHolder::adopt(g_main_context_new())
135                }
136            }
137        };
138        unsafe { g_main_context_push_thread_default(context.0) };
139        let main_loop = unsafe { g_main_loop_new(context.0, GFALSE) };
140        Self {
141            context,
142            next_handle: Cell::new(INVALID_HANDLE + 1),
143            timers: Rc::new(RefCell::new(HashMap::new())),
144            main_loop,
145        }
146    }
147
148    pub fn unschedule(&self, handle: HandleType) {
149        let source = self.timers.borrow_mut().remove(&handle);
150        if let Some(source) = source {
151            context_remove_source(self.context.0, source);
152        }
153    }
154
155    fn next_handle(&self) -> HandleType {
156        let r = self.next_handle.get();
157        self.next_handle.replace(r + 1);
158        r
159    }
160
161    #[must_use]
162    pub fn schedule<F>(&self, in_time: Duration, callback: F) -> HandleType
163    where
164        F: FnOnce() + 'static,
165    {
166        let callback = Rc::new(RefCell::new(Some(callback)));
167        let handle = self.next_handle();
168
169        let timers = self.timers.clone();
170
171        let source_id = context_add_source(self.context.0, in_time, move || {
172            timers.borrow_mut().remove(&handle);
173            let f = callback
174                .borrow_mut()
175                .take()
176                .expect("Timer callback was called multiple times");
177            f();
178            G_SOURCE_REMOVE
179        });
180        self.timers.borrow_mut().insert(handle, source_id);
181        handle
182    }
183
184    pub fn run(&self) {
185        unsafe { g_main_loop_run(self.main_loop) };
186    }
187
188    pub fn stop(&self) {
189        unsafe { g_main_loop_quit(self.main_loop) };
190    }
191
192    pub fn run_app(&self) {
193        unsafe { gtk_main() };
194    }
195
196    pub fn stop_app(&self) {
197        unsafe { gtk_main_quit() };
198    }
199
200    pub fn poll_once(&self) {
201        unsafe { gtk_main_iteration() };
202    }
203
204    pub fn is_main_thread() -> bool {
205        unsafe { g_main_context_is_owner(g_main_context_default()) == GTRUE }
206    }
207
208    pub fn new_sender(self: &Rc<Self>) -> PlatformRunLoopSender {
209        PlatformRunLoopSender::new(self.context.clone())
210    }
211}
212
213impl Drop for PlatformRunLoop {
214    fn drop(&mut self) {
215        unsafe {
216            g_main_context_pop_thread_default(self.context.0);
217            g_main_loop_unref(self.main_loop);
218        }
219    }
220}
221
222struct ContextHolder(*mut GMainContext);
223
224unsafe impl Send for ContextHolder {}
225unsafe impl Sync for ContextHolder {}
226
227impl ContextHolder {
228    unsafe fn retain(context: *mut GMainContext) -> Self {
229        Self(g_main_context_ref(context))
230    }
231    unsafe fn adopt(context: *mut GMainContext) -> Self {
232        Self(context)
233    }
234}
235
236impl Clone for ContextHolder {
237    fn clone(&self) -> Self {
238        Self(unsafe { g_main_context_ref(self.0) })
239    }
240}
241
242impl Drop for ContextHolder {
243    fn drop(&mut self) {
244        unsafe { g_main_context_unref(self.0) };
245    }
246}
247
248#[derive(Clone)]
249pub struct PlatformRunLoopSender {
250    context: ContextHolder,
251    thread_id: PlatformThreadId,
252}
253
254#[allow(unused_variables)]
255impl PlatformRunLoopSender {
256    fn new(context: ContextHolder) -> Self {
257        Self {
258            context,
259            thread_id: get_system_thread_id(),
260        }
261    }
262
263    pub fn send<F>(&self, callback: F) -> bool
264    where
265        F: FnOnce() + 'static + Send,
266    {
267        // This is to ensure consistent behavior on all platforms. When invoked on main thread
268        // the code below (g_main_context_invoke_full) would call the function synchronously,
269        // which is not expected and may lead to deadlocks.
270        if get_system_thread_id() == self.thread_id {
271            assert!(unsafe { g_main_context_is_owner(self.context.0) == GTRUE });
272            let run_loop = RunLoop::current();
273            run_loop.schedule(Duration::from_secs(0), callback).detach();
274            return true;
275        }
276
277        context_invoke(self.context.0, callback);
278
279        true
280    }
281}
282
283pub(crate) type PlatformThreadId = usize;
284
285pub(crate) fn get_system_thread_id() -> PlatformThreadId {
286    unsafe { libc::pthread_self() }
287}