use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, MutexGuard, PoisonError, Weak};
use std::thread::{self, ThreadId};
use super::dispatcher_operation::LocalCallback;
use super::dispatcher_priority_queue::DispatcherPriorityQueue;
use super::dispatcher_task::LocalTask;
use super::i_dispatcher_impl::{DetachedDispatcherImpl, DetachedDispatcherSignal};
use super::{
DispatcherFrame, DispatcherPriority, DispatcherTimer, DispatcherUnhandledExceptionEventArgs,
DispatcherUnhandledExceptionFilterEventArgs, FerroSynchronizationContext, IDispatcher, IDispatcherImpl,
IDispatcherSignal,
};
use crate::utilities::HandlerList;
pub type DispatcherUnhandledExceptionEventHandler = dyn for<'a> Fn(&DispatcherUnhandledExceptionEventArgs<'a>);
pub type DispatcherUnhandledExceptionFilterEventHandler =
dyn for<'a> Fn(&DispatcherUnhandledExceptionFilterEventArgs<'a>);
pub(crate) struct DispatcherLocal {
pub(crate) dispatcher_id: u64,
pub(crate) impl_: RefCell<Rc<dyn IDispatcherImpl>>,
attached: Cell<bool>,
timer_token: Cell<u64>,
signaled_token: Cell<u64>,
background_token: Cell<Option<u64>>,
pub(crate) priority_contexts: RefCell<Vec<Option<Arc<FerroSynchronizationContext>>>>,
pub(crate) tasks: RefCell<HashMap<u64, LocalTask>>,
pub(crate) jobs: RefCell<HashMap<u64, LocalCallback>>,
pub(crate) results: RefCell<HashMap<u64, Box<dyn Any>>>,
pub(crate) timers: RefCell<Vec<Rc<DispatcherTimer>>>,
pub(crate) timers_version: Cell<i64>,
pub(crate) shutdown_started: HandlerList<dyn Fn(&Arc<Dispatcher>)>,
pub(crate) shutdown_finished: HandlerList<dyn Fn(&Arc<Dispatcher>)>,
pub(crate) unhandled_exception: HandlerList<DispatcherUnhandledExceptionEventHandler>,
pub(crate) unhandled_exception_filter: HandlerList<DispatcherUnhandledExceptionFilterEventHandler>,
pub(crate) seen_exceptions: RefCell<Vec<usize>>,
pub(crate) executing_depth: Cell<u32>,
}
impl DispatcherLocal {
fn new(dispatcher_id: u64) -> Self {
Self {
dispatcher_id,
impl_: RefCell::new(Rc::new(DetachedDispatcherImpl::new())),
attached: Cell::new(false),
timer_token: Cell::new(0),
signaled_token: Cell::new(0),
background_token: Cell::new(None),
priority_contexts: RefCell::new(Vec::new()),
tasks: RefCell::new(HashMap::new()),
jobs: RefCell::new(HashMap::new()),
results: RefCell::new(HashMap::new()),
timers: RefCell::new(Vec::new()),
timers_version: Cell::new(0),
shutdown_started: HandlerList::new(),
shutdown_finished: HandlerList::new(),
unhandled_exception: HandlerList::new(),
unhandled_exception_filter: HandlerList::new(),
seen_exceptions: RefCell::new(Vec::new()),
executing_depth: Cell::new(0),
}
}
}
pub(crate) struct DispatcherState {
pub(crate) signal: Arc<dyn IDispatcherSignal>,
pub(crate) supports_run_loops: bool,
pub(crate) initialized: bool,
pub(crate) queue: DispatcherPriorityQueue,
pub(crate) signaled: bool,
pub(crate) explicit_background_processing_requested: bool,
pub(crate) maximum_input_starvation_time: i64,
pub(crate) dead_local_operations: Vec<u64>,
pub(crate) due_time_found: bool,
pub(crate) due_time_in_ms: i64,
pub(crate) due_time_for_timers: Option<i64>,
pub(crate) due_time_for_background_processing: Option<i64>,
pub(crate) os_timer_set_to: Option<i64>,
pub(crate) disabled_processing_count: i32,
pub(crate) has_shutdown_finished: bool,
pub(crate) starting_shutdown: bool,
pub(crate) frames: Vec<Arc<DispatcherFrame>>,
}
static NEXT_DISPATCHER_ID: AtomicU64 = AtomicU64::new(1);
pub struct Dispatcher {
pub(crate) id: u64,
thread: ThreadId,
pub(crate) weak_self: Weak<Dispatcher>,
state: Mutex<DispatcherState>,
pub(crate) exit_all_frames_requested: AtomicBool,
pub(crate) has_shutdown_started: AtomicBool,
}
impl Dispatcher {
pub(crate) fn new(impl_: Option<Rc<dyn IDispatcherImpl>>) -> Arc<Dispatcher> {
#[cfg(debug_assertions)]
{
use crate::LocatorExtensions;
let locator = crate::FerroLocator::current();
if locator.get_service::<dyn IDispatcherImpl>().is_some()
|| locator.get_service::<dyn crate::platform::IPlatformThreadingInterface>().is_some()
{
panic!("Registering IDispatcherImpl or IPlatformThreadingInterface via locator is no longer valid");
}
}
if let Some(impl_) = &impl_ {
if !impl_.current_thread_is_loop_thread() {
panic!("IDispatcherImpl belongs to a different thread");
}
}
let id = NEXT_DISPATCHER_ID.fetch_add(1, Ordering::Relaxed);
let dispatcher = Arc::new_cyclic(|weak_self| Dispatcher {
id,
thread: current_thread_id(),
weak_self: weak_self.clone(),
state: Mutex::new(DispatcherState {
signal: Arc::new(DetachedDispatcherSignal),
supports_run_loops: false,
initialized: false,
queue: DispatcherPriorityQueue::new(),
signaled: false,
explicit_background_processing_requested: false,
maximum_input_starvation_time: Self::MAXIMUM_INPUT_STARVATION_TIME_IN_FALLBACK_MODE,
dead_local_operations: Vec::new(),
due_time_found: false,
due_time_in_ms: 0,
due_time_for_timers: None,
due_time_for_background_processing: None,
os_timer_set_to: None,
disabled_processing_count: 0,
has_shutdown_finished: false,
starting_shutdown: false,
frames: Vec::new(),
}),
exit_all_frames_requested: AtomicBool::new(false),
has_shutdown_started: AtomicBool::new(false),
});
Self::register_current_thread_dispatcher(&dispatcher, Rc::new(DispatcherLocal::new(id)));
dispatcher.replace_implementation(impl_);
dispatcher
}
pub fn supports_run_loops(&self) -> bool {
self.lock().supports_run_loops
}
#[inline]
pub fn check_access(&self) -> bool {
current_thread_id() == self.thread
}
#[inline]
#[track_caller]
pub fn verify_access(&self) {
if !self.check_access() {
Self::throw_verify_access();
}
}
#[cold]
#[inline(never)]
#[track_caller]
fn throw_verify_access() -> ! {
panic!("The calling thread cannot access this object because a different thread owns it.");
}
pub fn thread(&self) -> ThreadId {
self.thread
}
pub fn platform_impl(&self) -> Rc<dyn IDispatcherImpl> {
self.verify_access();
self.local_impl().unwrap_or_else(|| Rc::new(DetachedDispatcherImpl::new()))
}
pub(crate) fn local_impl(&self) -> Option<Rc<dyn IDispatcherImpl>> {
self.try_local().map(|local| local.impl_.borrow().clone())
}
pub(crate) fn lock(&self) -> MutexGuard<'_, DispatcherState> {
self.state.lock().unwrap_or_else(PoisonError::into_inner)
}
pub(crate) fn to_arc(&self) -> Arc<Dispatcher> {
self.weak_self.upgrade().expect("the dispatcher is alive while it is borrowed")
}
pub(crate) fn try_local(&self) -> Option<Rc<DispatcherLocal>> {
if !self.check_access() {
return None;
}
Self::current_thread_local().filter(|local| local.dispatcher_id == self.id)
}
pub(crate) fn discard_local_operation(&self, operation_id: u64) {
if self.check_access() {
if let Some(local) = self.try_local() {
let job = local.jobs.borrow_mut().remove(&operation_id);
let result = local.results.borrow_mut().remove(&operation_id);
drop(job);
drop(result);
}
} else {
self.lock().dead_local_operations.push(operation_id);
}
}
pub(crate) fn replace_implementation(&self, impl_: Option<Rc<dyn IDispatcherImpl>>) {
if let Some(impl_) = &impl_ {
if !impl_.current_thread_is_loop_thread() {
panic!("IDispatcherImpl belongs to a different thread");
}
}
self.verify_access();
let Some(local) = self.try_local() else {
return;
};
let mut time_shift = None;
{
let mut st = self.lock();
let mut old_now = None;
if local.attached.get() {
old_now = Some(local.impl_.borrow().now());
Self::detach_implementation(&local);
}
let impl_ = match impl_ {
Some(impl_) => {
st.initialized = true;
impl_
}
None => default_dispatcher_impl(),
};
*local.impl_.borrow_mut() = impl_.clone();
st.signal = impl_.signal_handle();
st.supports_run_loops = impl_.as_controlled().is_some();
let weak = self.weak_self.clone();
local.timer_token.set(impl_.timer().add(Rc::new(move |()| {
if let Some(dispatcher) = weak.upgrade() {
dispatcher.on_os_timer();
}
})));
let weak = self.weak_self.clone();
local.signaled_token.set(impl_.signaled().add(Rc::new(move |()| {
if let Some(dispatcher) = weak.upgrade() {
dispatcher.signaled();
}
})));
local.attached.set(true);
let background = impl_.as_explicit_background_processing();
st.maximum_input_starvation_time = if background.is_none() {
Self::MAXIMUM_INPUT_STARVATION_TIME_IN_FALLBACK_MODE
} else {
Self::MAXIMUM_INPUT_STARVATION_TIME_IN_EXPLICIT_PROCESSING_EXPLICIT_MODE
};
if let Some(background) = background {
let weak = self.weak_self.clone();
local.background_token.set(Some(background.ready_for_background_processing().add(Rc::new(
move |()| {
if let Some(dispatcher) = weak.upgrade() {
dispatcher.on_ready_for_explicit_background_processing();
}
},
))));
}
if let Some(old_now) = old_now {
let shift = impl_.now().wrapping_sub(old_now);
if shift != 0 {
st.due_time_in_ms = st.due_time_in_ms.wrapping_add(shift);
st.due_time_for_timers = st.due_time_for_timers.map(|due| due.wrapping_add(shift));
st.due_time_for_background_processing =
st.due_time_for_background_processing.map(|due| due.wrapping_add(shift));
time_shift = Some(shift);
}
}
if st.signaled {
impl_.signal();
}
if st.explicit_background_processing_requested {
match background {
Some(background) => background.request_background_processing(),
None => {
st.explicit_background_processing_requested = false;
self.request_background_processing_locked(&mut st);
}
}
}
st.os_timer_set_to = None;
self.update_os_timer_locked(&mut st);
}
if let Some(shift) = time_shift {
for timer in local.timers.borrow().iter() {
timer.shift_due_time(shift);
}
}
}
pub(crate) fn detach_implementation(local: &DispatcherLocal) {
if !local.attached.get() {
return;
}
let impl_ = local.impl_.borrow().clone();
impl_.timer().remove(local.timer_token.get());
impl_.signaled().remove(local.signaled_token.get());
if let Some(token) = local.background_token.take() {
if let Some(background) = impl_.as_explicit_background_processing() {
background.ready_for_background_processing().remove(token);
}
}
local.attached.set(false);
}
pub(crate) fn detach_from_platform(st: &mut DispatcherState, local: Option<&DispatcherLocal>) {
if let Some(local) = local {
Self::detach_implementation(local);
let previous = local.impl_.replace(Rc::new(DetachedDispatcherImpl::new()));
drop(previous);
}
st.signal = Arc::new(DetachedDispatcherSignal);
st.supports_run_loops = false;
}
}
impl IDispatcher for Dispatcher {
fn check_access(&self) -> bool {
Dispatcher::check_access(self)
}
fn verify_access(&self) {
Dispatcher::verify_access(self)
}
fn post(&self, action: Box<dyn FnOnce() + Send>, priority: DispatcherPriority) {
Dispatcher::post(self, action, priority)
}
}
impl std::fmt::Debug for Dispatcher {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Dispatcher").field("id", &self.id).field("thread", &self.thread).finish()
}
}
fn default_dispatcher_impl() -> Rc<dyn IDispatcherImpl> {
#[cfg(not(target_family = "wasm"))]
{
Rc::new(crate::platform::ManagedDispatcherImpl::new(None))
}
#[cfg(target_family = "wasm")]
{
Rc::new(DetachedDispatcherImpl::new())
}
}
crate::ferro_thread_local! {
static THREAD_ID: ThreadId = thread::current().id();
}
#[inline]
pub(crate) fn current_thread_id() -> ThreadId {
THREAD_ID.try_with(|id| *id).unwrap_or_else(|_| thread::current().id())
}