use std::cell::RefCell; use std::num::{NonZero, NonZeroU16}; use std::rc::{Rc, Weak}; use std::time::Duration; use std::{fmt, ops}; use futures::future::LocalBoxFuture; use futures_locks::RwLock; use hashbrown::HashMap; use crate::api; use crate::expr_store::ExprStore; use crate::logger::LoggerImpl; use crate::system::{System, WeakSystem}; use crate::tree::WeakRoot; pub trait JoinHandle { /// It is guaranteed that the future will never be polled after this is called fn abort(&self); /// take the future out of the task. If the return value /// is dropped, the spawned future is also dropped fn join(self: Box) -> LocalBoxFuture<'static, ()>; } pub trait Spawner { /// Spawn a future off-task, but in the same thread. If all objects associated /// with the owning [Ctx] are dropped (eg. expressions, extensions, systems) /// and no extensions create permanent ref loops, then all tasks will /// eventually settle, therefore the implementor of this interface should not /// exit while there are pending tasks to allow external communication /// channels to cleanly shut down. fn spawn_obj(&self, delay: Duration, fut: LocalBoxFuture<'static, ()>) -> Box; } pub struct CtxData { spawner: Rc, pub systems: RwLock>, pub system_id: RefCell, pub exprs: ExprStore, pub root: RwLock, pub logger: LoggerImpl, } #[derive(Clone)] pub struct Ctx(Rc); impl ops::Deref for Ctx { type Target = CtxData; fn deref(&self) -> &Self::Target { &self.0 } } #[derive(Clone)] pub struct WeakCtx(Weak); impl WeakCtx { #[must_use] pub fn try_upgrade(&self) -> Option { Some(Ctx(self.0.upgrade()?)) } #[must_use] pub fn upgrade(&self) -> Ctx { self.try_upgrade().expect("Ctx manually kept alive until exit") } } impl Ctx { #[must_use] pub fn new(spawner: impl Spawner + 'static, logger: LoggerImpl) -> Self { Self(Rc::new(CtxData { spawner: Rc::new(spawner), systems: RwLock::default(), system_id: RefCell::new(NonZero::new(1).unwrap()), exprs: ExprStore::default(), root: RwLock::default(), logger, })) } /// Spawn a parallel future that you can join at any later time. /// /// Don't use this for async Drop, use [orchid_base::stash] instead. /// If you use this for an actor object, make sure to actually join the /// handle. #[must_use] pub fn spawn( &self, delay: Duration, fut: impl Future + 'static, ) -> Box { self.spawner.spawn_obj(delay, Box::pin(fut)) } #[must_use] pub(crate) async fn system_inst(&self, id: api::SysId) -> Option { self.systems.read().await.get(&id).and_then(WeakSystem::upgrade) } #[must_use] pub(crate) fn next_sys_id(&self) -> api::SysId { let mut g = self.system_id.borrow_mut(); *g = g.checked_add(1).unwrap_or(NonZeroU16::new(1).unwrap()); api::SysId(*g) } #[must_use] pub fn downgrade(&self) -> WeakCtx { WeakCtx(Rc::downgrade(&self.0)) } } impl fmt::Debug for Ctx { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Ctx").field("system_id", &self.system_id).finish_non_exhaustive() } }