use alloc::boxed::Box;
use alloc::ffi::CString;
use core::ffi::c_long;
use core::ptr::NonNull;
use super::utils::function_wrapper;
use crate::generated::{
_dispatch_main_q, _dispatch_queue_attr_concurrent, dispatch_get_global_queue,
dispatch_queue_set_specific,
};
use crate::{
DispatchObject, DispatchQoS, DispatchRetained, DispatchTime, QualityOfServiceClassFloorError,
};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum QueueAfterError {
TimeOverflow,
}
enum_with_val! {
#[doc(alias = "dispatch_queue_priority_t")]
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct DispatchQueueGlobalPriority(pub c_long) {
#[doc(alias = "DISPATCH_QUEUE_PRIORITY_HIGH")]
High = 0x2,
#[doc(alias = "DISPATCH_QUEUE_PRIORITY_DEFAULT")]
Default = 0x0,
#[doc(alias = "DISPATCH_QUEUE_PRIORITY_LOW")]
Low = -0x2,
#[doc(alias = "DISPATCH_QUEUE_PRIORITY_BACKGROUND")]
Background = u16::MIN as c_long,
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum GlobalQueueIdentifier {
Priority(DispatchQueueGlobalPriority),
QualityOfService(DispatchQoS),
}
impl GlobalQueueIdentifier {
pub fn to_identifier(self) -> isize {
match self {
GlobalQueueIdentifier::Priority(queue_priority) => queue_priority.0 as isize,
GlobalQueueIdentifier::QualityOfService(qos_class) => qos_class.0 as isize,
}
}
}
dispatch_object!(
#[doc(alias = "dispatch_queue_t")]
#[doc(alias = "dispatch_queue_s")]
pub struct DispatchQueue;
);
dispatch_object_not_data!(unsafe DispatchQueue);
impl DispatchQueue {
pub fn new(label: &str, queue_attribute: Option<&DispatchQueueAttr>) -> DispatchRetained<Self> {
let label = CString::new(label).expect("Invalid label!");
unsafe { Self::__new(label.as_ptr(), queue_attribute) }
}
pub fn new_with_target(
label: &str,
queue_attribute: Option<&DispatchQueueAttr>,
target: Option<&DispatchQueue>,
) -> DispatchRetained<Self> {
let label = CString::new(label).expect("Invalid label!");
unsafe { Self::__new_with_target(label.as_ptr(), queue_attribute, target) }
}
pub fn global_queue(identifier: GlobalQueueIdentifier) -> DispatchRetained<Self> {
let raw_identifier = identifier.to_identifier();
dispatch_get_global_queue(raw_identifier, 0)
}
#[inline]
#[doc(alias = "dispatch_get_main_queue")]
pub fn main() -> &'static Self {
unsafe { &_dispatch_main_q }
}
pub fn exec_sync<F>(&self, work: F)
where
F: Send + FnOnce(),
{
let work_boxed = Box::into_raw(Box::new(work)).cast();
unsafe { Self::exec_sync_f(self, work_boxed, function_wrapper::<F>) }
}
pub fn exec_async<F>(&self, work: F)
where
F: Send + FnOnce() + 'static,
{
let work_boxed = Box::into_raw(Box::new(work)).cast();
unsafe { Self::exec_async_f(self, work_boxed, function_wrapper::<F>) }
}
pub fn after<F>(&self, when: DispatchTime, work: F) -> Result<(), QueueAfterError>
where
F: Send + FnOnce(),
{
let work_boxed = Box::into_raw(Box::new(work)).cast();
unsafe { Self::exec_after_f(when, self, work_boxed, function_wrapper::<F>) };
Ok(())
}
pub fn barrier_async<F>(&self, work: F)
where
F: Send + FnOnce() + 'static,
{
let work_boxed = Box::into_raw(Box::new(work)).cast();
unsafe { Self::barrier_async_f(self, work_boxed, function_wrapper::<F>) }
}
pub fn barrier_sync<F>(&self, work: F)
where
F: Send + FnOnce(),
{
let work_boxed = Box::into_raw(Box::new(work)).cast();
unsafe { Self::barrier_sync_f(self, work_boxed, function_wrapper::<F>) }
}
pub fn barrier_async_and_wait<F>(&self, work: F)
where
F: Send + FnOnce() + 'static,
{
let work_boxed = Box::into_raw(Box::new(work)).cast();
unsafe { Self::barrier_async_and_wait_f(self, work_boxed, function_wrapper::<F>) }
}
pub fn set_specific<F>(&self, key: NonNull<()>, destructor: F)
where
F: Send + FnOnce(),
{
let destructor_boxed = Box::into_raw(Box::new(destructor)).cast();
unsafe {
dispatch_queue_set_specific(self, key.cast(), destructor_boxed, function_wrapper::<F>)
}
}
pub fn set_qos_class_floor(
&self,
qos_class: DispatchQoS,
relative_priority: i32,
) -> Result<(), QualityOfServiceClassFloorError> {
unsafe { DispatchObject::set_qos_class_floor(self, qos_class, relative_priority) }
}
#[allow(missing_docs)]
#[doc(alias = "DISPATCH_APPLY_AUTO")]
pub const APPLY_AUTO: Option<&DispatchQueue> = None;
#[allow(missing_docs)]
#[doc(alias = "DISPATCH_TARGET_QUEUE_DEFAULT")]
pub const TARGET_QUEUE_DEFAULT: Option<&DispatchQueue> = None;
#[allow(missing_docs)]
#[doc(alias = "DISPATCH_CURRENT_QUEUE_LABEL")]
pub const CURRENT_QUEUE_LABEL: Option<&DispatchQueue> = None;
}
dispatch_object!(
#[doc(alias = "dispatch_queue_attr_t")]
#[doc(alias = "dispatch_queue_attr_s")]
pub struct DispatchQueueAttr;
);
dispatch_object_not_data!(unsafe DispatchQueueAttr);
impl DispatchQueueAttr {
#[doc(alias = "DISPATCH_QUEUE_SERIAL")]
pub const SERIAL: Option<&Self> = None;
pub fn concurrent() -> Option<&'static Self> {
unsafe { Some(&_dispatch_queue_attr_concurrent) }
}
}
pub fn dispatch_main() -> ! {
extern "C" {
fn dispatch_main() -> !;
}
unsafe { dispatch_main() }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_main_queue() {
let _ = DispatchQueue::main();
}
#[test]
#[cfg(feature = "std")]
fn test_serial_queue() {
let queue = DispatchQueue::new("com.github.madsmtm.objc2", DispatchQueueAttr::SERIAL);
let (tx, rx) = std::sync::mpsc::channel();
queue.exec_async(move || {
tx.send(()).unwrap();
});
rx.recv().unwrap();
}
#[test]
#[cfg(feature = "std")]
fn test_concurrent_queue() {
let queue = DispatchQueue::new("com.github.madsmtm.objc2", DispatchQueueAttr::concurrent());
let (tx, rx) = std::sync::mpsc::channel();
let cloned_tx = tx.clone();
queue.exec_async(move || {
tx.send(()).unwrap();
});
queue.exec_async(move || {
cloned_tx.send(()).unwrap();
});
for _ in 0..2 {
rx.recv().unwrap();
}
}
#[test]
#[cfg(feature = "std")]
fn test_global_default_queue() {
let queue = DispatchQueue::global_queue(GlobalQueueIdentifier::QualityOfService(
DispatchQoS::Default,
));
let (tx, rx) = std::sync::mpsc::channel();
queue.exec_async(move || {
tx.send(()).unwrap();
});
rx.recv().unwrap();
}
#[test]
#[cfg(feature = "std")]
fn test_share_queue_across_threads() {
let queue = DispatchQueue::new("com.github.madsmtm.objc2", DispatchQueueAttr::SERIAL);
let (tx, rx) = std::sync::mpsc::channel();
let cloned_tx = tx.clone();
let cloned_queue = queue.clone();
queue.exec_async(move || {
cloned_queue.exec_async(move || {
cloned_tx.send(()).unwrap();
});
});
queue.exec_async(move || {
tx.send(()).unwrap();
});
for _ in 0..2 {
rx.recv().unwrap();
}
}
#[test]
#[cfg(feature = "std")]
fn test_move_queue_between_threads() {
let queue = DispatchQueue::new("com.github.madsmtm.objc2", DispatchQueueAttr::SERIAL);
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
queue.exec_async(move || {
tx.send(()).unwrap();
});
});
rx.recv().unwrap();
}
}