use core::fmt;
use core::mem::{self, ManuallyDrop};
use objc2::MainThreadMarker;
use crate::DispatchQueue;
pub fn run_on_main<F, R>(f: F) -> R
where
F: Send + FnOnce(MainThreadMarker) -> R,
R: Send,
{
if let Some(mtm) = MainThreadMarker::new() {
f(mtm)
} else {
let mut ret = None;
DispatchQueue::main().exec_sync(|| {
ret = Some(f(unsafe { MainThreadMarker::new_unchecked() }))
});
ret.unwrap()
}
}
#[doc(alias = "@MainActor")]
pub struct MainThreadBound<T>(ManuallyDrop<T>);
unsafe impl<T> Send for MainThreadBound<T> {}
unsafe impl<T> Sync for MainThreadBound<T> {}
impl<T> Drop for MainThreadBound<T> {
fn drop(&mut self) {
if mem::needs_drop::<T>() {
run_on_main(|_mtm| {
let this = self;
unsafe { ManuallyDrop::drop(&mut this.0) };
})
}
}
}
impl<T> MainThreadBound<T> {
#[inline]
pub const fn new(inner: T, _mtm: MainThreadMarker) -> Self {
Self(ManuallyDrop::new(inner))
}
#[inline]
pub fn get(&self, _mtm: MainThreadMarker) -> &T {
&self.0
}
#[inline]
pub fn get_mut(&mut self, _mtm: MainThreadMarker) -> &mut T {
&mut self.0
}
#[inline]
pub fn into_inner(self, _mtm: MainThreadMarker) -> T {
let mut this = ManuallyDrop::new(self);
unsafe { ManuallyDrop::take(&mut this.0) }
}
}
impl<T> MainThreadBound<T> {
#[inline]
pub fn get_on_main<F, R>(&self, f: F) -> R
where
F: Send + FnOnce(&T) -> R,
R: Send,
{
run_on_main(|mtm| f(self.get(mtm)))
}
#[inline]
pub fn get_on_main_mut<F, R>(&mut self, f: F) -> R
where
F: Send + FnOnce(&mut T) -> R,
R: Send,
{
run_on_main(|mtm| f(self.get_mut(mtm)))
}
}
impl<T> fmt::Debug for MainThreadBound<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MainThreadBound").finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::cell::Cell;
static_assertions::assert_impl_all!(MainThreadBound<MainThreadMarker>: Send, Sync);
static_assertions::assert_impl_all!(MainThreadBound<*const ()>: Send, Sync);
#[test]
fn always_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
fn foo<T>() {
assert_send_sync::<MainThreadBound<T>>();
}
foo::<()>();
}
#[test]
fn test_main_thread_bound_into_inner() {
let mtm = unsafe { MainThreadMarker::new_unchecked() };
struct Foo<'a> {
is_dropped: &'a Cell<bool>,
}
impl Drop for Foo<'_> {
fn drop(&mut self) {
self.is_dropped.set(true);
}
}
let is_dropped = Cell::new(false);
let foo = Foo {
is_dropped: &is_dropped,
};
let foo = MainThreadBound::new(foo, mtm);
assert!(!is_dropped.get());
let foo = foo.into_inner(mtm);
assert!(!is_dropped.get());
drop(foo);
assert!(is_dropped.get());
}
}