use core::{
cell::Cell,
ffi::c_void,
marker::PhantomData,
mem::ManuallyDrop,
ptr::{addr_of_mut, NonNull},
sync::atomic::Ordering,
};
use sdl3_sys::{
init::{SDL_IsMainThread, SDL_RunOnMainThread},
stdinc::{SDL_aligned_alloc, SDL_aligned_free},
thread::{SDL_GetCurrentThreadID, SDL_ThreadID},
timer::SDL_Delay,
};
#[cfg(target_has_atomic = "8")]
type AtomicLeastU8 = core::sync::atomic::AtomicU8;
#[cfg(all(not(target_has_atomic = "8"), target_has_atomic = "16"))]
type AtomicLeastU8 = core::sync::atomic::AtomicU16;
#[cfg(all(
not(any(target_has_atomic = "8", target_has_atomic = "16")),
target_has_atomic = "32"
))]
type AtomicLeastU8 = core::sync::atomic::AtomicU32;
#[cfg(all(
not(any(
target_has_atomic = "8",
target_has_atomic = "16",
target_has_atomic = "32"
)),
target_has_atomic = "64"
))]
type AtomicLeastU8 = core::sync::atomic::AtomicU64;
#[cfg(all(
not(any(
target_has_atomic = "8",
target_has_atomic = "16",
target_has_atomic = "32",
target_has_atomic = "64"
)),
target_has_atomic = "128"
))]
type AtomicLeastU8 = core::sync::atomic::AtomicU128;
#[cfg(not(any(
target_has_atomic = "8",
target_has_atomic = "16",
target_has_atomic = "32",
target_has_atomic = "64",
target_has_atomic = "128"
)))]
compile_error!("no supported atomic integer type");
#[derive(Clone, Copy)]
pub struct MainThreadToken(PhantomData<*const ()>);
impl MainThreadToken {
pub fn get() -> Option<Self> {
struct ThreadId(Cell<SDL_ThreadID>);
unsafe impl Sync for ThreadId {}
static MAIN_THREAD_ID: ThreadId = ThreadId(Cell::new(SDL_ThreadID(0)));
static MAIN_THREAD_ID_STATUS: AtomicLeastU8 = AtomicLeastU8::new(0);
loop {
match MAIN_THREAD_ID_STATUS.load(Ordering::Acquire) {
0 => {
if !unsafe { SDL_IsMainThread() } {
return None;
}
if MAIN_THREAD_ID_STATUS
.compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
MAIN_THREAD_ID.0.set(SDL_GetCurrentThreadID());
MAIN_THREAD_ID_STATUS.store(2, Ordering::Release);
return Some(Self(PhantomData));
}
}
1 => (),
_ => {
return (MAIN_THREAD_ID.0.get() == SDL_GetCurrentThreadID())
.then_some(Self(PhantomData));
}
}
unsafe { SDL_Delay(0) }
}
}
#[track_caller]
pub fn assert() -> Self {
Self::get().expect("This operation can only be performed on the main thread")
}
#[track_caller]
#[inline(always)]
pub fn init() {
Self::assert();
}
}
#[repr(transparent)]
pub struct MainThreadData<T>(T);
unsafe impl<T> Send for MainThreadData<T> {}
unsafe impl<T> Sync for MainThreadData<T> {}
impl<T> Drop for MainThreadData<T> {
fn drop(&mut self) {
MainThreadToken::assert();
}
}
impl<T> MainThreadData<T> {
#[inline(always)]
pub fn new(_: MainThreadToken, data: T) -> Self {
Self(data)
}
#[inline(always)]
pub fn get(&self, _: MainThreadToken) -> &T {
&self.0
}
#[inline(always)]
pub fn get_mut(&mut self, _: MainThreadToken) -> &mut T {
&mut self.0
}
#[track_caller]
#[inline(always)]
pub fn assert_new(data: T) -> Self {
Self::new(MainThreadToken::assert(), data)
}
#[track_caller]
#[inline(always)]
pub fn assert_get(&self) -> &T {
self.get(MainThreadToken::assert())
}
#[track_caller]
#[inline(always)]
pub fn assert_get_mut(&mut self) -> &mut T {
self.get_mut(MainThreadToken::assert())
}
#[must_use]
#[inline(always)]
pub fn get_on_main_thread(&self, callback: impl FnOnce(&T) + Send) -> bool {
run_sync_on_main_thread(move || callback(&self.0))
}
#[must_use]
#[inline(always)]
pub fn get_mut_on_main_thread(&mut self, callback: impl FnOnce(&mut T) + Send) -> bool {
run_sync_on_main_thread(move || callback(&mut self.0))
}
}
struct CallOnceContainer<F>(Option<F>);
trait CallOnce {
fn call_once(&mut self);
fn discard(&mut self);
}
impl<F: FnOnce()> CallOnce for CallOnceContainer<F> {
fn call_once(&mut self) {
if let Some(f) = self.0.take() {
f();
}
}
fn discard(&mut self) {
self.0.take();
}
}
#[repr(transparent)]
struct MainThreadCallHeader(*mut dyn CallOnce);
#[repr(C)]
struct MainThreadCall<T> {
header: MainThreadCallHeader,
data: T,
}
#[must_use]
pub fn run_sync_on_main_thread<F: FnOnce() + Send>(callback: F) -> bool {
unsafe extern "C" fn main_thread_fn(userdata: *mut c_void) {
let call_once = unsafe { &mut *(userdata as *mut &mut dyn CallOnce) };
call_once.call_once();
}
let mut f = CallOnceContainer(Some(callback));
let mut f: &mut dyn CallOnce = &mut f;
let f = &mut f as *mut &mut dyn CallOnce as *mut c_void;
unsafe { SDL_RunOnMainThread(Some(main_thread_fn), f, true) }
}
#[must_use]
pub fn run_async_on_main_thread<F: FnOnce() + Send + 'static>(callback: F) -> bool {
if const { size_of::<F>() == 0 } {
unsafe extern "C" fn main_thread_fn<F: FnOnce() + Send + 'static>(userdata: *mut c_void) {
unsafe { (userdata as *mut F).read()() }
}
let callback = ManuallyDrop::new(callback);
let userdata: *mut F = NonNull::<F>::dangling().as_ptr();
if unsafe { SDL_RunOnMainThread(Some(main_thread_fn::<F>), userdata as *mut c_void, false) }
{
true
} else {
let _ = ManuallyDrop::into_inner(callback);
false
}
} else {
unsafe extern "C" fn main_thread_fn(userdata: *mut c_void) {
defer!(unsafe { SDL_aligned_free(userdata) });
unsafe { &mut *((*(userdata as *mut MainThreadCallHeader)).0) }.call_once();
}
let f = CallOnceContainer(Some(callback));
let userdata = unsafe {
SDL_aligned_alloc(
align_of::<MainThreadCall<CallOnceContainer<F>>>(),
size_of::<MainThreadCall<CallOnceContainer<F>>>(),
)
} as *mut MainThreadCall<CallOnceContainer<F>>;
if userdata.is_null() {
return false;
}
let payload = unsafe { addr_of_mut!((*userdata).data) };
unsafe {
addr_of_mut!((*userdata).header.0).write(payload as *mut dyn CallOnce);
(payload as *mut ManuallyDrop<CallOnceContainer<F>>).write(ManuallyDrop::new(f));
}
let userdata = userdata as *mut c_void;
if unsafe { SDL_RunOnMainThread(Some(main_thread_fn), userdata, false) } {
true
} else {
defer!(unsafe { SDL_aligned_free(userdata) });
unsafe { &mut *((*(userdata as *mut MainThreadCallHeader)).0) }.discard();
false
}
}
}