sdl3_main/
main_thread.rs

1use core::{
2    cell::UnsafeCell,
3    ffi::c_void,
4    marker::PhantomData,
5    mem::{align_of, size_of, ManuallyDrop, MaybeUninit},
6    ptr::{self, addr_of_mut},
7    sync::atomic::{AtomicBool, Ordering},
8};
9use sdl3_sys::{
10    init::SDL_RunOnMainThread,
11    stdinc::{SDL_aligned_alloc, SDL_aligned_free},
12    thread::{SDL_GetCurrentThreadID, SDL_ThreadID},
13};
14
15#[cfg(doc)]
16use crate::{app_init, main};
17
18/// Zero sized token that can only exist on the main thread.
19///
20/// Call [`MainThreadToken::get()`] or [`MainThreadToken::assert()`] to get one.
21#[derive(Clone, Copy)]
22pub struct MainThreadToken(PhantomData<*const ()>);
23
24static MAIN_THREAD_ID: MainOnceLock<SDL_ThreadID> = MainOnceLock::new();
25
26impl MainThreadToken {
27    /// Get `Some(MainThreadToken)` if called on the main thread, or `None` otherwise.
28    /// Returns `None` if `MainThreadToken` hasn't been inited.
29    ///
30    /// On targets that don't support threads, this will always succeed if `MainThreadToken`
31    /// has been inited.
32    ///
33    /// See also [`MainThreadToken::assert()`]
34    pub fn get() -> Option<Self> {
35        MAIN_THREAD_ID.get().and_then(|tid| {
36            (*tid == unsafe { SDL_GetCurrentThreadID() }).then_some(Self(PhantomData))
37        })
38    }
39
40    /// Get `MainThreadToken` if called on the main thread, or panic otherwise.
41    /// Panics if `MainThreadToken` hasn't been inited.
42    ///
43    /// On targets that don't support threads, this will always succeed if `MainThreadToken`
44    /// has been inited.
45    ///
46    /// See also [`MainThreadToken::get()`]
47    pub fn assert() -> Self {
48        Self::get().expect("This operation can only be performed on the main thread")
49    }
50
51    /// Init and declare the current thread as the main thread.
52    ///
53    /// This doesn't change what's the actual main thread, and it's UB to call this on
54    /// any thread other than the main thread. [`MainThreadToken::get()`] and
55    /// [`MainThreadToken::assert()`] will fail on any thread including the main thread
56    /// before this function is called.
57    ///
58    /// The [`main`] and [`app_init`] macros will call this for you.
59    ///
60    /// # Safety
61    /// It's undefined behaviour to call this on any thread other than the main thread.
62    /// On targets that don't support threads, this is always safe.
63    pub unsafe fn init() {
64        unsafe { MAIN_THREAD_ID.set_once(SDL_GetCurrentThreadID()) };
65    }
66}
67
68/// Data that can only be accessed from the main thread. Accessors take a [`MainThreadToken`].
69///
70/// This can be moved freely between threads, but the Drop implementation will panic if it's
71/// dropped from a thread other than the main thread.
72#[repr(transparent)]
73pub struct MainThreadData<T>(T);
74
75unsafe impl<T> Send for MainThreadData<T> {}
76unsafe impl<T> Sync for MainThreadData<T> {}
77
78impl<T> Drop for MainThreadData<T> {
79    fn drop(&mut self) {
80        MainThreadToken::assert();
81    }
82}
83
84impl<T> MainThreadData<T> {
85    /// Create a new `MainThreadData`.
86    #[inline(always)]
87    pub fn new(_: MainThreadToken, data: T) -> Self {
88        Self(data)
89    }
90
91    /// Get shared access to this data.
92    #[inline(always)]
93    pub fn get(&self, _: MainThreadToken) -> &T {
94        &self.0
95    }
96
97    /// Get exclusive access to this data.
98    #[inline(always)]
99    pub fn get_mut(&mut self, _: MainThreadToken) -> &mut T {
100        &mut self.0
101    }
102
103    /// Get shared access to this data in a callback that's run on the main thread.
104    /// This method waits for the callback to complete before returning.
105    ///
106    /// If this is called on a thread other than the main thread, it requires the SDL
107    /// event loop to run. See [`SDL_RunOnMainThread`] for details.
108    ///
109    /// Returns false if the callback failed to run.
110    ///
111    /// See also [`run_sync_on_main_thread()`].
112    #[must_use]
113    #[inline(always)]
114    pub fn get_on_main_thread(&self, callback: impl FnOnce(&T) + Send) -> bool {
115        run_sync_on_main_thread(move || callback(&self.0))
116    }
117
118    /// Get exclusive access to this data in a callback that's run on the main thread.
119    /// This method waits for the callback to complete before returning.
120    ///
121    /// If this is called on a thread other than the main thread, it requires the SDL
122    /// event loop to run. See [`SDL_RunOnMainThread`] for details.
123    ///
124    /// Returns false if the callback failed to run.
125    ///
126    /// See also [`run_sync_on_main_thread()`].
127    #[must_use]
128    #[inline(always)]
129    pub fn get_mut_on_main_thread(&mut self, callback: impl FnOnce(&mut T) + Send) -> bool {
130        run_sync_on_main_thread(move || callback(&mut self.0))
131    }
132}
133
134struct CallOnceContainer<F>(Option<F>);
135
136trait CallOnce {
137    fn call_once(&mut self);
138    fn discard(&mut self);
139}
140
141impl<F: FnOnce()> CallOnce for CallOnceContainer<F> {
142    fn call_once(&mut self) {
143        if let Some(f) = self.0.take() {
144            f();
145        }
146    }
147
148    fn discard(&mut self) {
149        self.0.take();
150    }
151}
152
153#[repr(transparent)]
154struct MainThreadCallHeader(*mut dyn CallOnce);
155
156#[repr(C)]
157struct MainThreadCall<T> {
158    header: MainThreadCallHeader,
159    data: T,
160}
161
162/// Run a callback on the main thread and wait for it to complete before returning.
163///
164/// If this is called on a thread other than the main thread, it requires the SDL
165/// event loop to run. See [`SDL_RunOnMainThread`] for details.
166///
167/// Returns false if the callback failed to run.
168///
169/// See also:
170/// - [`run_async_on_main_thread()`]
171/// - [`MainThreadData::get_on_main_thread()`]
172#[must_use]
173pub fn run_sync_on_main_thread<F: FnOnce() + Send>(callback: F) -> bool {
174    unsafe extern "C" fn main_thread_fn(userdata: *mut c_void) {
175        let call_once = unsafe { &mut *(userdata as *mut &mut dyn CallOnce) };
176        call_once.call_once();
177    }
178    let mut f = CallOnceContainer(Some(callback));
179    let mut f: &mut dyn CallOnce = &mut f;
180    let f = &mut f as *mut &mut dyn CallOnce as *mut c_void;
181    unsafe { SDL_RunOnMainThread(Some(main_thread_fn), f, true) }
182}
183
184/// Schedule a callback to run on the main thread and immediately return without waiting for it.
185///
186/// If this is called on a thread other than the main thread, it requires the SDL
187/// event loop to run. See [`SDL_RunOnMainThread`] for details.
188///
189/// Returns false if the callback failed to run.
190///
191/// See also [`run_sync_on_main_thread()`].
192#[must_use]
193pub fn run_async_on_main_thread<F: FnOnce() + Send + 'static>(callback: F) -> bool {
194    // we can't copy uninit bytes such as padding, because that's unsound,
195    // and we can't know there's no uninit bytes unless the size is zero
196    if const { size_of::<F>() == 0 } {
197        // callback is zero sized; we don't need to allocate
198        unsafe extern "C" fn main_thread_fn<F: FnOnce() + Send + 'static>(userdata: *mut c_void) {
199            unsafe { (userdata as *mut F).read()() }
200        }
201        let callback = ManuallyDrop::new(callback);
202        let userdata: *mut F = ptr::dangling_mut();
203        if unsafe { SDL_RunOnMainThread(Some(main_thread_fn::<F>), userdata as *mut c_void, false) }
204        {
205            true
206        } else {
207            let _ = ManuallyDrop::into_inner(callback);
208            false
209        }
210    } else {
211        // have to allocate for callback
212        unsafe extern "C" fn main_thread_fn(userdata: *mut c_void) {
213            defer!(unsafe { SDL_aligned_free(userdata) });
214            unsafe { &mut *((*(userdata as *mut MainThreadCallHeader)).0) }.call_once();
215        }
216        let f = CallOnceContainer(Some(callback));
217        let userdata = unsafe {
218            SDL_aligned_alloc(
219                align_of::<MainThreadCall<CallOnceContainer<F>>>(),
220                size_of::<MainThreadCall<CallOnceContainer<F>>>(),
221            )
222        } as *mut MainThreadCall<CallOnceContainer<F>>;
223        if userdata.is_null() {
224            return false;
225        }
226        let payload = unsafe { addr_of_mut!((*userdata).data) };
227        unsafe {
228            addr_of_mut!((*userdata).header.0).write(payload as *mut dyn CallOnce);
229            (payload as *mut ManuallyDrop<CallOnceContainer<F>>).write(ManuallyDrop::new(f));
230        }
231        let userdata = userdata as *mut c_void;
232        if unsafe { SDL_RunOnMainThread(Some(main_thread_fn), userdata, false) } {
233            true
234        } else {
235            defer!(unsafe { SDL_aligned_free(userdata) });
236            unsafe { &mut *((*(userdata as *mut MainThreadCallHeader)).0) }.discard();
237            false
238        }
239    }
240}
241
242#[repr(transparent)]
243struct SyncUnsafeCell<T: ?Sized>(UnsafeCell<T>);
244
245unsafe impl<T: ?Sized + Sync> Sync for SyncUnsafeCell<T> {}
246
247// OnceLock-ish struct that works with no_std
248// (Copy for !Drop)
249struct MainOnceLock<T: Copy> {
250    is_set: AtomicBool,
251    data: SyncUnsafeCell<MaybeUninit<T>>,
252}
253
254impl<T: Copy> MainOnceLock<T> {
255    const fn new() -> Self {
256        Self {
257            is_set: AtomicBool::new(false),
258            data: SyncUnsafeCell(UnsafeCell::new(MaybeUninit::uninit())),
259        }
260    }
261
262    fn get(&self) -> Option<&T> {
263        self.is_set
264            .load(Ordering::Acquire)
265            .then(|| unsafe { (*self.data.0.get()).assume_init_ref() })
266    }
267
268    /// # Safety
269    /// This does *not* protect against a data race if called concurrently from multiple threads
270    unsafe fn set_once(&self, data: T) {
271        if !self.is_set.load(Ordering::Relaxed) {
272            unsafe { self.data.0.get().write(MaybeUninit::new(data)) };
273            self.is_set.store(true, Ordering::Release);
274        }
275    }
276}