diff --git a/crates/dispatch2/Cargo.toml b/crates/dispatch2/Cargo.toml new file mode 100644 index 000000000..1f31d697c --- /dev/null +++ b/crates/dispatch2/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "dispatch2" +version = "0.1.0" +authors = ["Mary "] +license = "Apache-2.0 OR MIT" +repository = "https://www.github.com/marysaka/dispatch2.git" +homepage = "https://www.github.com/marysaka/dispatch2" +description = "Bindings and wrappers for the Grand Central Dispatch (GCD)" +keywords = ["gcd", "macOS", "iOS", "watchOS", "ipadOS"] +categories = ["api-bindings", "development-tools::ffi", "os::macos-apis"] +edition = "2021" + +exclude = [ + ".github" +] + +[dependencies] +libc = "0.2" diff --git a/crates/dispatch2/README.md b/crates/dispatch2/README.md new file mode 100644 index 000000000..cbbae31a3 --- /dev/null +++ b/crates/dispatch2/README.md @@ -0,0 +1,31 @@ +# dispatch2 + +Allows interaction with the [Apple Dispatch](https://developer.apple.com/documentation/dispatch) library in a safe and unsafe way. + +## Usage + +To use `dispatch2`, add this to your `Cargo.toml`: + +```toml +[dependencies] +dispatch2 = "0.1.0" +``` + +## Example + +```rust +use dispatch2::{Queue, QueueAttribute}; + +fn main() { + let queue = Queue::new("example_queue", QueueAttribute::Serial); + queue.exec_async(|| println!("Hello")); + queue.exec_sync(|| println!("World")); +} +``` + +## License + +dispatch2 is distributed under the terms of either the MIT license or the Apache +License (Version 2.0), at the user's choice. + +See [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT). diff --git a/crates/dispatch2/TODO.md b/crates/dispatch2/TODO.md new file mode 100644 index 000000000..dd3ec4ce9 --- /dev/null +++ b/crates/dispatch2/TODO.md @@ -0,0 +1,12 @@ +# TODO + +- Fully document all ffi APIs +- CI test on macOS +- CI test on Linux using https://github.com/apple/swift-corelibs-libdispatch +- CI test on Windows using https://github.com/apple/swift-corelibs-libdispatch +- Safe wrapper for ``dispatch_source_*`` + ``set_target_queue/activate/suspend/resume`` for it +- Safe wrapper for ``dispatch_data_*`` +- Safe wrapper for ``dispatch_once_f`` (is that relevent?) +- Safe wrapper for ``dispatch_get_context/dispatch_set_context`` (quite impossible without big overhead => wrap dispatch object destructor to release the boxed value) +- All blocks related bindings and ``dispatch_block_*`` functions with compat with ``block2`` on Apple platforms. +- Integrate conversion from SystemTime to dispatch_time_t via dispatch_walltime and safe APIs using that. diff --git a/crates/dispatch2/src/ffi.rs b/crates/dispatch2/src/ffi.rs new file mode 100644 index 000000000..756d6c5f4 --- /dev/null +++ b/crates/dispatch2/src/ffi.rs @@ -0,0 +1,562 @@ +//! Bindings to the Apple Grand Central Dispatch (GCD). + +#![allow(missing_docs, non_camel_case_types)] + +//use block2::Block; +use core::ffi::{c_char, c_int, c_long, c_uint, c_ulong, c_void}; + +macro_rules! create_opaque_type { + ($type_name: ident, $typedef_name: ident) => { + #[repr(C)] + #[derive(Debug)] + #[allow(missing_docs)] + pub struct $type_name { + /// opaque value + _inner: [u8; 0], + } + + #[allow(missing_docs)] + pub type $typedef_name = *mut $type_name; + }; +} + +macro_rules! enum_with_val { + ($(#[$meta:meta])* $vis:vis struct $ident:ident($innervis:vis $ty:ty) { + $($(#[$varmeta:meta])* $variant:ident = $num:expr),* $(,)* + }) => { + $(#[$meta])* + #[repr(transparent)] + $vis struct $ident($innervis $ty); + impl $ident { + $($(#[$varmeta])* $vis const $variant: $ident = $ident($num);)* + } + + impl ::core::fmt::Debug for $ident { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + match self { + $(&$ident::$variant => write!(f, "{}::{}", stringify!($ident), stringify!($variant)),)* + &$ident(v) => write!(f, "UNKNOWN({})", v), + } + } + } + } +} + +create_opaque_type!(dispatch_object_s, dispatch_object_t); +create_opaque_type!(dispatch_data_s, dispatch_data_t); +create_opaque_type!(dispatch_source_type_s, dispatch_source_type_t); + +// As we cannot switch block from one type to another, we let the user do the pointer conversion. +/// The prototype of blocks submitted to dispatch queues, which take no arguments and have no return value. +pub type dispatch_block_t = *const c_void; +/// The prototype of functions submitted to dispatch queues. +pub type dispatch_function_t = extern "C" fn(*mut c_void); +/// A predicate for use with the dispatch_once function. +pub type dispatch_once_t = usize; + +/// An abstract representation of time. +#[repr(transparent)] +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord)] +pub struct dispatch_time_t(pub u64); + +create_opaque_type!(dispatch_group_s, dispatch_group_t); +create_opaque_type!(dispatch_queue_global_s, dispatch_queue_global_t); +create_opaque_type!(dispatch_queue_serial_s, dispatch_queue_serial_t); +create_opaque_type!(dispatch_queue_main_s, dispatch_queue_main_t); +create_opaque_type!(dispatch_queue_concurrent_s, dispatch_queue_concurrent_t); +create_opaque_type!(dispatch_queue_attr_s, dispatch_queue_attr_t); +create_opaque_type!(dispatch_semaphore_s, dispatch_semaphore_t); +create_opaque_type!(dispatch_source_s, dispatch_source_t); +create_opaque_type!(dispatch_queue_s, dispatch_queue_t); +create_opaque_type!(dispatch_workloop_s, dispatch_workloop_t); + +/// A dispatch queue that executes blocks serially in FIFO order. +pub const DISPATCH_QUEUE_SERIAL: dispatch_queue_attr_t = core::ptr::null_mut(); +/// A dispatch queue that executes blocks concurrently. +pub static DISPATCH_QUEUE_CONCURRENT: &dispatch_queue_attr_s = { + // Safety: immutable external definition + unsafe { &_dispatch_queue_attr_concurrent } +}; + +pub const DISPATCH_APPLY_AUTO: dispatch_queue_t = core::ptr::null_mut(); +pub const DISPATCH_TARGET_QUEUE_DEFAULT: dispatch_queue_t = core::ptr::null_mut(); +pub const DISPATCH_CURRENT_QUEUE_LABEL: dispatch_queue_t = core::ptr::null_mut(); + +pub const DISPATCH_TIME_NOW: dispatch_time_t = dispatch_time_t(0); +pub const DISPATCH_TIME_FOREVER: dispatch_time_t = dispatch_time_t(u64::MAX); +pub const QOS_MIN_RELATIVE_PRIORITY: i32 = -15; + +enum_with_val! { + /// Flags to pass to the [dispatch_block_create] and [dispatch_block_create_with_qos_class] functions. + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct dispatch_block_flags_t(pub c_long) { + DISPATCH_BLOCK_BARRIER = 0x1, + DISPATCH_BLOCK_DETACHED = 0x2, + DISPATCH_BLOCK_ASSIGN_CURRENT = 0x4, + DISPATCH_BLOCK_NO_QOS_CLASS = 0x8, + DISPATCH_BLOCK_INHERIT_QOS_CLASS = 0x10, + DISPATCH_BLOCK_ENFORCE_QOS_CLASS = 0x20, + } +} + +enum_with_val! { + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct dispatch_autorelease_frequency_t(pub c_ulong) { + DISPATCH_AUTORELEASE_FREQUENCY_INHERIT = 0x0, + DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM = 0x1, + DISPATCH_AUTORELEASE_FREQUENCY_NEVER = 0x2, + } +} + +enum_with_val! { + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct dispatch_queue_priority_t(pub c_long) { + DISPATCH_QUEUE_PRIORITY_HIGH = 0x2, + DISPATCH_QUEUE_PRIORITY_DEFAULT = 0x0, + DISPATCH_QUEUE_PRIORITY_LOW = -0x2, + DISPATCH_QUEUE_PRIORITY_BACKGROUND = u16::MIN as c_long, + } +} + +enum_with_val! { + /// Quality-of-service classes that specify the priorities for executing tasks. + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct dispatch_qos_class_t(pub c_uint) { + QOS_CLASS_USER_INTERACTIVE = 0x21, + QOS_CLASS_USER_INITIATED = 0x19, + QOS_CLASS_DEFAULT = 0x15, + QOS_CLASS_UTILITY = 0x11, + QOS_CLASS_BACKGROUND = 0x09, + QOS_CLASS_UNSPECIFIED = 0x00, + } +} + +enum_with_val! { + /// Mach send-right flags. + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct dispatch_source_mach_send_flags_t(pub c_ulong) { + DISPATCH_MACH_SEND_DEAD = 0x1 + } +} + +enum_with_val! { + /// Mach receive-right flags. + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct dispatch_source_mach_recv_flags_t(pub c_ulong) { + // no definition + } +} + +enum_with_val! { + // Memory pressure events. + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct dispatch_source_memorypressure_flags_t(pub c_ulong) { + DISPATCH_MEMORYPRESSURE_NORMAL = 0x1, + DISPATCH_MEMORYPRESSURE_WARN = 0x2, + DISPATCH_MEMORYPRESSURE_CRITICAL = 0x4, + } +} + +enum_with_val! { + /// Events related to a process. + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct dispatch_source_proc_flags_t(pub c_ulong) { + DISPATCH_PROC_EXIT = 0x80000000, + DISPATCH_PROC_FORK = 0x40000000, + DISPATCH_PROC_EXEC = 0x20000000, + DISPATCH_PROC_SIGNAL = 0x08000000, + } +} + +enum_with_val! { + /// Events involving a change to a file system object. + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct dispatch_source_vnode_flags_t(pub c_ulong) { + DISPATCH_VNODE_DELETE = 0x1, + DISPATCH_VNODE_WRITE = 0x2, + DISPATCH_VNODE_EXTEND = 0x4, + DISPATCH_VNODE_ATTRIB = 0x8, + DISPATCH_VNODE_LINK = 0x10, + DISPATCH_VNODE_RENAME = 0x20, + DISPATCH_VNODE_REVOKE = 0x40, + DISPATCH_VNODE_FUNLOCK = 0x100, + } +} + +enum_with_val! { + /// Flags to use when configuring a timer dispatch source. + #[derive(PartialEq, Eq, Clone, Copy)] + pub struct dispatch_source_timer_flags_t(pub c_ulong) { + DISPATCH_TIMER_STRICT = 0x1, + } +} + +#[cfg_attr( + any(target_os = "macos", target_os = "ios", target_os = "tvos"), + link(name = "System", kind = "dylib") +)] +#[cfg_attr( + not(any(target_os = "macos", target_os = "ios", target_os = "tvos")), + link(name = "dispatch", kind = "dylib") +)] +extern "C" { + /// Increments the reference count (the retain count) of a dispatch object. + pub fn dispatch_retain(object: dispatch_object_t); + /// Decrements the reference count (the retain count) of a dispatch object. + pub fn dispatch_release(object: dispatch_object_t); + /// Returns the application-defined context of an object. + pub fn dispatch_get_context(object: dispatch_object_t) -> *mut c_void; + /// Associates an application-defined context with the object. + pub fn dispatch_set_context(object: dispatch_object_t, context: *mut c_void); + /// Sets the finalizer function for a dispatch object. + pub fn dispatch_set_finalizer_f(object: dispatch_object_t, finalizer: dispatch_function_t); + /// Activates the dispatch object. + pub fn dispatch_activate(object: dispatch_object_t); + /// Suspends the invocation of block objects on a dispatch object. + pub fn dispatch_suspend(object: dispatch_object_t); + /// Resumes the invocation of block objects on a dispatch object. + pub fn dispatch_resume(object: dispatch_object_t); + /// Specifies the minimum quality-of-service level for a dispatch queue, source, or workloop. + pub fn dispatch_set_qos_class_floor( + object: dispatch_object_t, + qos_class: dispatch_qos_class_t, + relative_priority: i32, + ); + + /// Executes a block object only once for the lifetime of an application. + pub fn dispatch_once(predicate: *mut dispatch_once_t, block: dispatch_block_t); + /// Executes an application-defined function only once for the lifetime of an application. + pub fn dispatch_once_f( + predicate: *mut dispatch_once_t, + context: *mut c_void, + function: dispatch_function_t, + ); + + /// Creates a new dispatch block on the heap using an existing block and the given flags. + pub fn dispatch_block_create( + flags: dispatch_block_flags_t, + block: dispatch_block_t, + ) -> dispatch_block_t; + /// Creates a new dispatch block from an existing block and the given flags, and assigns it the specified quality-of-service class and relative priority. + pub fn dispatch_block_create_with_qos_class( + flags: dispatch_block_flags_t, + qos_class: dispatch_qos_class_t, + relative_priority: i32, + block: dispatch_block_t, + ) -> dispatch_block_t; + /// Creates, synchronously executes, and releases a dispatch block from the specified block and flags. + pub fn dispatch_block_perform(flags: dispatch_block_flags_t, block: dispatch_block_t); + /// Waits synchronously until execution of the specified dispatch block has completed or until the specified timeout has elapsed. + pub fn dispatch_block_wait(block: dispatch_block_t, timeout: dispatch_time_t) -> isize; + /// Schedules a notification block to be submitted to a queue when the execution of a specified dispatch block has completed. + pub fn dispatch_block_notify( + block: dispatch_block_t, + queue: dispatch_queue_t, + notification_block: dispatch_block_t, + ); + /// Cancels the specified dispatch block asynchronously. + pub fn dispatch_block_cancel(block: dispatch_block_t); + /// Tests whether the given dispatch block has been canceled. + pub fn dispatch_block_testcancel(block: dispatch_block_t) -> isize; + + pub static _dispatch_data_empty: dispatch_data_s; + pub static _dispatch_data_destructor_free: dispatch_block_t; + pub static _dispatch_data_destructor_munmap: dispatch_block_t; + /// Creates a new dispatch data object with the specified memory buffer. + pub fn dispatch_data_create( + buffer: *const c_void, + size: usize, + queue: dispatch_queue_t, + destructor: dispatch_block_t, + ) -> dispatch_data_t; + /// Returns the logical size of the memory managed by a dispatch data object + pub fn dispatch_data_get_size(data: dispatch_data_t) -> usize; + /// Returns a new dispatch data object containing a contiguous representation of the specified object’s memory. + pub fn dispatch_data_create_map( + data: dispatch_data_t, + buffer_ptr: *const c_void, + size_ptr: *mut usize, + ) -> dispatch_data_t; + /// Returns a new dispatch data object consisting of the concatenated data from two other data objects. + pub fn dispatch_data_create_concat( + data1: dispatch_data_t, + data2: dispatch_data_t, + ) -> dispatch_data_t; + /// Returns a new dispatch data object whose contents consist of a portion of another object’s memory region. + pub fn dispatch_data_create_subrange( + data: dispatch_data_t, + offset: usize, + length: usize, + ) -> dispatch_data_t; + // TODO: check if block2::Block layout is safe to use via FFI directly. + /*pub fn dispatch_data_apply( + data: dispatch_data_t, + applier: Block<(usize, *const c_void, usize), bool>, + ) -> bool;*/ + /// Returns a data object containing a portion of the data in another data object. + pub fn dispatch_data_copy_region( + data: dispatch_data_t, + location: usize, + offset_ptr: *mut usize, + ) -> dispatch_data_t; + + /// Creates a new group to which you can assign block objects. + pub fn dispatch_group_create() -> dispatch_group_t; + /// Schedules a block asynchronously for execution and simultaneously associates it with the specified dispatch group. + pub fn dispatch_group_async( + group: dispatch_group_t, + queue: dispatch_queue_t, + block: dispatch_block_t, + ); + /// Submits an application-defined function to a dispatch queue and associates it with the specified dispatch group. + pub fn dispatch_group_async_f( + group: dispatch_group_t, + queue: dispatch_queue_t, + context: *const c_void, + work: dispatch_function_t, + ); + /// Waits synchronously for the previously submitted block objects to finish; returns if the blocks do not complete before the specified timeout period has elapsed. + pub fn dispatch_group_wait(group: dispatch_group_t, timeout: dispatch_time_t) -> usize; + /// Schedules a block object to be submitted to a queue when a group of previously submitted block objects have completed. + pub fn dispatch_group_notify( + group: dispatch_group_t, + queue: dispatch_queue_t, + block: dispatch_block_t, + ); + /// Schedules an application-defined function to be submitted to a queue when a group of previously submitted block objects have completed. + pub fn dispatch_group_notify_f( + group: dispatch_group_t, + queue: dispatch_queue_t, + context: *const c_void, + work: dispatch_function_t, + ); + /// Explicitly indicates that a block has entered the group. + pub fn dispatch_group_enter(group: dispatch_group_t); + /// Explicitly indicates that a block in the group finished executing. + pub fn dispatch_group_leave(group: dispatch_group_t); + + /// Submits a block for asynchronous execution on a dispatch queue and returns immediately. + pub fn dispatch_async(queue: dispatch_queue_t, block: dispatch_block_t); + /// Submits an app-defined function for asynchronous execution on a dispatch queue and returns immediately. + pub fn dispatch_async_f( + queue: dispatch_queue_t, + context: *mut c_void, + work: dispatch_function_t, + ); + /// Submits a block object for execution and returns after that block finishes executing. + pub fn dispatch_sync(queue: dispatch_queue_t, block: dispatch_block_t); + /// Submits an app-defined function for synchronous execution on a dispatch queue. + pub fn dispatch_sync_f( + queue: dispatch_queue_t, + context: *mut c_void, + work: dispatch_function_t, + ); + /// Submits a work item for execution and returns only after it finishes executing. + pub fn dispatch_async_and_wait(queue: dispatch_queue_t, block: dispatch_block_t); + /// Submits a function-based work item for execution and returns only after it finishes executing. + pub fn dispatch_async_and_wait_f( + queue: dispatch_queue_t, + context: *mut c_void, + work: dispatch_function_t, + ); + // TODO: check if block2::Block layout is safe to use via FFI directly. + //pub fn dispatch_apply(iterations: usize, queue: dispatch_queue_t, block: Block<(usize,), ()>); + /// Submits a single function to the dispatch queue and causes the function to be executed the specified number of times. + pub fn dispatch_apply_f( + iterations: usize, + queue: dispatch_queue_t, + context: *mut c_void, + work: extern "C" fn(context: *mut c_void, iteration: usize), + ); + /// See [DISPATCH_QUEUE_CONCURRENT]. + pub static _dispatch_main_q: dispatch_queue_s; + /// Returns a system-defined global concurrent queue with the specified quality-of-service class. + pub fn dispatch_get_global_queue(identifier: usize, flags: usize) -> dispatch_queue_global_t; + /// See [DISPATCH_QUEUE_CONCURRENT]. + pub static _dispatch_queue_attr_concurrent: dispatch_queue_attr_s; + /// Returns an attribute that configures a dispatch queue as initially inactive. + pub fn dispatch_queue_attr_make_initially_inactive( + attr: dispatch_queue_attr_t, + ) -> dispatch_queue_attr_t; + /// Returns an attribute that specifies how the dispatch queue manages autorelease pools for the blocks it executes. + pub fn dispatch_queue_attr_make_with_autorelease_frequency( + attr: dispatch_queue_attr_t, + frequency: dispatch_autorelease_frequency_t, + ) -> dispatch_queue_attr_t; + /// Returns attributes suitable for creating a dispatch queue with the desired quality-of-service information. + pub fn dispatch_queue_attr_make_with_qos_class( + attr: dispatch_queue_attr_t, + qos_class: dispatch_qos_class_t, + relative_priority: c_int, + ) -> dispatch_queue_attr_t; + /// Creates a new dispatch queue to which you can submit blocks. + #[cfg_attr( + any(target_os = "macos", target_os = "ios", target_os = "tvos"), + link_name = "dispatch_queue_create_with_target$V2" + )] + pub fn dispatch_queue_create_with_target( + label: *const c_char, + attr: dispatch_queue_attr_t, + target: dispatch_queue_t, + ) -> dispatch_queue_t; + /// Creates a new dispatch queue to which you can submit blocks. + pub fn dispatch_queue_create( + label: *const c_char, + attr: dispatch_queue_attr_t, + ) -> dispatch_queue_t; + /// Returns the label you assigned to the dispatch queue at creation time. + pub fn dispatch_queue_get_label(queue: dispatch_queue_t) -> *const c_char; + /// Returns the quality-of-service class for the specified queue. + pub fn dispatch_queue_get_qos_class( + queue: dispatch_queue_t, + relative_priority_ptr: *mut c_int, + ) -> dispatch_qos_class_t; + /// Specifies the dispatch queue on which to perform work associated with the current object. + pub fn dispatch_set_target_queue(object: dispatch_object_t, queue: dispatch_queue_t); + /// Executes blocks submitted to the main queue. + pub fn dispatch_main() -> !; + /// Enqueues a block for execution at the specified time. + pub fn dispatch_after(when: dispatch_time_t, queue: dispatch_queue_t, block: dispatch_block_t); + /// Enqueues an app-defined function for execution at the specified time. + pub fn dispatch_after_f( + when: dispatch_time_t, + queue: dispatch_queue_t, + context: *mut c_void, + work: dispatch_function_t, + ); + /// Submits a barrier block for asynchronous execution and returns immediately. + pub fn dispatch_barrier_async(queue: dispatch_queue_t, block: dispatch_block_t); + /// Submits a barrier function for asynchronous execution and returns immediately. + pub fn dispatch_barrier_async_f( + queue: dispatch_queue_t, + context: *mut c_void, + work: dispatch_function_t, + ); + /// Submits a barrier block object for execution and waits until that block completes. + pub fn dispatch_barrier_sync(queue: dispatch_queue_t, block: dispatch_block_t); + /// Submits a barrier function for execution and waits until that function completes. + pub fn dispatch_barrier_sync_f( + queue: dispatch_queue_t, + context: *mut c_void, + work: dispatch_function_t, + ); + /// Submits a work item for synchronous execution and marks the work as a barrier for subsequent concurrent tasks. + pub fn dispatch_barrier_async_and_wait(queue: dispatch_queue_t, block: dispatch_block_t); + /// Submits a function-based work item for synchronous execution and marks the work as a barrier for subsequent concurrent tasks. + pub fn dispatch_barrier_async_and_wait_f( + queue: dispatch_queue_t, + context: *mut c_void, + work: dispatch_function_t, + ); + /// Sets the key/value data for the specified dispatch queue. + pub fn dispatch_queue_set_specific( + queue: dispatch_queue_t, + key: *const c_void, + context: *mut c_void, + destructor: dispatch_function_t, + ); + /// Gets the value for the key associated with the specified dispatch queue. + pub fn dispatch_queue_get_specific(queue: dispatch_queue_t, key: *const c_void) -> *mut c_void; + /// Returns the value for the key associated with the current dispatch queue. + pub fn dispatch_get_specific(key: *const c_void) -> *mut c_void; + #[cfg_attr( + any(target_os = "macos", target_os = "ios", target_os = "tvos"), + link_name = "dispatch_assert_queue$V2" + )] + /// Generates an assertion if the current block is not running on the specified dispatch queue. + pub fn dispatch_assert_queue(queue: dispatch_queue_t); + /// Generates an assertion if the current block is not running as a barrier on the specified dispatch queue. + pub fn dispatch_assert_queue_barrier(queue: dispatch_queue_t); + #[cfg_attr( + any(target_os = "macos", target_os = "ios", target_os = "tvos"), + link_name = "dispatch_assert_queue_not$V2" + )] + /// Generates an assertion if the current block is executing on the specified dispatch queue. + pub fn dispatch_assert_queue_not(queue: dispatch_queue_t); + + /// Creates new counting semaphore with an initial value. + pub fn dispatch_semaphore_create(value: isize) -> dispatch_semaphore_t; + /// Waits for (decrements) a semaphore. + pub fn dispatch_semaphore_wait(dsema: dispatch_semaphore_t, timeout: dispatch_time_t) -> usize; + /// Signals (increments) a semaphore. + pub fn dispatch_semaphore_signal(dsema: dispatch_semaphore_t) -> usize; + + pub static _dispatch_source_type_data_add: dispatch_source_type_s; + pub static _dispatch_source_type_data_or: dispatch_source_type_s; + pub static _dispatch_source_type_data_replace: dispatch_source_type_s; + pub static _dispatch_source_type_mach_send: dispatch_source_type_s; + pub static _dispatch_source_type_memorypressure: dispatch_source_type_s; + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos"))] + pub static _dispatch_source_type_proc: dispatch_source_type_s; + pub static _dispatch_source_type_read: dispatch_source_type_s; + pub static _dispatch_source_type_timer: dispatch_source_type_s; + #[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos"))] + pub static _dispatch_source_type_vnode: dispatch_source_type_s; + pub static _dispatch_source_type_write: dispatch_source_type_s; + /// Creates a new dispatch source to monitor low-level system events. + pub fn dispatch_source_create( + r#type: dispatch_source_type_t, + handle: usize, + mask: usize, + queue: dispatch_queue_t, + ) -> dispatch_source_t; + /// Sets the event handler block for the given dispatch source. + pub fn dispatch_source_set_event_handler(source: dispatch_source_t, handler: dispatch_block_t); + /// Sets the event handler function for the given dispatch source. + pub fn dispatch_source_set_event_handler_f( + source: dispatch_source_t, + handler: dispatch_function_t, + ); + /// Sets the cancellation handler block for the given dispatch source. + pub fn dispatch_source_set_cancel_handler(source: dispatch_source_t, handler: dispatch_block_t); + /// Sets the cancellation handler function for the given dispatch source. + pub fn dispatch_source_set_cancel_handler_f( + source: dispatch_source_t, + handler: dispatch_function_t, + ); + /// Asynchronously cancels the dispatch source, preventing any further invocation of its event handler block. + pub fn dispatch_source_cancel(source: dispatch_source_t); + /// Tests whether the given dispatch source has been canceled. + pub fn dispatch_source_testcancel(source: dispatch_source_t) -> usize; + /// Returns the underlying system handle associated with the specified dispatch source. + pub fn dispatch_source_get_handle(source: dispatch_source_t) -> usize; + /// Returns the mask of events monitored by the dispatch source. + pub fn dispatch_source_get_mask(source: dispatch_source_t) -> usize; + /// Returns pending data for the dispatch source. + pub fn dispatch_source_get_data(source: dispatch_source_t) -> usize; + /// Merges data into a dispatch source and submits its event handler block to its target queue. + pub fn dispatch_source_merge_data(source: dispatch_source_t, value: usize); + /// Sets a start time, interval, and leeway value for a timer source. + pub fn dispatch_source_set_timer( + source: dispatch_source_t, + start: dispatch_time_t, + interval: u64, + leeway: u64, + ); + /// Sets the registration handler block for the given dispatch source. + pub fn dispatch_source_set_registration_handler( + source: dispatch_source_t, + handler: dispatch_block_t, + ); + /// Sets the registration handler function for the given dispatch source. + pub fn dispatch_source_set_registration_handler_f( + source: dispatch_source_t, + handler: dispatch_function_t, + ); + + /// Creates a [dispatch_time_t] relative to the default clock or modifies an existing [dispatch_time_t]. + pub fn dispatch_time(when: dispatch_time_t, delta: i64) -> dispatch_time_t; + /// Creates a [dispatch_time_t] using an absolute time according to the wall clock. + pub fn dispatch_walltime(when: *const libc::timespec, delta: i64) -> dispatch_time_t; + + /// Creates a new workloop with the specified label. + pub fn dispatch_workloop_create(label: *const c_char) -> dispatch_workloop_t; + /// Creates a new inactive workloop with the specified label. + pub fn dispatch_workloop_create_inactive(label: *const c_char) -> dispatch_workloop_t; + /// Configures how the workloop manages the autorelease pools for the blocks it executes. + pub fn dispatch_workloop_set_autorelease_frequency( + workloop: dispatch_workloop_t, + frequency: dispatch_autorelease_frequency_t, + ); + // TODO: dispatch_workloop_set_os_workgroup +} diff --git a/crates/dispatch2/src/group.rs b/crates/dispatch2/src/group.rs new file mode 100644 index 000000000..b2fc46efd --- /dev/null +++ b/crates/dispatch2/src/group.rs @@ -0,0 +1,148 @@ +//! Dispatch group definition. + +use std::time::Duration; + +use core::ffi::c_void; + +use super::object::DispatchObject; +use super::queue::Queue; +use super::utils::function_wrapper; +use super::{ffi::*, WaitError}; + +/// Dispatch group. +#[derive(Debug, Clone)] +pub struct Group { + dispatch_object: DispatchObject, +} + +/// Dispatch group guard. +#[derive(Debug)] +pub struct GroupGuard(Group, bool); + +impl Group { + /// Creates a new [Group]. + pub fn new() -> Option { + // Safety: valid to call. + let object = unsafe { dispatch_group_create() }; + + if object.is_null() { + return None; + } + + // Safety: object cannot be null. + let dispatch_object = unsafe { DispatchObject::new_owned(object as *mut _) }; + + Some(Group { dispatch_object }) + } + + /// Submit a function to a [Queue] and associates it with the [Group]. + pub fn exec_async(&self, queue: &Queue, work: F) + where + F: Send + FnOnce(), + { + let work_boxed = Box::leak(Box::new(work)) as *mut _ as *mut c_void; + + // Safety: All parameters cannot be null. + unsafe { + dispatch_group_async_f( + self.as_raw(), + queue.as_raw(), + work_boxed, + function_wrapper::, + ); + } + } + + /// Wait synchronously for the previously submitted functions to finish. + /// + /// # Errors + /// + /// Return [WaitError::TimeOverflow] if the passed ``timeout`` is too big. + /// + /// Return [WaitError::Timeout] in case of timeout. + pub fn wait(&self, timeout: Option) -> Result<(), WaitError> { + let timeout = if let Some(timeout) = timeout { + dispatch_time_t::try_from(timeout).map_err(|_| WaitError::TimeOverflow)? + } else { + DISPATCH_TIME_FOREVER + }; + + // Safety: object cannot be null and timeout is valid. + let result = unsafe { dispatch_group_wait(self.as_raw(), timeout) }; + + match result { + 0 => Ok(()), + _ => Err(WaitError::Timeout), + } + } + + /// Schedule a function to be submitted to a [Queue] when a group of previously submitted functions have completed. + pub fn notify(&self, queue: &Queue, work: F) + where + F: Send + FnOnce(), + { + let work_boxed = Box::leak(Box::new(work)) as *mut _ as *mut c_void; + + // Safety: All parameters cannot be null. + unsafe { + dispatch_group_notify_f( + self.as_raw(), + queue.as_raw(), + work_boxed, + function_wrapper::, + ); + } + } + + /// Explicitly indicates that the function has entered the [Group]. + pub fn enter(&self) -> GroupGuard { + // Safety: object cannot be null. + unsafe { + dispatch_group_enter(self.as_raw()); + } + + GroupGuard(self.clone(), false) + } + + /// Set the finalizer function for the object. + pub fn set_finalizer(&mut self, destructor: F) + where + F: Send + FnOnce(), + { + self.dispatch_object.set_finalizer(destructor); + } + + /// Get the raw [dispatch_group_t] value. + /// + /// # Safety + /// + /// - Object shouldn't be released manually. + pub const unsafe fn as_raw(&self) -> dispatch_group_t { + self.dispatch_object.as_raw() + } +} + +impl GroupGuard { + /// Explicitly indicates that the function in the [Group] finished executing. + pub fn leave(mut self) { + // Safety: object cannot be null. + unsafe { + dispatch_group_leave(self.0.as_raw()); + } + + self.1 = true; + } +} + +impl Drop for GroupGuard { + fn drop(&mut self) { + if !self.1 { + // Safety: object cannot be null. + unsafe { + dispatch_group_leave(self.0.as_raw()); + } + + self.1 = true; + } + } +} diff --git a/crates/dispatch2/src/lib.rs b/crates/dispatch2/src/lib.rs new file mode 100644 index 000000000..631833f75 --- /dev/null +++ b/crates/dispatch2/src/lib.rs @@ -0,0 +1,81 @@ +#![allow(unused_unsafe, unreachable_patterns)] +#![deny( + missing_docs, + clippy::undocumented_unsafe_blocks, + clippy::missing_safety_doc +)] + +//! +//! Apple Dispatch (Grand Central Dispatch) +//! +//! This crate allows interaction with the [Apple Dispatch](https://developer.apple.com/documentation/dispatch) library in a safe (``dispatch2`` module) and unsafe (``ffi`` module) way. +//! +//! # Example: +//! +//! ``` +//! use dispatch2::{Queue, QueueAttribute}; +//! +//! fn main() { +//! let queue = Queue::new("example_queue", QueueAttribute::Serial); +//! queue.exec_async(|| println!("Hello")); +//! queue.exec_sync(|| println!("World")); +//! } +//! ``` + +use self::ffi::dispatch_qos_class_t; + +pub mod ffi; +pub mod group; +pub mod object; +pub mod queue; +pub mod semaphore; +mod utils; + +/// Wait error. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum WaitError { + /// The given timeout value will result in an overflow when converting to dispatch time. + TimeOverflow, + /// The operation timed out. + Timeout, +} + +/// Quality of service that specify the priorities for executing tasks. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum QualityOfServiceClass { + /// Quality of service for user-interactive tasks. + UserInteractive, + /// Quality of service for tasks that prevent the user from actively using your app. + UserInitiated, + /// Default Quality of service. + Default, + /// Quality of service for tasks that the user does not track actively. + Utility, + /// Quality of service for maintenance or cleanup tasks. + Background, + /// The absence of a Quality of service. + Unspecified, +} + +impl From for dispatch_qos_class_t { + fn from(value: QualityOfServiceClass) -> Self { + match value { + QualityOfServiceClass::UserInteractive => { + dispatch_qos_class_t::QOS_CLASS_USER_INTERACTIVE + } + QualityOfServiceClass::UserInitiated => dispatch_qos_class_t::QOS_CLASS_USER_INITIATED, + QualityOfServiceClass::Default => dispatch_qos_class_t::QOS_CLASS_DEFAULT, + QualityOfServiceClass::Utility => dispatch_qos_class_t::QOS_CLASS_UTILITY, + QualityOfServiceClass::Background => dispatch_qos_class_t::QOS_CLASS_BACKGROUND, + QualityOfServiceClass::Unspecified => dispatch_qos_class_t::QOS_CLASS_UNSPECIFIED, + _ => panic!("Unknown QualityOfServiceClass value: {:?}", value), + } + } +} + +pub use group::*; +pub use object::*; +pub use queue::*; +pub use semaphore::*; diff --git a/crates/dispatch2/src/object.rs b/crates/dispatch2/src/object.rs new file mode 100644 index 000000000..d1a0371b1 --- /dev/null +++ b/crates/dispatch2/src/object.rs @@ -0,0 +1,171 @@ +//! Dispatch object definition. + +use super::{ffi::*, queue::Queue, utils::function_wrapper, QualityOfServiceClass}; + +/// Error returned by [DispatchObject::set_target_queue]. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum TargetQueueError { + /// The [DispatchObject] is already active. + ObjectAlreadyActive, +} + +/// Error returned by [DispatchObject::set_qos_class_floor]. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum QualityOfServiceClassFloorError { + /// The relative priority is invalid. + InvalidRelativePriority, +} + +/// Represent a dispatch object. +#[repr(C)] +#[derive(Debug)] +pub struct DispatchObject { + object: *mut T, + is_activated: bool, +} + +impl DispatchObject { + /// Create a new owned instance + /// + /// # Safety + /// + /// - ``object`` is expected to be a dispatch object that is owned. + pub unsafe fn new_owned(object: *mut T) -> Self { + Self { + object, + is_activated: false, + } + } + + /// Create a new shared instance + /// + /// # Safety + /// + /// - ``object`` is expected to be a dispatch object that is shared. + pub unsafe fn new_shared(object: *mut T) -> Self { + let result = Self { + object, + is_activated: false, + }; + + // Safety: We own a reference to the object. + unsafe { + dispatch_retain(result.object as *mut _); + } + + result + } + + /// Set the finalizer function for the object. + pub fn set_finalizer(&mut self, destructor: F) + where + F: Send + FnOnce(), + { + let destructor_boxed = Box::leak(Box::new(destructor)) as *mut F as *mut _; + + // Safety: As this use the dispatch object's context, and because we need some way to wrap the Rust function, we set the context. + // Once the finalizer is executed, the context will be dangling. + // This isn't an issue as the context shall not be accessed after the dispatch object is destroyed. + unsafe { + dispatch_set_context(self.object as *mut _, destructor_boxed); + dispatch_set_finalizer_f(self.object as *mut _, function_wrapper::) + } + } + + /// Set the target [Queue] of this object. + /// + /// # Safety + /// + /// - DispatchObject should be a queue or queue source. + pub unsafe fn set_target_queue(&self, queue: &Queue) -> Result<(), TargetQueueError> { + if self.is_activated { + return Err(TargetQueueError::ObjectAlreadyActive); + } + + // Safety: object and queue cannot be null. + unsafe { + dispatch_set_target_queue(self.as_raw() as *mut _, queue.as_raw()); + } + + Ok(()) + } + + /// Set the QOS class floor on a dispatch queue, source or workloop. + /// + /// # Safety + /// + /// - DispatchObject should be a queue or queue source. + pub unsafe fn set_qos_class_floor( + &self, + qos_class: QualityOfServiceClass, + relative_priority: i32, + ) -> Result<(), QualityOfServiceClassFloorError> { + if !(QOS_MIN_RELATIVE_PRIORITY..=0).contains(&relative_priority) { + return Err(QualityOfServiceClassFloorError::InvalidRelativePriority); + } + + // Safety: Safe as relative_priority can only be valid. + unsafe { + dispatch_set_qos_class_floor( + self.as_raw() as *mut _, + dispatch_qos_class_t::from(qos_class), + relative_priority, + ); + } + + Ok(()) + } + + /// Activate the object. + pub fn activate(&mut self) { + // Safety: object cannot be null. + unsafe { + dispatch_activate(self.as_raw() as *mut _); + } + + self.is_activated = true; + } + + /// Suspend the invocation of functions on the object. + pub fn suspend(&self) { + // Safety: object cannot be null. + unsafe { + dispatch_suspend(self.as_raw() as *mut _); + } + } + + /// Resume the invocation of functions on the object. + pub fn resume(&self) { + // Safety: object cannot be null. + unsafe { + dispatch_resume(self.as_raw() as *mut _); + } + } + + /// Get the raw object value. + /// + /// # Safety + /// + /// - Object shouldn't be released manually. + pub const unsafe fn as_raw(&self) -> *mut T { + self.object + } +} + +impl Clone for DispatchObject { + fn clone(&self) -> Self { + // Safety: We own a reference to the object. + unsafe { Self::new_shared(self.object) } + } +} + +impl Drop for DispatchObject { + fn drop(&mut self) { + // Safety: We own a reference to the object. + unsafe { + dispatch_release(self.object as *mut _); + } + } +} diff --git a/crates/dispatch2/src/queue.rs b/crates/dispatch2/src/queue.rs new file mode 100644 index 000000000..3e6094452 --- /dev/null +++ b/crates/dispatch2/src/queue.rs @@ -0,0 +1,437 @@ +//! Dispatch queue definition. + +use std::borrow::{Borrow, BorrowMut}; +use std::ffi::CString; +use std::ops::{Deref, DerefMut}; +use std::time::Duration; + +use super::object::{DispatchObject, QualityOfServiceClassFloorError, TargetQueueError}; +use super::utils::function_wrapper; +use super::{ffi::*, QualityOfServiceClass}; + +/// Error returned by [Queue::after]. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum QueueAfterError { + /// The given timeout value will result in an overflow when converting to dispatch time. + TimeOverflow, +} + +/// Queue type attribute. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum QueueAttribute { + /// Serial queue. + Serial, + /// Concurrent queue. + Concurrent, +} + +impl From for dispatch_queue_attr_t { + fn from(value: QueueAttribute) -> Self { + match value { + QueueAttribute::Serial => DISPATCH_QUEUE_SERIAL, + QueueAttribute::Concurrent => DISPATCH_QUEUE_CONCURRENT as *const _ as *mut _, + _ => panic!("Unknown QueueAttribute value: {:?}", value), + } + } +} + +/// Queue priority. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum QueuePriority { + /// High priority. + High, + /// Default priority. + Default, + /// Low priority. + Low, + /// Background priority. + Background, +} + +impl From for dispatch_queue_priority_t { + fn from(value: QueuePriority) -> Self { + match value { + QueuePriority::High => dispatch_queue_priority_t::DISPATCH_QUEUE_PRIORITY_HIGH, + QueuePriority::Default => dispatch_queue_priority_t::DISPATCH_QUEUE_PRIORITY_DEFAULT, + QueuePriority::Low => dispatch_queue_priority_t::DISPATCH_QUEUE_PRIORITY_LOW, + QueuePriority::Background => { + dispatch_queue_priority_t::DISPATCH_QUEUE_PRIORITY_BACKGROUND + } + _ => panic!("Unknown QueuePriority value: {:?}", value), + } + } +} + +/// Global queue identifier definition for [Queue::new] and [Queue::new_with_target]. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum GlobalQueueIdentifier { + /// Standard priority based queue. + Priority(QueuePriority), + /// Quality of service priority based queue. + QualityOfService(QualityOfServiceClass), +} + +impl GlobalQueueIdentifier { + /// Convert and consume [GlobalQueueIdentifier] into its raw value. + pub fn to_identifier(self) -> usize { + match self { + GlobalQueueIdentifier::Priority(queue_priority) => { + dispatch_queue_priority_t::from(queue_priority).0 as usize + } + GlobalQueueIdentifier::QualityOfService(qos_class) => { + dispatch_qos_class_t::from(qos_class).0 as usize + } + } + } +} + +/// Auto release frequency for [WorkloopQueue::set_autorelease_frequency]. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] +pub enum DispatchAutoReleaseFrequency { + /// Inherit autorelease frequency from the target [Queue]. + Inherit, + /// Configure an autorelease pool before the execution of a function and releases the objects in that pool after the function finishes executing. + WorkItem, + /// Never setup an autorelease pool. + Never, +} + +impl From for dispatch_autorelease_frequency_t { + fn from(value: DispatchAutoReleaseFrequency) -> Self { + match value { + DispatchAutoReleaseFrequency::Inherit => { + dispatch_autorelease_frequency_t::DISPATCH_AUTORELEASE_FREQUENCY_INHERIT + } + DispatchAutoReleaseFrequency::WorkItem => { + dispatch_autorelease_frequency_t::DISPATCH_AUTORELEASE_FREQUENCY_WORK_ITEM + } + DispatchAutoReleaseFrequency::Never => { + dispatch_autorelease_frequency_t::DISPATCH_AUTORELEASE_FREQUENCY_NEVER + } + _ => panic!("Unknown DispatchAutoReleaseFrequency value: {:?}", value), + } + } +} + +/// Dispatch queue. +#[derive(Debug, Clone)] +pub struct Queue { + dispatch_object: DispatchObject, + is_workloop: bool, +} + +impl Queue { + /// Create a new [Queue]. + pub fn new(label: &str, queue_attribute: QueueAttribute) -> Self { + let label = CString::new(label).expect("Invalid label!"); + + // Safety: label and queue_attribute can only be valid. + let object = unsafe { + dispatch_queue_create(label.as_ptr(), dispatch_queue_attr_t::from(queue_attribute)) + }; + + assert!(!object.is_null(), "dispatch_queue_create shouldn't fail!"); + + // Safety: object cannot be null. + let dispatch_object = unsafe { DispatchObject::new_owned(object as *mut _) }; + + Queue { + dispatch_object, + is_workloop: false, + } + } + + /// Create a new [Queue] with a given target [Queue]. + pub fn new_with_target(label: &str, queue_attribute: QueueAttribute, target: &Queue) -> Self { + let label = CString::new(label).expect("Invalid label!"); + + // Safety: label, queue_attribute and target can only be valid. + let object = unsafe { + dispatch_queue_create_with_target( + label.as_ptr(), + dispatch_queue_attr_t::from(queue_attribute), + target.dispatch_object.as_raw(), + ) + }; + + assert!(!object.is_null(), "dispatch_queue_create shouldn't fail!"); + + // Safety: object cannot be null. + let dispatch_object = unsafe { DispatchObject::new_owned(object as *mut _) }; + + // NOTE: dispatch_queue_create_with_target is in charge of retaining the target Queue. + + Queue { + dispatch_object, + is_workloop: false, + } + } + + /// Return a system-defined global concurrent [Queue] with the priority derivated from [GlobalQueueIdentifier]. + pub fn global_queue(identifier: GlobalQueueIdentifier) -> Self { + let raw_identifier = identifier.to_identifier(); + + // Safety: raw_identifier cannot be invalid, flags is reserved. + let object = unsafe { dispatch_get_global_queue(raw_identifier, 0) }; + + assert!( + !object.is_null(), + "dispatch_get_global_queue shouldn't fail!" + ); + + // Safety: object cannot be null. + let dispatch_object = unsafe { DispatchObject::new_shared(object as *mut _) }; + + Queue { + dispatch_object, + is_workloop: false, + } + } + + /// Submit a function for synchronous execution on the [Queue]. + pub fn exec_sync(&self, work: F) + where + F: Send + FnOnce(), + { + assert!(!self.is_workloop, "exec_sync is invalid for WorkloopQueue"); + + let work_boxed = Box::leak(Box::new(work)) as *mut F as *mut _; + + // Safety: object cannot be null and work is wrapped to avoid ABI incompatibility. + unsafe { dispatch_sync_f(self.as_raw(), work_boxed, function_wrapper::) } + } + + /// Submit a function for asynchronous execution on the [Queue]. + pub fn exec_async(&self, work: F) + where + F: Send + FnOnce(), + { + let work_boxed = Box::leak(Box::new(work)) as *mut F as *mut _; + + // Safety: object cannot be null and work is wrapped to avoid ABI incompatibility. + unsafe { dispatch_async_f(self.as_raw(), work_boxed, function_wrapper::) } + } + + /// Enqueue a function for execution at the specified time on the [Queue]. + pub fn after(&self, wait_time: Duration, work: F) -> Result<(), QueueAfterError> + where + F: Send + FnOnce(), + { + let when = + dispatch_time_t::try_from(wait_time).map_err(|_| QueueAfterError::TimeOverflow)?; + let work_boxed = Box::leak(Box::new(work)) as *mut F as *mut _; + + // Safety: object cannot be null and work is wrapped to avoid ABI incompatibility. + unsafe { + dispatch_after_f(when, self.as_raw(), work_boxed, function_wrapper::); + } + + Ok(()) + } + + /// Enqueue a barrier function for asynchronous execution on the [Queue] and return immediately. + pub fn barrier_async(&self, work: F) + where + F: Send + FnOnce(), + { + let work_boxed = Box::leak(Box::new(work)) as *mut F as *mut _; + + // Safety: object cannot be null and work is wrapped to avoid ABI incompatibility. + unsafe { dispatch_barrier_async_f(self.as_raw(), work_boxed, function_wrapper::) } + } + + /// Enqueue a barrier function for synchronous execution on the [Queue] and wait until that function completes. + pub fn barrier_sync(&self, work: F) + where + F: Send + FnOnce(), + { + let work_boxed = Box::leak(Box::new(work)) as *mut F as *mut _; + + // Safety: object cannot be null and work is wrapped to avoid ABI incompatibility. + unsafe { dispatch_barrier_sync_f(self.as_raw(), work_boxed, function_wrapper::) } + } + + /// Submit a function for synchronous execution and mark the function as a barrier for subsequent concurrent tasks. + pub fn barrier_async_and_wait(&self, work: F) + where + F: Send + FnOnce(), + { + let work_boxed = Box::leak(Box::new(work)) as *mut F as *mut _; + + // Safety: object cannot be null and work is wrapped to avoid ABI incompatibility. + unsafe { + dispatch_barrier_async_and_wait_f(self.as_raw(), work_boxed, function_wrapper::) + } + } + + /// Sets a function at the given key that will be executed at [Queue] destruction. + pub fn set_specific(&mut self, key: usize, destructor: F) + where + F: Send + FnOnce(), + { + let destructor_boxed = Box::leak(Box::new(destructor)) as *mut F as *mut _; + + // Safety: object cannot be null and destructor is wrapped to avoid ABI incompatibility. + unsafe { + dispatch_queue_set_specific( + self.as_raw(), + key as *const _, + destructor_boxed, + function_wrapper::, + ) + } + } + + /// Set the finalizer function for the [Queue]. + pub fn set_finalizer(&mut self, destructor: F) + where + F: Send + FnOnce(), + { + self.dispatch_object.set_finalizer(destructor); + } + + /// Set the target [Queue] of this [Queue]. + pub fn set_target_queue(&self, queue: &Queue) -> Result<(), TargetQueueError> { + // Safety: We are in Queue instance. + unsafe { self.dispatch_object.set_target_queue(queue) } + } + + /// Set the QOS class floor of the [Queue]. + pub fn set_qos_class_floor( + &self, + qos_class: QualityOfServiceClass, + relative_priority: i32, + ) -> Result<(), QualityOfServiceClassFloorError> { + // Safety: We are in Queue instance. + unsafe { + self.dispatch_object + .set_qos_class_floor(qos_class, relative_priority) + } + } + + /// Activate the [Queue]. + pub fn activate(&mut self) { + self.dispatch_object.activate(); + } + + /// Suspend the invocation of functions on the [Queue]. + pub fn suspend(&self) { + self.dispatch_object.suspend(); + } + + /// Resume the invocation of functions on the [Queue]. + pub fn resume(&self) { + self.dispatch_object.resume(); + } + + /// Get the raw [dispatch_queue_t] value. + /// + /// # Safety + /// + /// - Object shouldn't be released manually. + pub const unsafe fn as_raw(&self) -> dispatch_queue_t { + self.dispatch_object.as_raw() + } +} + +/// Dispatch workloop queue. +#[derive(Debug, Clone)] +pub struct WorkloopQueue { + queue: Queue, +} + +impl WorkloopQueue { + /// Create a new [WorkloopQueue]. + pub fn new(label: &str, inactive: bool) -> Self { + let label = CString::new(label).expect("Invalid label!"); + + // Safety: label can only be valid. + let object = unsafe { + if inactive { + dispatch_workloop_create_inactive(label.as_ptr()) + } else { + dispatch_workloop_create(label.as_ptr()) + } + }; + + assert!(!object.is_null(), "dispatch_queue_create shouldn't fail!"); + + // Safety: object cannot be null. + let dispatch_object = unsafe { DispatchObject::new_owned(object as *mut _) }; + + WorkloopQueue { + queue: Queue { + dispatch_object, + is_workloop: true, + }, + } + } + + /// Configure how the [WorkloopQueue] manage the autorelease pools for the functions it executes. + pub fn set_autorelease_frequency(&self, frequency: DispatchAutoReleaseFrequency) { + // Safety: object and frequency can only be valid. + unsafe { + dispatch_workloop_set_autorelease_frequency( + self.as_raw(), + dispatch_autorelease_frequency_t::from(frequency), + ); + } + } + + /// Get the raw [dispatch_workloop_t] value. + /// + /// # Safety + /// + /// - Object shouldn't be released manually. + pub const unsafe fn as_raw(&self) -> dispatch_workloop_t { + self.queue.as_raw() as dispatch_workloop_t + } +} + +impl Deref for WorkloopQueue { + type Target = Queue; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.queue + } +} + +impl DerefMut for WorkloopQueue { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.queue + } +} + +impl AsRef for WorkloopQueue { + #[inline] + fn as_ref(&self) -> &Queue { + self + } +} + +impl AsMut for WorkloopQueue { + #[inline] + fn as_mut(&mut self) -> &mut Queue { + &mut *self + } +} + +impl Borrow for WorkloopQueue { + #[inline] + fn borrow(&self) -> &Queue { + self + } +} + +impl BorrowMut for WorkloopQueue { + #[inline] + fn borrow_mut(&mut self) -> &mut Queue { + &mut *self + } +} diff --git a/crates/dispatch2/src/semaphore.rs b/crates/dispatch2/src/semaphore.rs new file mode 100644 index 000000000..443dd5feb --- /dev/null +++ b/crates/dispatch2/src/semaphore.rs @@ -0,0 +1,106 @@ +//! Dispatch semaphore definition. + +use std::time::Duration; + +use super::ffi::*; +use super::object::DispatchObject; +use super::WaitError; + +/// Dispatch semaphore. +#[derive(Debug, Clone)] +pub struct Semaphore { + dispatch_object: DispatchObject, +} + +impl Semaphore { + /// Creates a new [Semaphore] with an initial value. + /// + /// Returns None if value is negative or if creation failed. + pub fn new(value: isize) -> Option { + // Per documentation creating a semaphore with a negative size isn't allowed. + if value < 0 { + return None; + } + + // Safety: value is valid + let object = unsafe { dispatch_semaphore_create(value) }; + + if object.is_null() { + return None; + } + + // Safety: object cannot be null. + let dispatch_object = unsafe { DispatchObject::new_owned(object as *mut _) }; + + Some(Semaphore { dispatch_object }) + } + + /// Attempt to aquire the [Semaphore] and return a [SemaphoreGuard]. + /// + /// # Errors + /// + /// Return [WaitError::TimeOverflow] if the passed ``timeout`` is too big. + /// + /// Return [WaitError::Timeout] in case of timeout. + pub fn try_acquire(&self, timeout: Option) -> Result { + let timeout = if let Some(timeout) = timeout { + dispatch_time_t::try_from(timeout).map_err(|_| WaitError::TimeOverflow)? + } else { + DISPATCH_TIME_FOREVER + }; + + // Safety: Semaphore cannot be null. + let result = unsafe { dispatch_semaphore_wait(self.as_raw(), timeout) }; + + match result { + 0 => Ok(SemaphoreGuard(self.clone(), false)), + _ => Err(WaitError::Timeout), + } + } + + /// Set the finalizer function for the object. + pub fn set_finalizer(&mut self, destructor: F) + where + F: Send + FnOnce(), + { + self.dispatch_object.set_finalizer(destructor); + } + + /// Get the raw [dispatch_semaphore_t] value. + /// + /// # Safety + /// + /// - Object shouldn't be released manually. + pub const unsafe fn as_raw(&self) -> dispatch_semaphore_t { + self.dispatch_object.as_raw() + } +} + +/// Dispatch semaphore guard. +#[derive(Debug)] +pub struct SemaphoreGuard(Semaphore, bool); + +impl SemaphoreGuard { + /// Release the [Semaphore]. + pub fn release(mut self) -> bool { + // Safety: Semaphore cannot be null. + let result = unsafe { dispatch_semaphore_signal(self.0.as_raw()) }; + + self.1 = true; + + result != 0 + } +} + +impl Drop for SemaphoreGuard { + fn drop(&mut self) { + if !self.1 { + // Safety: Semaphore cannot be null. + unsafe { + dispatch_semaphore_signal(self.0.as_raw()); + } + + self.1 = true; + } + } +} diff --git a/crates/dispatch2/src/utils.rs b/crates/dispatch2/src/utils.rs new file mode 100644 index 000000000..59e9301ff --- /dev/null +++ b/crates/dispatch2/src/utils.rs @@ -0,0 +1,31 @@ +use std::time::Duration; + +use core::ffi::c_void; + +use super::ffi::{dispatch_time, dispatch_time_t, DISPATCH_TIME_NOW}; + +impl TryFrom for dispatch_time_t { + type Error = (); + + fn try_from(value: Duration) -> Result { + let secs = value.as_secs() as i64; + + secs.checked_mul(1_000_000_000) + .and_then(|x| x.checked_add(i64::from(value.subsec_nanos()))) + .map(|delta| { + // Safety: delta cannot overflow + unsafe { dispatch_time(DISPATCH_TIME_NOW, delta) } + }) + .ok_or(()) + } +} + +pub(crate) extern "C" fn function_wrapper(work_boxed: *mut c_void) +where + F: FnOnce(), +{ + // Safety: we reconstruct from a Box. + let work = unsafe { Box::from_raw(work_boxed as *mut F) }; + + (*work)(); +}