1use std::any::Any;
2use std::cell::{Cell, RefCell};
3use std::collections::HashMap;
4use std::rc::Rc;
5use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
6use std::sync::{Arc, Mutex, MutexGuard, PoisonError, Weak};
7use std::thread::{self, ThreadId};
8
9use super::dispatcher_operation::LocalCallback;
10use super::dispatcher_priority_queue::DispatcherPriorityQueue;
11use super::dispatcher_task::LocalTask;
12use super::i_dispatcher_impl::{DetachedDispatcherImpl, DetachedDispatcherSignal};
13use super::{
14 DispatcherFrame, DispatcherPriority, DispatcherTimer, DispatcherUnhandledExceptionEventArgs,
15 DispatcherUnhandledExceptionFilterEventArgs, FerroSynchronizationContext, IDispatcher, IDispatcherImpl,
16 IDispatcherSignal,
17};
18use crate::utilities::HandlerList;
19
20pub type DispatcherUnhandledExceptionEventHandler = dyn for<'a> Fn(&DispatcherUnhandledExceptionEventArgs<'a>);
22
23pub type DispatcherUnhandledExceptionFilterEventHandler =
25 dyn for<'a> Fn(&DispatcherUnhandledExceptionFilterEventArgs<'a>);
26
27pub(crate) struct DispatcherLocal {
31 pub(crate) dispatcher_id: u64,
32 pub(crate) impl_: RefCell<Rc<dyn IDispatcherImpl>>,
34 attached: Cell<bool>,
35 timer_token: Cell<u64>,
36 signaled_token: Cell<u64>,
37 background_token: Cell<Option<u64>>,
38 pub(crate) priority_contexts: RefCell<Vec<Option<Arc<FerroSynchronizationContext>>>>,
40 pub(crate) tasks: RefCell<HashMap<u64, LocalTask>>,
42 pub(crate) jobs: RefCell<HashMap<u64, LocalCallback>>,
44 pub(crate) results: RefCell<HashMap<u64, Box<dyn Any>>>,
46 pub(crate) timers: RefCell<Vec<Rc<DispatcherTimer>>>,
47 pub(crate) timers_version: Cell<i64>,
48 pub(crate) shutdown_started: HandlerList<dyn Fn(&Arc<Dispatcher>)>,
49 pub(crate) shutdown_finished: HandlerList<dyn Fn(&Arc<Dispatcher>)>,
50 pub(crate) unhandled_exception: HandlerList<DispatcherUnhandledExceptionEventHandler>,
51 pub(crate) unhandled_exception_filter: HandlerList<DispatcherUnhandledExceptionFilterEventHandler>,
52 pub(crate) seen_exceptions: RefCell<Vec<usize>>,
55 pub(crate) executing_depth: Cell<u32>,
57}
58
59impl DispatcherLocal {
60 fn new(dispatcher_id: u64) -> Self {
61 Self {
62 dispatcher_id,
63 impl_: RefCell::new(Rc::new(DetachedDispatcherImpl::new())),
64 attached: Cell::new(false),
65 timer_token: Cell::new(0),
66 signaled_token: Cell::new(0),
67 background_token: Cell::new(None),
68 priority_contexts: RefCell::new(Vec::new()),
69 tasks: RefCell::new(HashMap::new()),
70 jobs: RefCell::new(HashMap::new()),
71 results: RefCell::new(HashMap::new()),
72 timers: RefCell::new(Vec::new()),
73 timers_version: Cell::new(0),
74 shutdown_started: HandlerList::new(),
75 shutdown_finished: HandlerList::new(),
76 unhandled_exception: HandlerList::new(),
77 unhandled_exception_filter: HandlerList::new(),
78 seen_exceptions: RefCell::new(Vec::new()),
79 executing_depth: Cell::new(0),
80 }
81 }
82}
83
84pub(crate) struct DispatcherState {
86 pub(crate) signal: Arc<dyn IDispatcherSignal>,
89 pub(crate) supports_run_loops: bool,
90 pub(crate) initialized: bool,
91
92 pub(crate) queue: DispatcherPriorityQueue,
94 pub(crate) signaled: bool,
95 pub(crate) explicit_background_processing_requested: bool,
96 pub(crate) maximum_input_starvation_time: i64,
97 pub(crate) dead_local_operations: Vec<u64>,
100
101 pub(crate) due_time_found: bool,
103 pub(crate) due_time_in_ms: i64,
104 pub(crate) due_time_for_timers: Option<i64>,
105 pub(crate) due_time_for_background_processing: Option<i64>,
106 pub(crate) os_timer_set_to: Option<i64>,
107
108 pub(crate) disabled_processing_count: i32,
110 pub(crate) has_shutdown_finished: bool,
111 pub(crate) starting_shutdown: bool,
112 pub(crate) frames: Vec<Arc<DispatcherFrame>>,
113}
114
115static NEXT_DISPATCHER_ID: AtomicU64 = AtomicU64::new(1);
116
117pub struct Dispatcher {
164 pub(crate) id: u64,
165 thread: ThreadId,
166 pub(crate) weak_self: Weak<Dispatcher>,
167 state: Mutex<DispatcherState>,
168 pub(crate) exit_all_frames_requested: AtomicBool,
169 pub(crate) has_shutdown_started: AtomicBool,
170}
171
172impl Dispatcher {
173 pub(crate) fn new(impl_: Option<Rc<dyn IDispatcherImpl>>) -> Arc<Dispatcher> {
179 #[cfg(debug_assertions)]
180 {
181 use crate::LocatorExtensions;
182 let locator = crate::FerroLocator::current();
183 if locator.get_service::<dyn IDispatcherImpl>().is_some()
184 || locator.get_service::<dyn crate::platform::IPlatformThreadingInterface>().is_some()
185 {
186 panic!("Registering IDispatcherImpl or IPlatformThreadingInterface via locator is no longer valid");
187 }
188 }
189
190 if let Some(impl_) = &impl_ {
191 if !impl_.current_thread_is_loop_thread() {
192 panic!("IDispatcherImpl belongs to a different thread");
193 }
194 }
195
196 let id = NEXT_DISPATCHER_ID.fetch_add(1, Ordering::Relaxed);
197 let dispatcher = Arc::new_cyclic(|weak_self| Dispatcher {
198 id,
199 thread: current_thread_id(),
200 weak_self: weak_self.clone(),
201 state: Mutex::new(DispatcherState {
202 signal: Arc::new(DetachedDispatcherSignal),
204 supports_run_loops: false,
205 initialized: false,
206 queue: DispatcherPriorityQueue::new(),
207 signaled: false,
208 explicit_background_processing_requested: false,
209 maximum_input_starvation_time: Self::MAXIMUM_INPUT_STARVATION_TIME_IN_FALLBACK_MODE,
210 dead_local_operations: Vec::new(),
211 due_time_found: false,
212 due_time_in_ms: 0,
213 due_time_for_timers: None,
214 due_time_for_background_processing: None,
215 os_timer_set_to: None,
216 disabled_processing_count: 0,
217 has_shutdown_finished: false,
218 starting_shutdown: false,
219 frames: Vec::new(),
220 }),
221 exit_all_frames_requested: AtomicBool::new(false),
222 has_shutdown_started: AtomicBool::new(false),
223 });
224
225 Self::register_current_thread_dispatcher(&dispatcher, Rc::new(DispatcherLocal::new(id)));
227
228 dispatcher.replace_implementation(impl_);
229 dispatcher
230 }
231
232 pub fn supports_run_loops(&self) -> bool {
236 self.lock().supports_run_loops
237 }
238
239 #[inline]
241 pub fn check_access(&self) -> bool {
242 current_thread_id() == self.thread
243 }
244
245 #[inline]
248 #[track_caller]
249 pub fn verify_access(&self) {
250 if !self.check_access() {
251 Self::throw_verify_access();
252 }
253 }
254
255 #[cold]
256 #[inline(never)]
257 #[track_caller]
258 fn throw_verify_access() -> ! {
259 panic!("The calling thread cannot access this object because a different thread owns it.");
260 }
261
262 pub fn thread(&self) -> ThreadId {
264 self.thread
265 }
266
267 pub fn platform_impl(&self) -> Rc<dyn IDispatcherImpl> {
273 self.verify_access();
274 self.local_impl().unwrap_or_else(|| Rc::new(DetachedDispatcherImpl::new()))
275 }
276
277 pub(crate) fn local_impl(&self) -> Option<Rc<dyn IDispatcherImpl>> {
280 self.try_local().map(|local| local.impl_.borrow().clone())
281 }
282
283 pub(crate) fn lock(&self) -> MutexGuard<'_, DispatcherState> {
285 self.state.lock().unwrap_or_else(PoisonError::into_inner)
286 }
287
288 pub(crate) fn to_arc(&self) -> Arc<Dispatcher> {
290 self.weak_self.upgrade().expect("the dispatcher is alive while it is borrowed")
291 }
292
293 pub(crate) fn try_local(&self) -> Option<Rc<DispatcherLocal>> {
296 if !self.check_access() {
297 return None;
298 }
299 Self::current_thread_local().filter(|local| local.dispatcher_id == self.id)
300 }
301
302 pub(crate) fn discard_local_operation(&self, operation_id: u64) {
305 if self.check_access() {
306 if let Some(local) = self.try_local() {
307 let job = local.jobs.borrow_mut().remove(&operation_id);
308 let result = local.results.borrow_mut().remove(&operation_id);
309 drop(job);
310 drop(result);
311 }
312 } else {
313 self.lock().dead_local_operations.push(operation_id);
314 }
315 }
316
317 pub(crate) fn replace_implementation(&self, impl_: Option<Rc<dyn IDispatcherImpl>>) {
318 if let Some(impl_) = &impl_ {
319 if !impl_.current_thread_is_loop_thread() {
320 panic!("IDispatcherImpl belongs to a different thread");
321 }
322 }
323 self.verify_access();
324 let Some(local) = self.try_local() else {
325 return;
326 };
327
328 let mut time_shift = None;
329 {
330 let mut st = self.lock();
331 let mut old_now = None;
332
333 if local.attached.get() {
334 old_now = Some(local.impl_.borrow().now());
336 Self::detach_implementation(&local);
337 }
338
339 let impl_ = match impl_ {
340 Some(impl_) => {
341 st.initialized = true;
342 impl_
343 }
344 None => default_dispatcher_impl(),
345 };
346 *local.impl_.borrow_mut() = impl_.clone();
347 st.signal = impl_.signal_handle();
348 st.supports_run_loops = impl_.as_controlled().is_some();
349
350 let weak = self.weak_self.clone();
351 local.timer_token.set(impl_.timer().add(Rc::new(move |()| {
352 if let Some(dispatcher) = weak.upgrade() {
353 dispatcher.on_os_timer();
354 }
355 })));
356 let weak = self.weak_self.clone();
357 local.signaled_token.set(impl_.signaled().add(Rc::new(move |()| {
358 if let Some(dispatcher) = weak.upgrade() {
359 dispatcher.signaled();
360 }
361 })));
362 local.attached.set(true);
363
364 let background = impl_.as_explicit_background_processing();
365 st.maximum_input_starvation_time = if background.is_none() {
366 Self::MAXIMUM_INPUT_STARVATION_TIME_IN_FALLBACK_MODE
367 } else {
368 Self::MAXIMUM_INPUT_STARVATION_TIME_IN_EXPLICIT_PROCESSING_EXPLICIT_MODE
369 };
370 if let Some(background) = background {
371 let weak = self.weak_self.clone();
372 local.background_token.set(Some(background.ready_for_background_processing().add(Rc::new(
373 move |()| {
374 if let Some(dispatcher) = weak.upgrade() {
375 dispatcher.on_ready_for_explicit_background_processing();
376 }
377 },
378 ))));
379 }
380
381 if let Some(old_now) = old_now {
385 let shift = impl_.now().wrapping_sub(old_now);
386 if shift != 0 {
387 st.due_time_in_ms = st.due_time_in_ms.wrapping_add(shift);
388 st.due_time_for_timers = st.due_time_for_timers.map(|due| due.wrapping_add(shift));
389 st.due_time_for_background_processing =
390 st.due_time_for_background_processing.map(|due| due.wrapping_add(shift));
391 time_shift = Some(shift);
392 }
393 }
394
395 if st.signaled {
396 impl_.signal();
397 }
398 if st.explicit_background_processing_requested {
399 match background {
400 Some(background) => background.request_background_processing(),
401 None => {
402 st.explicit_background_processing_requested = false;
406 self.request_background_processing_locked(&mut st);
407 }
408 }
409 }
410
411 st.os_timer_set_to = None;
412 self.update_os_timer_locked(&mut st);
413 }
414
415 if let Some(shift) = time_shift {
416 for timer in local.timers.borrow().iter() {
417 timer.shift_due_time(shift);
418 }
419 }
420 }
421
422 pub(crate) fn detach_implementation(local: &DispatcherLocal) {
424 if !local.attached.get() {
425 return;
426 }
427 let impl_ = local.impl_.borrow().clone();
428 impl_.timer().remove(local.timer_token.get());
429 impl_.signaled().remove(local.signaled_token.get());
430 if let Some(token) = local.background_token.take() {
431 if let Some(background) = impl_.as_explicit_background_processing() {
432 background.ready_for_background_processing().remove(token);
433 }
434 }
435 local.attached.set(false);
436 }
437
438 pub(crate) fn detach_from_platform(st: &mut DispatcherState, local: Option<&DispatcherLocal>) {
442 if let Some(local) = local {
443 Self::detach_implementation(local);
444 let previous = local.impl_.replace(Rc::new(DetachedDispatcherImpl::new()));
445 drop(previous);
446 }
447 st.signal = Arc::new(DetachedDispatcherSignal);
448 st.supports_run_loops = false;
449 }
450}
451
452impl IDispatcher for Dispatcher {
453 fn check_access(&self) -> bool {
454 Dispatcher::check_access(self)
455 }
456
457 fn verify_access(&self) {
458 Dispatcher::verify_access(self)
459 }
460
461 fn post(&self, action: Box<dyn FnOnce() + Send>, priority: DispatcherPriority) {
462 Dispatcher::post(self, action, priority)
463 }
464}
465
466impl std::fmt::Debug for Dispatcher {
467 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
468 f.debug_struct("Dispatcher").field("id", &self.id).field("thread", &self.thread).finish()
469 }
470}
471
472fn default_dispatcher_impl() -> Rc<dyn IDispatcherImpl> {
474 #[cfg(not(target_family = "wasm"))]
475 {
476 Rc::new(crate::platform::ManagedDispatcherImpl::new(None))
477 }
478 #[cfg(target_family = "wasm")]
479 {
480 Rc::new(DetachedDispatcherImpl::new())
484 }
485}
486
487crate::ferro_thread_local! {
488 static THREAD_ID: ThreadId = thread::current().id();
489}
490
491#[inline]
493pub(crate) fn current_thread_id() -> ThreadId {
494 THREAD_ID.try_with(|id| *id).unwrap_or_else(|_| thread::current().id())
495}