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#[derive(Clone, Copy)]
22pub struct MainThreadToken(PhantomData<*const ()>);
23
24static MAIN_THREAD_ID: MainOnceLock<SDL_ThreadID> = MainOnceLock::new();
25
26impl MainThreadToken {
27 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 pub fn assert() -> Self {
48 Self::get().expect("This operation can only be performed on the main thread")
49 }
50
51 pub unsafe fn init() {
64 unsafe { MAIN_THREAD_ID.set_once(SDL_GetCurrentThreadID()) };
65 }
66}
67
68#[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 #[inline(always)]
87 pub fn new(_: MainThreadToken, data: T) -> Self {
88 Self(data)
89 }
90
91 #[inline(always)]
93 pub fn get(&self, _: MainThreadToken) -> &T {
94 &self.0
95 }
96
97 #[inline(always)]
99 pub fn get_mut(&mut self, _: MainThreadToken) -> &mut T {
100 &mut self.0
101 }
102
103 #[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 #[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#[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#[must_use]
193pub fn run_async_on_main_thread<F: FnOnce() + Send + 'static>(callback: F) -> bool {
194 if const { size_of::<F>() == 0 } {
197 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 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
247struct 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 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}