Skip to main content

dispatch2/
object.rs

1use core::ffi::{c_uint, c_void};
2use core::ptr::NonNull;
3
4use alloc::boxed::Box;
5
6use crate::dispatch_function_t;
7use crate::generated::dispatch_get_context;
8use crate::{
9    generated::{
10        dispatch_activate, dispatch_resume, dispatch_set_context, dispatch_set_finalizer_f,
11        dispatch_set_qos_class_floor, dispatch_set_target_queue, dispatch_suspend,
12    },
13    DispatchRetained,
14};
15
16use super::{utils::function_wrapper, DispatchQueue};
17
18enum_with_val! {
19    /// Quality-of-service classes that specify the priorities for executing tasks.
20    #[doc(alias = "dispatch_qos_class_t")]
21    #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
22    pub struct DispatchQoS(pub c_uint) {
23        /// Quality of service for user-interactive tasks.
24        #[doc(alias = "QOS_CLASS_USER_INTERACTIVE")]
25        UserInteractive = 0x21,
26        /// Quality of service for tasks that prevent the user from actively using your app.
27        #[doc(alias = "QOS_CLASS_USER_INITIATED")]
28        UserInitiated = 0x19,
29        /// Default Quality of service.
30        #[doc(alias = "QOS_CLASS_DEFAULT")]
31        Default = 0x15,
32        /// Quality of service for tasks that the user does not track actively.
33        #[doc(alias = "QOS_CLASS_UTILITY")]
34        Utility = 0x11,
35        /// Quality of service for maintenance or cleanup tasks.
36        #[doc(alias = "QOS_CLASS_BACKGROUND")]
37        Background = 0x09,
38        /// The absence of a Quality of service.
39        #[doc(alias = "QOS_CLASS_UNSPECIFIED")]
40        Unspecified = 0x00,
41    }
42}
43
44#[allow(missing_docs)] // TODO
45pub const QOS_MIN_RELATIVE_PRIORITY: i32 = -15;
46
47/// Error returned by [DispatchObject::set_qos_class_floor].
48#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
49#[non_exhaustive]
50pub enum QualityOfServiceClassFloorError {
51    /// The relative priority is invalid.
52    InvalidRelativePriority,
53}
54
55/// Types that represent dispatch objects.
56///
57/// # Safety
58///
59/// The object must represent a dispatch object, and be usable in
60/// `dispatch_retain` / `dispatch_release`.
61#[doc(alias = "dispatch_object_t")]
62pub unsafe trait DispatchObject {
63    /// Increment the reference count of the object.
64    ///
65    /// This extends the duration in which the object is alive by detaching it
66    /// from the lifetime information carried by the reference.
67    #[doc(alias = "dispatch_retain")]
68    #[inline]
69    fn retain(&self) -> DispatchRetained<Self> {
70        let ptr: NonNull<Self> = NonNull::from(self);
71        // SAFETY:
72        // - The pointer is valid since it came from `&self`.
73        // - The lifetime of the pointer itself is extended, but any lifetime
74        //   that the object may carry is still kept within the type itself.
75        unsafe { DispatchRetained::retain(ptr) }
76    }
77
78    /// TODO.
79    ///
80    /// # Safety
81    ///
82    /// TODO.
83    #[doc(alias = "dispatch_get_context")]
84    #[inline]
85    fn context(&self) -> *mut c_void {
86        dispatch_get_context(self.as_raw())
87    }
88
89    /// TODO.
90    ///
91    /// # Safety
92    ///
93    /// TODO.
94    #[doc(alias = "dispatch_set_context")]
95    #[inline]
96    unsafe fn set_context(&self, context: *mut c_void) {
97        // SAFETY: Upheld by the caller.
98        unsafe { dispatch_set_context(self.as_raw(), context) }
99    }
100
101    /// TODO.
102    ///
103    /// # Safety
104    ///
105    /// TODO.
106    #[doc(alias = "dispatch_set_finalizer_f")]
107    #[inline]
108    unsafe fn set_finalizer_f(&self, finalizer: dispatch_function_t) {
109        // SAFETY: Upheld by the caller.
110        unsafe { dispatch_set_finalizer_f(self.as_raw(), finalizer) }
111    }
112
113    /// Set the finalizer function for the object.
114    #[inline]
115    fn set_finalizer<F>(&self, destructor: F)
116    where
117        F: Send + FnOnce(),
118    {
119        let destructor_boxed = Box::into_raw(Box::new(destructor)).cast();
120
121        // Safety: As this use the dispatch object's context, and because we need some way to wrap the Rust function, we set the context.
122        //         Once the finalizer is executed, the context will be dangling.
123        //         This isn't an issue as the context shall not be accessed after the dispatch object is destroyed.
124        unsafe {
125            self.set_context(destructor_boxed);
126            self.set_finalizer_f(function_wrapper::<F>)
127        }
128    }
129
130    /// Set the target [`DispatchQueue`] of this object.
131    ///
132    /// # Aborts
133    ///
134    /// Aborts if the object has been activated.
135    ///
136    /// # Safety
137    ///
138    /// - There must not be a cycle in the hierarchy of queues.
139    #[doc(alias = "dispatch_set_target_queue")]
140    #[inline]
141    unsafe fn set_target_queue(&self, queue: &DispatchQueue) {
142        // SAFETY: `object` and `queue` cannot be null, rest is upheld by caller.
143        unsafe { dispatch_set_target_queue(self.as_raw(), Some(queue)) };
144    }
145
146    /// Set the QOS class floor on a dispatch queue, source or workloop.
147    ///
148    /// # Safety
149    ///
150    /// - DispatchObject should be a queue or queue source.
151    #[inline]
152    unsafe fn set_qos_class_floor(
153        &self,
154        qos_class: DispatchQoS,
155        relative_priority: i32,
156    ) -> Result<(), QualityOfServiceClassFloorError> {
157        if !(QOS_MIN_RELATIVE_PRIORITY..=0).contains(&relative_priority) {
158            return Err(QualityOfServiceClassFloorError::InvalidRelativePriority);
159        }
160
161        // SAFETY: Safe as relative_priority can only be valid.
162        unsafe { dispatch_set_qos_class_floor(self.as_raw(), qos_class, relative_priority) };
163
164        Ok(())
165    }
166
167    /// Activate the object.
168    #[inline]
169    fn activate(&self) {
170        dispatch_activate(self.as_raw());
171    }
172
173    /// Suspend the invocation of functions on the object.
174    #[inline]
175    fn suspend(&self) {
176        dispatch_suspend(self.as_raw());
177    }
178
179    /// Resume the invocation of functions on the object.
180    #[inline]
181    fn resume(&self) {
182        dispatch_resume(self.as_raw());
183    }
184
185    #[inline]
186    #[doc(hidden)]
187    fn as_raw(&self) -> NonNull<dispatch_object_s> {
188        NonNull::from(self).cast()
189    }
190}
191
192// Helper to allow generated/mod.rs to emit the functions it needs to.
193//
194// This is private because we do not want users to hold a `dispatch_object_t`
195// directly, as there's no way to safely down- nor upcast to the other types.
196// Instead, they should use `DispatchObject`.
197mod private {
198    #[allow(non_camel_case_types)]
199    #[repr(C)]
200    #[derive(Debug)]
201    pub struct dispatch_object_s {
202        /// opaque value
203        _inner: [u8; 0],
204        _p: crate::OpaqueData,
205    }
206
207    #[cfg(feature = "objc2")]
208    // SAFETY: Dispatch types are internally objects.
209    unsafe impl objc2::encode::RefEncode for dispatch_object_s {
210        const ENCODING_REF: objc2::encode::Encoding = objc2::encode::Encoding::Object;
211    }
212}
213
214pub(crate) use private::dispatch_object_s;