use core::fmt;
use core::marker::PhantomData;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{NSObject, NSString};
use crate::rc::{Id, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
#[derive(PartialEq, Eq, Hash)]
pub struct NSThread;
unsafe impl ClassType for NSThread {
type Super = NSObject;
}
);
unsafe impl Send for NSThread {}
unsafe impl Sync for NSThread {}
impl UnwindSafe for NSThread {}
impl RefUnwindSafe for NSThread {}
extern_methods!(
unsafe impl NSThread {
pub fn current() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), currentThread] }
}
pub fn main() -> Id<NSThread, Shared> {
let obj: Option<_> = unsafe { msg_send_id![Self::class(), mainThread] };
obj.expect("Could not retrieve main thread.")
}
#[sel(isMainThread)]
pub fn is_main(&self) -> bool;
pub fn name(&self) -> Option<Id<NSString, Shared>> {
unsafe { msg_send_id![self, name] }
}
unsafe fn new() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), new] }
}
#[sel(start)]
unsafe fn start(&self);
#[sel(isMainThread)]
fn is_current_main() -> bool;
#[sel(isMultiThreaded)]
fn is_global_multi() -> bool;
}
);
impl fmt::Debug for NSThread {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let obj: &NSObject = self;
fmt::Debug::fmt(obj, f)
}
}
pub fn is_multi_threaded() -> bool {
NSThread::is_global_multi()
}
pub fn is_main_thread() -> bool {
NSThread::is_current_main()
}
#[allow(unused)]
fn make_multithreaded() {
let thread = unsafe { NSThread::new() };
unsafe { thread.start() };
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MainThreadMarker {
_priv: PhantomData<*mut ()>,
}
impl MainThreadMarker {
pub fn new() -> Option<Self> {
if is_main_thread() {
Some(unsafe { Self::new_unchecked() })
} else {
None
}
}
pub unsafe fn new_unchecked() -> Self {
Self { _priv: PhantomData }
}
}
impl fmt::Debug for MainThreadMarker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("MainThreadMarker").finish()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::*;
#[test]
#[cfg_attr(
feature = "gnustep-1-7",
ignore = "Retrieving main thread is weirdly broken, only works with --test-threads=1"
)]
fn test_main_thread() {
let current = NSThread::current();
let main = NSThread::main();
assert!(main.is_main());
if main == current {
assert!(current.is_main());
assert!(is_main_thread());
} else {
assert!(!current.is_main());
assert!(!is_main_thread());
}
}
#[test]
fn test_not_main_thread() {
let res = std::thread::spawn(|| (is_main_thread(), NSThread::current().is_main()))
.join()
.unwrap();
assert_eq!(res, (false, false));
}
#[test]
fn test_main_thread_auto_traits() {
fn assert_traits<T: Unpin + UnwindSafe + RefUnwindSafe + Sized>() {}
assert_traits::<MainThreadMarker>()
}
#[test]
#[cfg_attr(
feature = "gnustep-1-7",
ignore = "Retrieving main thread is weirdly broken, only works with --test-threads=1"
)]
fn test_debug() {
let thread = NSThread::main();
let actual = format!("{:?}", thread);
let expected = [
format!("<NSThread: {:p}>{{number = 1, name = (null)}}", thread),
format!("<NSThread: {:p}>{{number = 1, name = main}}", thread),
format!("<_NSMainThread: {:p}>{{number = 1, name = (null)}}", thread),
format!("<_NSMainThread: {:p}>{{number = 1, name = main}}", thread),
];
assert!(
expected.contains(&actual),
"Expected one of {:?}, got {:?}",
expected,
actual,
);
let marker = unsafe { MainThreadMarker::new_unchecked() };
assert_eq!(format!("{:?}", marker), "MainThreadMarker");
}
}