use std::any::{Any, TypeId, type_name}; use std::fmt; use std::num::NonZero; use std::rc::Rc; use memo_map::MemoMap; use orchid_base::builtin::Spawner; use orchid_base::interner::Interner; use orchid_base::logging::Logger; use orchid_base::reqnot::ReqNot; use task_local::task_local; use crate::api; use crate::system_ctor::CtedObj; #[derive(Clone)] pub struct SysCtx(Rc>>); impl SysCtx { pub fn new( id: api::SysId, i: Interner, reqnot: ReqNot, spawner: Spawner, logger: Logger, cted: CtedObj, ) -> Self { let this = Self(Rc::new(MemoMap::new())); this.add(id).add(i).add(reqnot).add(spawner).add(logger).add(cted); this } pub fn add(&self, t: T) -> &Self { assert!(self.0.insert(TypeId::of::(), Box::new(t)), "Key already exists"); self } pub fn get_or_insert(&self, f: impl FnOnce() -> T) -> &T { (self.0.get_or_insert_owned(TypeId::of::(), || Box::new(f())).downcast_ref()) .expect("Keyed by TypeId") } pub fn get_or_default(&self) -> &T { self.get_or_insert(T::default) } pub fn try_get(&self) -> Option<&T> { Some(self.0.get(&TypeId::of::())?.downcast_ref().expect("Keyed by TypeId")) } pub fn get(&self) -> &T { self.try_get().unwrap_or_else(|| panic!("Context {} missing", type_name::())) } /// Shorthand to get the messaging link pub fn reqnot(&self) -> &ReqNot { self.get::>() } /// Shorthand to get the system ID pub fn sys_id(&self) -> api::SysId { *self.get::() } /// Spawn a task that will eventually be executed asynchronously pub fn spawn(&self, f: impl Future + 'static) { (self.get::())(Box::pin(CTX.scope(self.clone(), f))) } /// Shorthand to get the logger pub fn logger(&self) -> &Logger { self.get::() } /// Shorthand to get the constructed system object pub fn cted(&self) -> &CtedObj { self.get::() } } impl fmt::Debug for SysCtx { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "SysCtx({:?})", self.sys_id()) } } pub trait SysCtxEntry: 'static + Sized {} impl SysCtxEntry for api::SysId {} impl SysCtxEntry for ReqNot {} impl SysCtxEntry for Spawner {} impl SysCtxEntry for CtedObj {} impl SysCtxEntry for Logger {} impl SysCtxEntry for Interner {} task_local! { static CTX: SysCtx; } pub async fn with_ctx(ctx: SysCtx, f: F) -> F::Output { CTX.scope(ctx, f).await } pub fn ctx() -> SysCtx { CTX.get() } /// Shorthand to get the [Interner] instance pub fn i() -> Interner { ctx().get::().clone() } pub fn mock_ctx() -> SysCtx { let ctx = SysCtx(Rc::default()); ctx .add(Logger::new(api::LogStrategy::StdErr)) .add(Interner::new_master()) .add::(Rc::new(|_| panic!("Cannot fork in test environment"))) .add(api::SysId(NonZero::::MIN)); ctx }