use objc2::runtime::AnyObject;
use std::ffi::c_void;
unsafe extern "C" {
fn dispatch_queue_create(label: *const i8, attr: *const c_void) -> *mut AnyObject;
fn dispatch_sync_f(
queue: *mut AnyObject,
context: *mut c_void,
work: unsafe extern "C" fn(*mut c_void),
);
fn dispatch_async_f(
queue: *mut AnyObject,
context: *mut c_void,
work: unsafe extern "C" fn(*mut c_void),
);
fn dispatch_release(object: *mut AnyObject);
static _dispatch_main_q: *mut AnyObject;
}
pub struct DispatchQueue {
inner: *mut AnyObject,
owned: bool,
}
unsafe impl Send for DispatchQueue {}
unsafe impl Sync for DispatchQueue {}
impl DispatchQueue {
#[must_use]
pub fn new(label: &str) -> Self {
let sanitized: String = label
.chars()
.map(|c| if c == '\0' { '_' } else { c })
.collect();
let label_cstr = std::ffi::CString::new(sanitized).expect("NULs already stripped");
let queue = unsafe {
dispatch_queue_create(
label_cstr.as_ptr(),
std::ptr::null(), )
};
Self {
inner: queue,
owned: true,
}
}
#[must_use]
pub fn main() -> Self {
Self {
inner: unsafe { _dispatch_main_q },
owned: false,
}
}
pub unsafe fn from_raw(ptr: *mut AnyObject) -> Self {
Self {
inner: ptr,
owned: false,
}
}
#[must_use]
pub fn as_ptr(&self) -> *mut AnyObject {
self.inner
}
pub fn sync<F, R>(&self, f: F) -> R
where
F: FnOnce() -> R,
{
if self.inner.is_null() {
return f();
}
let mut result: Option<R> = None;
let mut closure = Some(f);
struct Context<'a, F, R> {
closure: &'a mut Option<F>,
result: &'a mut Option<R>,
}
unsafe extern "C" fn trampoline<F, R>(context: *mut c_void)
where
F: FnOnce() -> R,
{
unsafe {
let ctx = &mut *(context as *mut Context<'_, F, R>);
if let Some(f) = ctx.closure.take() {
*ctx.result = Some(f());
}
}
}
let mut context = Context {
closure: &mut closure,
result: &mut result,
};
unsafe {
dispatch_sync_f(
self.inner,
&mut context as *mut Context<'_, F, R> as *mut c_void,
trampoline::<F, R>,
);
}
result.expect("dispatch_sync_f did not execute closure")
}
pub fn async_exec<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
if self.inner.is_null() {
f();
return;
}
let boxed = Box::new(f);
let context = Box::into_raw(boxed);
unsafe extern "C" fn trampoline<F>(context: *mut c_void)
where
F: FnOnce() + Send + 'static,
{
unsafe {
let f = Box::from_raw(context as *mut F);
f();
}
}
unsafe {
dispatch_async_f(self.inner, context as *mut c_void, trampoline::<F>);
}
}
}
impl Drop for DispatchQueue {
fn drop(&mut self) {
if self.owned && !self.inner.is_null() {
unsafe {
dispatch_release(self.inner);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[test]
fn test_dispatch_queue_sync() {
let queue = DispatchQueue::new("test.queue.sync");
let result = queue.sync(|| 42);
assert_eq!(result, 42);
}
#[test]
fn test_dispatch_queue_async() {
let queue = DispatchQueue::new("test.queue.async");
let flag = Arc::new(AtomicBool::new(false));
let flag_clone = flag.clone();
queue.async_exec(move || {
flag_clone.store(true, Ordering::SeqCst);
});
std::thread::sleep(std::time::Duration::from_millis(100));
assert!(flag.load(Ordering::SeqCst));
}
#[test]
fn test_main_queue() {
let main = DispatchQueue::main();
assert!(!main.as_ptr().is_null());
}
}