use std::cell::{Cell, RefCell};
use std::future::Future;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use super::dispatcher_task::{wrap_local_future, wrap_send_future};
use super::{Dispatcher, DispatcherPriority, DispatcherTask};
use crate::reactive::IDisposable;
static AUTO_INSTALL: AtomicBool = AtomicBool::new(true);
crate::ferro_thread_local! {
static CURRENT: RefCell<Option<Arc<FerroSynchronizationContext>>> = const { RefCell::new(None) };
}
pub struct FerroSynchronizationContext {
dispatcher: Arc<Dispatcher>,
priority: DispatcherPriority,
}
impl FerroSynchronizationContext {
#[allow(clippy::new_ret_no_self)]
pub fn new() -> Arc<Self> {
Self::with_dispatcher(&Dispatcher::current_dispatcher(), DispatcherPriority::DEFAULT)
}
pub fn with_priority(priority: DispatcherPriority) -> Arc<Self> {
Self::with_dispatcher(&Dispatcher::current_dispatcher(), priority)
}
pub fn with_dispatcher(dispatcher: &Arc<Dispatcher>, priority: DispatcherPriority) -> Arc<Self> {
Arc::new(Self { dispatcher: dispatcher.clone(), priority })
}
pub fn priority(&self) -> DispatcherPriority {
self.priority
}
pub fn dispatcher(&self) -> &Arc<Dispatcher> {
&self.dispatcher
}
pub fn current() -> Option<Arc<FerroSynchronizationContext>> {
CURRENT.try_with(|current| current.borrow().clone()).ok().flatten()
}
pub fn set_current(context: Option<Arc<FerroSynchronizationContext>>) -> Option<Arc<FerroSynchronizationContext>> {
CURRENT.try_with(|current| current.replace(context)).ok().flatten()
}
pub fn auto_install() -> bool {
AUTO_INSTALL.load(Ordering::Relaxed)
}
pub fn set_auto_install(value: bool) {
AUTO_INSTALL.store(value, Ordering::Relaxed);
}
pub fn install_if_needed() {
if !Self::auto_install() || Self::current().is_some() {
return;
}
Self::set_current(Some(Dispatcher::current_dispatcher().get_context_with_priority(DispatcherPriority::NORMAL)));
}
pub fn post(&self, callback: impl FnOnce() + Send + 'static) {
self.dispatcher.post(callback, self.priority);
}
pub fn post_local(&self, callback: impl FnOnce() + 'static) {
self.dispatcher.post_local(callback, self.priority);
}
pub fn send(&self, callback: impl FnOnce() + Send + 'static) {
if self.dispatcher.check_access() {
self.dispatcher.send(callback, Some(DispatcherPriority::SEND));
} else {
self.dispatcher.send(callback, Some(self.priority));
}
}
pub fn send_local(&self, callback: impl FnOnce() + 'static) {
self.dispatcher.send_local(callback, Some(DispatcherPriority::SEND));
}
pub fn to_task_scheduler(self: &Arc<Self>) -> DispatcherTaskScheduler {
DispatcherTaskScheduler { context: self.clone() }
}
pub fn ensure(priority: DispatcherPriority) -> RestoreContext {
Self::ensure_with_dispatcher(&Dispatcher::current_dispatcher(), priority)
}
pub fn ensure_with_dispatcher(dispatcher: &Dispatcher, priority: DispatcherPriority) -> RestoreContext {
let unchanged = CURRENT
.try_with(|current| current.borrow().as_ref().is_some_and(|context| context.priority == priority))
.unwrap_or(true);
if unchanged {
return RestoreContext::default();
}
dispatcher.verify_access();
let old_context = Self::set_current(Some(dispatcher.get_context_with_priority(priority)));
RestoreContext { old_context: Cell::new(Some(old_context)) }
}
}
#[derive(Default)]
pub struct RestoreContext {
old_context: Cell<Option<Option<Arc<FerroSynchronizationContext>>>>,
}
impl IDisposable for RestoreContext {
fn dispose(&self) {
if let Some(old_context) = self.old_context.take() {
FerroSynchronizationContext::set_current(old_context);
}
}
}
impl Drop for RestoreContext {
fn drop(&mut self) {
self.dispose();
}
}
#[derive(Clone)]
pub struct DispatcherTaskScheduler {
context: Arc<FerroSynchronizationContext>,
}
impl DispatcherTaskScheduler {
pub fn dispatcher(&self) -> &Arc<Dispatcher> {
&self.context.dispatcher
}
pub fn priority(&self) -> DispatcherPriority {
self.context.priority
}
pub fn spawn<Fut>(&self, future: Fut) -> DispatcherTask<Fut::Output>
where
Fut: Future + Send + 'static,
Fut::Output: Send + 'static,
{
let dispatcher = self.context.dispatcher.clone();
let priority = self.context.priority;
let (task, completion, wrap) = wrap_send_future::<Fut>(&dispatcher);
let target = dispatcher.clone();
dispatcher.post(move || target.start_task(priority, wrap(future), completion, true), priority);
task
}
pub fn spawn_local<Fut>(&self, future: Fut) -> DispatcherTask<Fut::Output>
where
Fut: Future + 'static,
Fut::Output: 'static,
{
let dispatcher = &self.context.dispatcher;
dispatcher.verify_access();
let (task, completion, wrap) = wrap_local_future::<Fut>(dispatcher);
dispatcher.start_task(self.context.priority, wrap(future), completion, false);
task
}
pub fn start_local<Fut>(&self, future: Fut) -> DispatcherTask<Fut::Output>
where
Fut: Future + 'static,
Fut::Output: 'static,
{
let dispatcher = &self.context.dispatcher;
dispatcher.verify_access();
let (task, completion, wrap) = wrap_local_future::<Fut>(dispatcher);
dispatcher.start_task(self.context.priority, wrap(future), completion, true);
task
}
}
impl Dispatcher {
pub(crate) fn get_context_with_priority(&self, priority: DispatcherPriority) -> Arc<FerroSynchronizationContext> {
DispatcherPriority::validate(priority, "priority");
let Some(local) = self.try_local() else {
return FerroSynchronizationContext::with_dispatcher(&self.to_arc(), priority);
};
let index = (priority.value() - DispatcherPriority::MIN_VALUE.value()) as usize;
let mut contexts = local.priority_contexts.borrow_mut();
if contexts.is_empty() {
let count = (DispatcherPriority::MAX_VALUE.value() - DispatcherPriority::MIN_VALUE.value() + 1) as usize;
contexts.resize(count, None);
}
contexts[index]
.get_or_insert_with(|| FerroSynchronizationContext::with_dispatcher(&self.to_arc(), priority))
.clone()
}
pub fn to_task_scheduler(&self) -> DispatcherTaskScheduler {
match FerroSynchronizationContext::current() {
Some(context) => self.to_task_scheduler_with_priority(context.priority()),
None => self.to_task_scheduler_with_priority(DispatcherPriority::DEFAULT),
}
}
pub fn to_task_scheduler_with_priority(&self, priority: DispatcherPriority) -> DispatcherTaskScheduler {
self.get_context_with_priority(priority).to_task_scheduler()
}
}