1#![allow(non_upper_case_globals)]
2#![allow(non_camel_case_types)]
3#![allow(non_snake_case)]
4
5use crate::{
6 GLOBAL_THREAD_TIMINGS, PlatformDispatcher, RunnableMeta, RunnableVariant, THREAD_TIMINGS,
7 TaskLabel, TaskTiming, ThreadTaskTimings,
8};
9
10use async_task::Runnable;
11use objc::{
12 class, msg_send,
13 runtime::{BOOL, YES},
14 sel, sel_impl,
15};
16use std::{
17 ffi::c_void,
18 ptr::{NonNull, addr_of},
19 time::{Duration, Instant},
20};
21
22/// All items in the generated file are marked as pub, so we're gonna wrap it in a separate mod to prevent
23/// these pub items from leaking into public API.
24pub(crate) mod dispatch_sys {
25 include!(concat!(env!("OUT_DIR"), "/dispatch_sys.rs"));
26}
27
28use dispatch_sys::*;
29pub(crate) fn dispatch_get_main_queue() -> dispatch_queue_t {
30 addr_of!(_dispatch_main_q) as *const _ as dispatch_queue_t
31}
32
33pub(crate) struct MacDispatcher;
34
35impl PlatformDispatcher for MacDispatcher {
36 fn get_all_timings(&self) -> Vec<ThreadTaskTimings> {
37 let global_timings = GLOBAL_THREAD_TIMINGS.lock();
38 ThreadTaskTimings::convert(&global_timings)
39 }
40
41 fn get_current_thread_timings(&self) -> Vec<TaskTiming> {
42 THREAD_TIMINGS.with(|timings| {
43 let timings = &timings.lock().timings;
44
45 let mut vec = Vec::with_capacity(timings.len());
46
47 let (s1, s2) = timings.as_slices();
48 vec.extend_from_slice(s1);
49 vec.extend_from_slice(s2);
50 vec
51 })
52 }
53
54 fn is_main_thread(&self) -> bool {
55 let is_main_thread: BOOL = unsafe { msg_send![class!(NSThread), isMainThread] };
56 is_main_thread == YES
57 }
58
59 fn dispatch(&self, runnable: RunnableVariant, _: Option<TaskLabel>) {
60 let (context, trampoline) = match runnable {
61 RunnableVariant::Meta(runnable) => (
62 runnable.into_raw().as_ptr() as *mut c_void,
63 Some(trampoline as unsafe extern "C" fn(*mut c_void)),
64 ),
65 RunnableVariant::Compat(runnable) => (
66 runnable.into_raw().as_ptr() as *mut c_void,
67 Some(trampoline_compat as unsafe extern "C" fn(*mut c_void)),
68 ),
69 };
70 unsafe {
71 dispatch_async_f(
72 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH.try_into().unwrap(), 0),
73 context,
74 trampoline,
75 );
76 }
77 }
78
79 fn dispatch_on_main_thread(&self, runnable: RunnableVariant) {
80 let (context, trampoline) = match runnable {
81 RunnableVariant::Meta(runnable) => (
82 runnable.into_raw().as_ptr() as *mut c_void,
83 Some(trampoline as unsafe extern "C" fn(*mut c_void)),
84 ),
85 RunnableVariant::Compat(runnable) => (
86 runnable.into_raw().as_ptr() as *mut c_void,
87 Some(trampoline_compat as unsafe extern "C" fn(*mut c_void)),
88 ),
89 };
90 unsafe {
91 dispatch_async_f(dispatch_get_main_queue(), context, trampoline);
92 }
93 }
94
95 fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant) {
96 let (context, trampoline) = match runnable {
97 RunnableVariant::Meta(runnable) => (
98 runnable.into_raw().as_ptr() as *mut c_void,
99 Some(trampoline as unsafe extern "C" fn(*mut c_void)),
100 ),
101 RunnableVariant::Compat(runnable) => (
102 runnable.into_raw().as_ptr() as *mut c_void,
103 Some(trampoline_compat as unsafe extern "C" fn(*mut c_void)),
104 ),
105 };
106 unsafe {
107 let queue =
108 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH.try_into().unwrap(), 0);
109 let when = dispatch_time(DISPATCH_TIME_NOW as u64, duration.as_nanos() as i64);
110 dispatch_after_f(when, queue, context, trampoline);
111 }
112 }
113}
114
115extern "C" fn trampoline(runnable: *mut c_void) {
116 let task =
117 unsafe { Runnable::<RunnableMeta>::from_raw(NonNull::new_unchecked(runnable as *mut ())) };
118
119 let location = task.metadata().location;
120
121 let start = Instant::now();
122 let timing = TaskTiming {
123 location,
124 start,
125 end: None,
126 };
127
128 THREAD_TIMINGS.with(|timings| {
129 let mut timings = timings.lock();
130 let timings = &mut timings.timings;
131 if let Some(last_timing) = timings.iter_mut().rev().next() {
132 if last_timing.location == timing.location {
133 return;
134 }
135 }
136
137 timings.push_back(timing);
138 });
139
140 task.run();
141 let end = Instant::now();
142
143 THREAD_TIMINGS.with(|timings| {
144 let mut timings = timings.lock();
145 let timings = &mut timings.timings;
146 let Some(last_timing) = timings.iter_mut().rev().next() else {
147 return;
148 };
149 last_timing.end = Some(end);
150 });
151}
152
153extern "C" fn trampoline_compat(runnable: *mut c_void) {
154 let task = unsafe { Runnable::<()>::from_raw(NonNull::new_unchecked(runnable as *mut ())) };
155
156 let location = core::panic::Location::caller();
157
158 let start = Instant::now();
159 let timing = TaskTiming {
160 location,
161 start,
162 end: None,
163 };
164 THREAD_TIMINGS.with(|timings| {
165 let mut timings = timings.lock();
166 let timings = &mut timings.timings;
167 if let Some(last_timing) = timings.iter_mut().rev().next() {
168 if last_timing.location == timing.location {
169 return;
170 }
171 }
172
173 timings.push_back(timing);
174 });
175
176 task.run();
177 let end = Instant::now();
178
179 THREAD_TIMINGS.with(|timings| {
180 let mut timings = timings.lock();
181 let timings = &mut timings.timings;
182 let Some(last_timing) = timings.iter_mut().rev().next() else {
183 return;
184 };
185 last_timing.end = Some(end);
186 });
187}