1use std::cell::RefCell;
19use std::collections::HashMap;
20use std::fmt;
21use std::rc::Rc;
22#[cfg(feature = "tokio-worker")]
23use std::sync::Arc;
24use std::time::Instant;
25
26use crate::async_driver::LocalAsyncDriver;
27use crate::ctx::Ctx;
28pub use crate::protocol::Handle;
29
30pub type NodeFn = Rc<dyn Fn(&Ctx)>;
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum PoolKind {
41 #[default]
43 Sync,
44 Async,
47}
48
49struct Slot {
52 f: Option<NodeFn>,
53 generation: u32,
54}
55
56struct Pool {
64 kind: PoolKind,
65 slots: Vec<Slot>,
66 free: Vec<u32>,
68}
69
70impl Pool {
71 fn new(kind: PoolKind) -> Self {
72 Self {
73 kind,
74 slots: Vec::new(),
75 free: Vec::new(),
76 }
77 }
78
79 fn register(&mut self, f: NodeFn) -> (u32, u32) {
82 if let Some(id) = self.free.pop() {
83 let slot = &mut self.slots[id as usize];
84 slot.f = Some(f);
85 (id, slot.generation)
86 } else {
87 let id = self.slots.len() as u32;
88 self.slots.push(Slot {
89 f: Some(f),
90 generation: 0,
91 });
92 (id, 0)
93 }
94 }
95
96 fn unregister(&mut self, id: u32, generation: u32) {
99 if let Some(slot) = self.slots.get_mut(id as usize) {
100 if slot.generation == generation && slot.f.is_some() {
101 slot.f = None;
102 slot.generation = slot.generation.wrapping_add(1);
103 self.free.push(id);
104 }
105 }
106 }
107
108 fn get(&self, id: u32, generation: u32) -> Option<NodeFn> {
111 self.slots
112 .get(id as usize)
113 .filter(|s| s.generation == generation)
114 .and_then(|s| s.f.clone())
115 }
116}
117
118struct DispatcherInner {
119 pools: Vec<Pool>,
123 recording: bool,
126 stats: HashMap<Handle, ProfileStat>,
127 local_async_driver: Option<Rc<dyn LocalAsyncDriver>>,
128 #[cfg(feature = "tokio-worker")]
129 worker_backend: Option<WorkerBackend>,
130}
131
132#[derive(Clone)]
135pub struct Dispatcher(Rc<RefCell<DispatcherInner>>);
136
137impl fmt::Debug for Dispatcher {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 f.debug_struct("Dispatcher").finish_non_exhaustive()
140 }
141}
142
143#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
145pub struct ProfileStat {
146 pub invokes: u64,
148 pub total_duration_ns: u128,
150 pub last_duration_ns: u128,
152}
153
154#[cfg(feature = "tokio-worker")]
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub(crate) enum WorkerSubmitError {
157 MissingBackend,
158 MissingRuntime,
159}
160
161#[cfg(feature = "tokio-worker")]
162#[derive(Debug, Clone, Copy, Default)]
163struct WorkerBackend;
164
165#[cfg(feature = "tokio-worker")]
166type WorkerTask<R> = Box<dyn FnOnce() -> Result<R, String> + Send + 'static>;
167
168#[cfg(feature = "tokio-worker")]
169pub(crate) struct WorkerJob<R> {
170 handle: tokio::runtime::Handle,
171 task: WorkerTask<R>,
172}
173
174#[cfg(feature = "tokio-worker")]
175impl<R: Send + 'static> WorkerJob<R> {
176 pub(crate) fn spawn(self) -> tokio::task::JoinHandle<Result<R, String>> {
177 let task = self.task;
178 self.handle.spawn_blocking(task)
179 }
180}
181
182#[cfg(feature = "tokio-worker")]
183impl WorkerBackend {
184 fn submit<I, R, E, C>(
185 self,
186 input: I,
187 compute: Arc<C>,
188 ) -> Result<WorkerJob<R>, WorkerSubmitError>
189 where
190 I: Send + 'static,
191 R: Send + 'static,
192 E: fmt::Display + Send + 'static,
193 C: Fn(I) -> Result<R, E> + Send + Sync + 'static,
194 {
195 let handle =
196 tokio::runtime::Handle::try_current().map_err(|_| WorkerSubmitError::MissingRuntime)?;
197 Ok(WorkerJob {
198 handle,
199 task: Box::new(move || compute(input).map_err(|error| error.to_string())),
200 })
201 }
202}
203
204pub const SYNC_POOL_ID: u32 = 0;
206pub const ASYNC_POOL_ID: u32 = 1;
208
209impl Dispatcher {
210 pub fn new() -> Self {
212 Dispatcher(Rc::new(RefCell::new(DispatcherInner {
213 pools: vec![Pool::new(PoolKind::Sync), Pool::new(PoolKind::Async)],
215 recording: false,
216 stats: HashMap::new(),
217 local_async_driver: None,
218 #[cfg(feature = "tokio-worker")]
219 worker_backend: Some(WorkerBackend),
220 })))
221 }
222
223 pub fn set_recording(&self, on: bool) {
225 self.0.borrow_mut().recording = on;
226 }
227
228 pub fn stat_for(&self, handle: Handle) -> Option<ProfileStat> {
230 self.0.borrow().stats.get(&handle).copied()
231 }
232
233 pub fn set_local_async_driver(&self, driver: Option<Rc<dyn LocalAsyncDriver>>) {
235 self.0.borrow_mut().local_async_driver = driver;
236 }
237
238 pub fn local_async_driver(&self) -> Option<Rc<dyn LocalAsyncDriver>> {
240 self.0.borrow().local_async_driver.clone()
241 }
242
243 #[cfg(feature = "tokio-worker")]
244 pub(crate) fn submit_worker<I, R, E, C>(
245 &self,
246 input: I,
247 compute: Arc<C>,
248 ) -> Result<WorkerJob<R>, WorkerSubmitError>
249 where
250 I: Send + 'static,
251 R: Send + 'static,
252 E: fmt::Display + Send + 'static,
253 C: Fn(I) -> Result<R, E> + Send + Sync + 'static,
254 {
255 let backend = self
256 .0
257 .borrow()
258 .worker_backend
259 .ok_or(WorkerSubmitError::MissingBackend)?;
260 backend.submit(input, compute)
261 }
262
263 #[cfg(all(test, feature = "tokio-worker"))]
264 pub(crate) fn set_worker_backend_for_test(&self, installed: bool) {
265 self.0.borrow_mut().worker_backend = installed.then_some(WorkerBackend);
266 }
267
268 pub fn register(&self, f: NodeFn) -> Handle {
270 self.register_in(SYNC_POOL_ID, f)
271 }
272
273 pub fn register_async(&self, f: NodeFn) -> Handle {
277 self.register_in(ASYNC_POOL_ID, f)
278 }
279
280 fn register_in(&self, pool_id: u32, f: NodeFn) -> Handle {
281 let (handle_id, generation) = self.0.borrow_mut().pools[pool_id as usize].register(f);
282 Handle {
283 pool_id,
284 handle_id,
285 generation,
286 }
287 }
288
289 pub fn pool_kind(&self, pool_id: u32) -> PoolKind {
292 self.0.borrow().pools[pool_id as usize].kind
293 }
294
295 pub fn unregister(&self, handle: Handle) {
299 let mut inner = self.0.borrow_mut();
300 inner.pools[handle.pool_id as usize].unregister(handle.handle_id, handle.generation);
301 inner.stats.remove(&handle);
302 }
303
304 pub fn invoke(&self, handle: Handle, ctx: &Ctx) {
308 let (f, recording) = {
309 let inner = self.0.borrow();
310 (
311 inner.pools[handle.pool_id as usize].get(handle.handle_id, handle.generation),
312 inner.recording,
313 )
314 };
315 if let Some(f) = f {
320 if recording {
321 let _profile = InvokeProfileGuard {
322 dispatcher: self.clone(),
323 handle,
324 start: Instant::now(),
325 };
326 f(ctx);
327 } else {
328 f(ctx);
329 }
330 }
331 }
332}
333
334struct InvokeProfileGuard {
335 dispatcher: Dispatcher,
336 handle: Handle,
337 start: Instant,
338}
339
340impl Drop for InvokeProfileGuard {
341 fn drop(&mut self) {
342 let elapsed = self.start.elapsed().as_nanos();
343 let mut inner = self.dispatcher.0.borrow_mut();
344 let stat = inner.stats.entry(self.handle).or_default();
345 stat.invokes += 1;
346 stat.total_duration_ns += elapsed;
347 stat.last_duration_ns = elapsed;
348 }
349}
350
351impl Default for Dispatcher {
352 fn default() -> Self {
353 Self::new()
354 }
355}
356
357thread_local! {
358 static DEFAULT: Dispatcher = Dispatcher::new();
361}
362
363pub fn default_dispatcher() -> Dispatcher {
365 DEFAULT.with(|d| d.clone())
366}