1#![allow(clippy::missing_panics_doc)]
18
19use crate::utils::panic_safe;
30use std::ffi::c_void;
31use std::fmt;
32use std::time::Duration;
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
50pub enum DispatchQoS {
51 Background = 0,
53 Utility = 1,
55 #[default]
57 Default = 2,
58 UserInitiated = 3,
60 UserInteractive = 4,
62}
63
64pub struct DispatchQueue {
77 ptr: *const c_void,
78}
79
80unsafe impl Send for DispatchQueue {}
85unsafe impl Sync for DispatchQueue {}
86
87impl DispatchQueue {
88 #[must_use]
110 pub fn new(label: &str, qos: DispatchQoS) -> Self {
111 let c_label = crate::utils::ffi_string::cstring_until_nul(label);
112 let ptr = unsafe { crate::ffi::acf_dispatch_queue_create(c_label.as_ptr(), qos as i32) };
113 assert!(!ptr.is_null(), "Failed to create dispatch queue");
114 Self { ptr }
115 }
116
117 #[must_use]
118 pub fn concurrent(label: &str, qos: DispatchQoS) -> Self {
119 let c_label = crate::utils::ffi_string::cstring_until_nul(label);
120 let ptr = unsafe {
121 crate::ffi::acf_dispatch_queue_create_concurrent(c_label.as_ptr(), qos as i32)
122 };
123 assert!(!ptr.is_null(), "Failed to create dispatch queue");
124 Self { ptr }
125 }
126
127 #[must_use]
128 pub fn main() -> Self {
129 let ptr = unsafe { crate::ffi::acf_dispatch_queue_main() };
130 assert!(!ptr.is_null(), "dispatch main queue is NULL");
131 Self { ptr }
132 }
133
134 #[must_use]
135 pub fn global(qos: DispatchQoS) -> Self {
136 let ptr = unsafe { crate::ffi::acf_dispatch_queue_global(qos as i32) };
137 assert!(!ptr.is_null(), "dispatch global queue is NULL");
138 Self { ptr }
139 }
140
141 #[must_use]
145 pub const fn as_ptr(&self) -> *const c_void {
146 self.ptr
147 }
148
149 #[must_use]
150 const fn as_mut_ptr(&self) -> *mut c_void {
151 self.ptr.cast_mut()
152 }
153
154 #[allow(clippy::missing_safety_doc)]
155 pub unsafe fn exec_async_f(
156 &self,
157 context: *mut c_void,
158 work: unsafe extern "C" fn(*mut c_void),
159 ) {
160 unsafe { dispatch_async_f(self.as_mut_ptr(), context, work) };
161 }
162
163 #[allow(clippy::missing_safety_doc)]
164 pub unsafe fn exec_after_f(
165 &self,
166 delay: Duration,
167 context: *mut c_void,
168 work: unsafe extern "C" fn(*mut c_void),
169 ) {
170 let delta = i64::try_from(delay.as_nanos()).unwrap_or(i64::MAX);
171 unsafe {
172 dispatch_after_f(
173 dispatch_time(DISPATCH_TIME_NOW, delta),
174 self.as_mut_ptr(),
175 context,
176 work,
177 );
178 };
179 }
180}
181
182const DISPATCH_TIME_NOW: u64 = 0;
183
184extern "C" {
185 fn dispatch_async_f(
186 queue: *mut c_void,
187 context: *mut c_void,
188 work: unsafe extern "C" fn(*mut c_void),
189 );
190 fn dispatch_after_f(
191 when: u64,
192 queue: *mut c_void,
193 context: *mut c_void,
194 work: unsafe extern "C" fn(*mut c_void),
195 );
196 fn dispatch_time(when: u64, delta: i64) -> u64;
197}
198
199crate::utils::retained::cf_retained!(
200 DispatchQueue,
201 field = ptr,
202 retain = crate::ffi::dispatch_queue_retain,
203 release = crate::ffi::dispatch_queue_release,
204 drop = unchecked,
205);
206
207impl fmt::Debug for DispatchQueue {
208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209 f.debug_struct("DispatchQueue")
210 .field("ptr", &self.ptr)
211 .finish()
212 }
213}
214
215impl fmt::Display for DispatchQueue {
216 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217 write!(f, "DispatchQueue")
218 }
219}
220
221fn timeout_ms(timeout: Option<Duration>) -> i64 {
222 timeout.map_or(-1, |duration| {
223 i64::try_from(duration.as_millis()).unwrap_or(i64::MAX)
224 })
225}
226
227struct DispatchOnceTask {
228 site: &'static str,
229 work: Option<Box<dyn FnOnce() + Send + 'static>>,
230}
231
232struct DispatchApplyTask {
233 work: Box<dyn Fn(usize) + Send + Sync + 'static>,
234}
235
236extern "C" fn dispatch_once_trampoline(context: *mut c_void) {
237 if context.is_null() {
238 return;
239 }
240 let mut task = unsafe { Box::from_raw(context.cast::<DispatchOnceTask>()) };
241 if let Some(work) = task.work.take() {
242 panic_safe::catch_user_panic(task.site, work);
243 }
244}
245
246extern "C" fn dispatch_apply_trampoline(iteration: usize, context: *mut c_void) {
247 if context.is_null() {
248 return;
249 }
250 let task = unsafe { &*context.cast::<DispatchApplyTask>() };
251 panic_safe::catch_user_panic("dispatch_apply", || (task.work)(iteration));
252}
253
254pub fn dispatch_async<F>(queue: &DispatchQueue, work: F)
256where
257 F: FnOnce() + Send + 'static,
258{
259 let task = Box::new(DispatchOnceTask {
260 site: "dispatch_async",
261 work: Some(Box::new(work)),
262 });
263 unsafe {
264 crate::ffi::acf_dispatch_async_f(
265 queue.as_mut_ptr(),
266 Box::into_raw(task).cast(),
267 dispatch_once_trampoline,
268 );
269 }
270}
271
272pub fn dispatch_async_and_wait<F>(queue: &DispatchQueue, work: F)
274where
275 F: FnOnce() + Send + 'static,
276{
277 let task = Box::new(DispatchOnceTask {
278 site: "dispatch_async_and_wait",
279 work: Some(Box::new(work)),
280 });
281 unsafe {
282 crate::ffi::acf_dispatch_async_and_wait_f(
283 queue.as_mut_ptr(),
284 Box::into_raw(task).cast(),
285 dispatch_once_trampoline,
286 );
287 }
288}
289
290pub fn dispatch_after<F>(delay: Duration, queue: &DispatchQueue, work: F)
291where
292 F: FnOnce() + Send + 'static,
293{
294 let task = Box::new(DispatchOnceTask {
295 site: "dispatch_after",
296 work: Some(Box::new(work)),
297 });
298 let delay_ns = u64::try_from(delay.as_nanos()).unwrap_or(u64::MAX);
299 unsafe {
300 crate::ffi::acf_dispatch_after_f(
301 delay_ns,
302 queue.as_mut_ptr(),
303 Box::into_raw(task).cast(),
304 dispatch_once_trampoline,
305 );
306 }
307}
308
309pub fn dispatch_apply<F>(iterations: usize, queue: &DispatchQueue, work: F)
311where
312 F: Fn(usize) + Send + Sync + 'static,
313{
314 if iterations == 0 {
315 return;
316 }
317 let task = Box::new(DispatchApplyTask {
318 work: Box::new(work),
319 });
320 let raw = Box::into_raw(task);
321 unsafe {
322 crate::ffi::acf_dispatch_apply_f(
323 iterations,
324 queue.as_mut_ptr(),
325 raw.cast(),
326 dispatch_apply_trampoline,
327 );
328 drop(Box::from_raw(raw));
329 }
330}
331
332#[derive(PartialEq, Eq, Hash)]
334pub struct DispatchGroup {
335 ptr: *mut c_void,
336}
337
338unsafe impl Send for DispatchGroup {}
341unsafe impl Sync for DispatchGroup {}
342
343impl DispatchGroup {
344 #[must_use]
346 pub fn new() -> Self {
347 let ptr = unsafe { crate::ffi::acf_dispatch_group_holder_create() };
348 assert!(!ptr.is_null(), "failed to create DispatchGroup");
349 Self { ptr }
350 }
351
352 pub fn enter(&self) {
354 unsafe { crate::ffi::acf_dispatch_group_holder_enter(self.ptr) };
355 }
356
357 pub fn leave(&self) {
359 unsafe { crate::ffi::acf_dispatch_group_holder_leave(self.ptr) };
360 }
361
362 #[must_use]
364 pub fn wait(&self, timeout: Option<Duration>) -> bool {
365 unsafe { crate::ffi::acf_dispatch_group_holder_wait(self.ptr, timeout_ms(timeout)) }
366 }
367}
368
369impl Default for DispatchGroup {
370 fn default() -> Self {
371 Self::new()
372 }
373}
374
375crate::utils::retained::cf_retained!(
376 DispatchGroup,
377 field = ptr,
378 retain = crate::ffi::acf_object_retain,
379 release = crate::ffi::acf_object_release,
380 drop = unchecked,
381);
382
383impl fmt::Debug for DispatchGroup {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 f.debug_struct("DispatchGroup")
386 .field("ptr", &self.ptr)
387 .finish()
388 }
389}
390
391#[derive(PartialEq, Eq, Hash)]
393pub struct DispatchSemaphore {
394 ptr: *mut c_void,
395}
396
397unsafe impl Send for DispatchSemaphore {}
400unsafe impl Sync for DispatchSemaphore {}
401
402impl DispatchSemaphore {
403 #[must_use]
405 pub fn new(value: i64) -> Option<Self> {
406 if value < 0 {
407 return None;
408 }
409 let ptr = unsafe { crate::ffi::acf_dispatch_semaphore_holder_create(value) };
410 if ptr.is_null() {
411 None
412 } else {
413 Some(Self { ptr })
414 }
415 }
416
417 #[must_use]
419 pub fn signal(&self) -> i64 {
420 unsafe { crate::ffi::acf_dispatch_semaphore_holder_signal(self.ptr) }
421 }
422
423 #[must_use]
425 pub fn wait(&self, timeout: Option<Duration>) -> bool {
426 unsafe { crate::ffi::acf_dispatch_semaphore_holder_wait(self.ptr, timeout_ms(timeout)) }
427 }
428}
429
430crate::utils::retained::cf_retained!(
431 DispatchSemaphore,
432 field = ptr,
433 retain = crate::ffi::acf_object_retain,
434 release = crate::ffi::acf_object_release,
435 drop = unchecked,
436);
437
438impl fmt::Debug for DispatchSemaphore {
439 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
440 f.debug_struct("DispatchSemaphore")
441 .field("ptr", &self.ptr)
442 .finish()
443 }
444}
445
446#[derive(PartialEq, Eq, Hash)]
448pub struct DispatchSource {
449 ptr: *mut c_void,
450}
451
452unsafe impl Send for DispatchSource {}
455unsafe impl Sync for DispatchSource {}
456
457impl DispatchSource {
458 #[must_use]
460 pub fn timer(interval: Duration, leeway: Duration) -> Self {
461 let interval_ns = u64::try_from(interval.as_nanos()).unwrap_or(u64::MAX);
462 let leeway_ns = u64::try_from(leeway.as_nanos()).unwrap_or(u64::MAX);
463 let ptr =
464 unsafe { crate::ffi::acf_dispatch_source_timer_create_ns(interval_ns, leeway_ns) };
465 assert!(!ptr.is_null(), "failed to create DispatchSource timer");
466 Self { ptr }
467 }
468
469 pub fn resume(&self) {
473 unsafe { crate::ffi::acf_dispatch_source_timer_resume(self.ptr) };
474 }
475
476 pub fn cancel(&self) {
481 unsafe { crate::ffi::acf_dispatch_source_timer_cancel(self.ptr) };
482 }
483
484 #[must_use]
486 pub fn fire_count(&self) -> u64 {
487 unsafe { crate::ffi::acf_dispatch_source_timer_fire_count(self.ptr) }
488 }
489}
490
491crate::utils::retained::cf_retained!(
492 DispatchSource,
493 field = ptr,
494 retain = crate::ffi::acf_object_retain,
495 release = crate::ffi::acf_object_release,
496 drop = unchecked,
497);
498
499impl fmt::Debug for DispatchSource {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 f.debug_struct("DispatchSource")
502 .field("ptr", &self.ptr)
503 .field("fire_count", &self.fire_count())
504 .finish()
505 }
506}