sdl3_main/
main_thread.rs

1use core::{
2    cell::Cell,
3    ffi::c_void,
4    marker::PhantomData,
5    mem::ManuallyDrop,
6    ptr::{addr_of_mut, NonNull},
7    sync::atomic::Ordering,
8};
9use sdl3_sys::{
10    init::{SDL_IsMainThread, SDL_RunOnMainThread},
11    stdinc::{SDL_aligned_alloc, SDL_aligned_free},
12    thread::{SDL_GetCurrentThreadID, SDL_ThreadID},
13    timer::SDL_Delay,
14};
15
16#[cfg(target_has_atomic = "8")]
17type AtomicLeastU8 = core::sync::atomic::AtomicU8;
18#[cfg(all(not(target_has_atomic = "8"), target_has_atomic = "16"))]
19type AtomicLeastU8 = core::sync::atomic::AtomicU16;
20#[cfg(all(
21    not(any(target_has_atomic = "8", target_has_atomic = "16")),
22    target_has_atomic = "32"
23))]
24type AtomicLeastU8 = core::sync::atomic::AtomicU32;
25#[cfg(all(
26    not(any(
27        target_has_atomic = "8",
28        target_has_atomic = "16",
29        target_has_atomic = "32"
30    )),
31    target_has_atomic = "64"
32))]
33type AtomicLeastU8 = core::sync::atomic::AtomicU64;
34#[cfg(all(
35    not(any(
36        target_has_atomic = "8",
37        target_has_atomic = "16",
38        target_has_atomic = "32",
39        target_has_atomic = "64"
40    )),
41    target_has_atomic = "128"
42))]
43type AtomicLeastU8 = core::sync::atomic::AtomicU128;
44#[cfg(not(any(
45    target_has_atomic = "8",
46    target_has_atomic = "16",
47    target_has_atomic = "32",
48    target_has_atomic = "64",
49    target_has_atomic = "128"
50)))]
51compile_error!("no supported atomic integer type");
52
53/// Zero sized token that can only exist on the main thread.
54///
55/// Call [`MainThreadToken::get()`] or [`MainThreadToken::assert()`] to get one.
56///
57/// As of `sdl3-main` 0.6, it's not required to call [`MainThreadToken::init()`].
58/// If SDL has been inited properly, only the thread that SDL considers the main thread
59/// can get a `MainThreadToken`. Otherwise, the first call to either of [`MainThreadToken::get()`]
60/// or [`MainThreadToken::assert()`] will determine which thread `MainThreadToken` considers
61/// the main thread for the lifetime of the process. [`MainThreadToken::init()`] is equivalent
62/// to [`MainThreadToken::assert()`] and is retained for backwards compatibility.
63#[derive(Clone, Copy)]
64pub struct MainThreadToken(PhantomData<*const ()>);
65
66impl MainThreadToken {
67    /// Get `Some(MainThreadToken)` if called on the main thread, or `None` otherwise.
68    ///
69    /// On targets that don't support threads, this will always succeed.
70    ///
71    /// See also [`MainThreadToken::assert()`]
72    pub fn get() -> Option<Self> {
73        struct ThreadId(Cell<SDL_ThreadID>);
74        unsafe impl Sync for ThreadId {}
75
76        static MAIN_THREAD_ID: ThreadId = ThreadId(Cell::new(SDL_ThreadID(0)));
77        static MAIN_THREAD_ID_STATUS: AtomicLeastU8 = AtomicLeastU8::new(0);
78
79        loop {
80            match MAIN_THREAD_ID_STATUS.load(Ordering::Acquire) {
81                0 => {
82                    // if this returns false we know it's not the main thread, but true may be
83                    // unreliable depending on how/if SDL was inited and the version of SDL
84                    if !unsafe { SDL_IsMainThread() } {
85                        return None;
86                    }
87                    if MAIN_THREAD_ID_STATUS
88                        .compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed)
89                        .is_ok()
90                    {
91                        MAIN_THREAD_ID.0.set(SDL_GetCurrentThreadID());
92                        MAIN_THREAD_ID_STATUS.store(2, Ordering::Release);
93                        return Some(Self(PhantomData));
94                    }
95                }
96                1 => (),
97                _ => {
98                    return (MAIN_THREAD_ID.0.get() == SDL_GetCurrentThreadID())
99                        .then_some(Self(PhantomData));
100                }
101            }
102            unsafe { SDL_Delay(0) }
103        }
104    }
105
106    /// Get `MainThreadToken` if called on the main thread, or panic otherwise.
107    ///
108    /// On targets that don't support threads, this will always succeed.
109    ///
110    /// See also [`MainThreadToken::get()`]
111    #[track_caller]
112    pub fn assert() -> Self {
113        Self::get().expect("This operation can only be performed on the main thread")
114    }
115
116    /// Initialize `MainThreadToken` (if it wasn't already) if called on the main thread,
117    /// or panic otherwise.
118    ///
119    /// As of `sdl3-main` 0.6, this function is equivalent to [`MainThreadToken::assert()`].
120    ///
121    /// On targets that don't support threads, this will always succeed.
122    #[track_caller]
123    #[inline(always)]
124    pub unsafe fn init() {
125        Self::assert();
126    }
127}
128
129/// Data that can only be accessed from the main thread. Accessors take a [`MainThreadToken`].
130///
131/// This can be moved freely between threads, but the Drop implementation will panic if it's
132/// dropped from a thread other than the main thread.
133#[repr(transparent)]
134pub struct MainThreadData<T>(T);
135
136unsafe impl<T> Send for MainThreadData<T> {}
137unsafe impl<T> Sync for MainThreadData<T> {}
138
139impl<T> Drop for MainThreadData<T> {
140    fn drop(&mut self) {
141        MainThreadToken::assert();
142    }
143}
144
145impl<T> MainThreadData<T> {
146    /// Create a new `MainThreadData`.
147    ///
148    /// See also [`Self::assert_new()`]
149    #[inline(always)]
150    pub fn new(_: MainThreadToken, data: T) -> Self {
151        Self(data)
152    }
153
154    /// Get shared access to this data.
155    ///
156    /// See also [`Self::assert_get()`], [`Self::get_on_main_thread()`]
157    #[inline(always)]
158    pub fn get(&self, _: MainThreadToken) -> &T {
159        &self.0
160    }
161
162    /// Get exclusive access to this data.
163    ///
164    /// See also [`Self::assert_get_mut()`], [`Self::get_mut_on_main_thread()`]
165    #[inline(always)]
166    pub fn get_mut(&mut self, _: MainThreadToken) -> &mut T {
167        &mut self.0
168    }
169
170    /// Create a new `MainThreadData`. Panic if not called on the main thread.
171    ///
172    /// See also [`Self::new()`]
173    #[track_caller]
174    #[inline(always)]
175    pub fn assert_new(data: T) -> Self {
176        Self::new(MainThreadToken::assert(), data)
177    }
178
179    /// Get shared access to this data. Panic if not called on the main thread.
180    ///
181    /// See also [`Self::get()`], [`Self::get_on_main_thread()`]
182    #[track_caller]
183    #[inline(always)]
184    pub fn assert_get(&self) -> &T {
185        self.get(MainThreadToken::assert())
186    }
187
188    /// Get shared access to this data. Panic if not called on the main thread.
189    ///
190    /// See also [`Self::get_mut()`], [`Self::get_mut_on_main_thread()`]
191    #[track_caller]
192    #[inline(always)]
193    pub fn assert_get_mut(&mut self) -> &mut T {
194        self.get_mut(MainThreadToken::assert())
195    }
196
197    /// Get shared access to this data in a callback that's run on the main thread.
198    /// This method waits for the callback to complete before returning.
199    ///
200    /// If this is called on a thread other than the main thread, it requires the SDL
201    /// event loop to run. See [`SDL_RunOnMainThread`] for details.
202    ///
203    /// Returns false if the callback failed to run.
204    ///
205    /// See also [`run_sync_on_main_thread()`], [`Self::get()`], [`Self::assert_get()`]
206    #[must_use]
207    #[inline(always)]
208    pub fn get_on_main_thread(&self, callback: impl FnOnce(&T) + Send) -> bool {
209        run_sync_on_main_thread(move || callback(&self.0))
210    }
211
212    /// Get exclusive access to this data in a callback that's run on the main thread.
213    /// This method waits for the callback to complete before returning.
214    ///
215    /// If this is called on a thread other than the main thread, it requires the SDL
216    /// event loop to run. See [`SDL_RunOnMainThread`] for details.
217    ///
218    /// Returns false if the callback failed to run.
219    ///
220    /// See also [`run_sync_on_main_thread()`], [`Self::get_mut()`], [`Self::assert_get_mut()`]
221    #[must_use]
222    #[inline(always)]
223    pub fn get_mut_on_main_thread(&mut self, callback: impl FnOnce(&mut T) + Send) -> bool {
224        run_sync_on_main_thread(move || callback(&mut self.0))
225    }
226}
227
228struct CallOnceContainer<F>(Option<F>);
229
230trait CallOnce {
231    fn call_once(&mut self);
232    fn discard(&mut self);
233}
234
235impl<F: FnOnce()> CallOnce for CallOnceContainer<F> {
236    fn call_once(&mut self) {
237        if let Some(f) = self.0.take() {
238            f();
239        }
240    }
241
242    fn discard(&mut self) {
243        self.0.take();
244    }
245}
246
247#[repr(transparent)]
248struct MainThreadCallHeader(*mut dyn CallOnce);
249
250#[repr(C)]
251struct MainThreadCall<T> {
252    header: MainThreadCallHeader,
253    data: T,
254}
255
256/// Run a callback on the main thread and wait for it to complete before returning.
257///
258/// If this is called on a thread other than the main thread, it requires the SDL
259/// event loop to run. See [`SDL_RunOnMainThread`] for details.
260///
261/// Returns false if the callback failed to run.
262///
263/// See also:
264/// - [`run_async_on_main_thread()`]
265/// - [`MainThreadData::get_on_main_thread()`]
266#[must_use]
267pub fn run_sync_on_main_thread<F: FnOnce() + Send>(callback: F) -> bool {
268    unsafe extern "C" fn main_thread_fn(userdata: *mut c_void) {
269        let call_once = unsafe { &mut *(userdata as *mut &mut dyn CallOnce) };
270        call_once.call_once();
271    }
272    let mut f = CallOnceContainer(Some(callback));
273    let mut f: &mut dyn CallOnce = &mut f;
274    let f = &mut f as *mut &mut dyn CallOnce as *mut c_void;
275    unsafe { SDL_RunOnMainThread(Some(main_thread_fn), f, true) }
276}
277
278/// Schedule a callback to run on the main thread and immediately return without waiting for it.
279///
280/// If this is called on a thread other than the main thread, it requires the SDL
281/// event loop to run. See [`SDL_RunOnMainThread`] for details.
282///
283/// Returns false if the callback failed to run.
284///
285/// See also [`run_sync_on_main_thread()`].
286#[must_use]
287pub fn run_async_on_main_thread<F: FnOnce() + Send + 'static>(callback: F) -> bool {
288    // we can't copy uninit bytes such as padding, because that's unsound,
289    // and we can't know there's no uninit bytes unless the size is zero
290    if const { size_of::<F>() == 0 } {
291        // callback is zero sized; we don't need to allocate
292        unsafe extern "C" fn main_thread_fn<F: FnOnce() + Send + 'static>(userdata: *mut c_void) {
293            unsafe { (userdata as *mut F).read()() }
294        }
295        let callback = ManuallyDrop::new(callback);
296        let userdata: *mut F = NonNull::<F>::dangling().as_ptr();
297        if unsafe { SDL_RunOnMainThread(Some(main_thread_fn::<F>), userdata as *mut c_void, false) }
298        {
299            true
300        } else {
301            let _ = ManuallyDrop::into_inner(callback);
302            false
303        }
304    } else {
305        // have to allocate for callback
306        unsafe extern "C" fn main_thread_fn(userdata: *mut c_void) {
307            defer!(unsafe { SDL_aligned_free(userdata) });
308            unsafe { &mut *((*(userdata as *mut MainThreadCallHeader)).0) }.call_once();
309        }
310        let f = CallOnceContainer(Some(callback));
311        let userdata = unsafe {
312            SDL_aligned_alloc(
313                align_of::<MainThreadCall<CallOnceContainer<F>>>(),
314                size_of::<MainThreadCall<CallOnceContainer<F>>>(),
315            )
316        } as *mut MainThreadCall<CallOnceContainer<F>>;
317        if userdata.is_null() {
318            return false;
319        }
320        let payload = unsafe { addr_of_mut!((*userdata).data) };
321        unsafe {
322            addr_of_mut!((*userdata).header.0).write(payload as *mut dyn CallOnce);
323            (payload as *mut ManuallyDrop<CallOnceContainer<F>>).write(ManuallyDrop::new(f));
324        }
325        let userdata = userdata as *mut c_void;
326        if unsafe { SDL_RunOnMainThread(Some(main_thread_fn), userdata, false) } {
327            true
328        } else {
329            defer!(unsafe { SDL_aligned_free(userdata) });
330            unsafe { &mut *((*(userdata as *mut MainThreadCallHeader)).0) }.discard();
331            false
332        }
333    }
334}