Files
orchid/orchid-extension/src/context.rs

91 lines
2.9 KiB
Rust

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<MemoMap<TypeId, Box<dyn Any>>>);
impl SysCtx {
pub fn new(
id: api::SysId,
i: Interner,
reqnot: ReqNot<api::ExtMsgSet>,
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<T: SysCtxEntry>(&self, t: T) -> &Self {
assert!(self.0.insert(TypeId::of::<T>(), Box::new(t)), "Key already exists");
self
}
pub fn get_or_insert<T: SysCtxEntry>(&self, f: impl FnOnce() -> T) -> &T {
(self.0.get_or_insert_owned(TypeId::of::<T>(), || Box::new(f())).downcast_ref())
.expect("Keyed by TypeId")
}
pub fn get_or_default<T: SysCtxEntry + Default>(&self) -> &T { self.get_or_insert(T::default) }
pub fn try_get<T: SysCtxEntry>(&self) -> Option<&T> {
Some(self.0.get(&TypeId::of::<T>())?.downcast_ref().expect("Keyed by TypeId"))
}
pub fn get<T: SysCtxEntry>(&self) -> &T {
self.try_get().unwrap_or_else(|| panic!("Context {} missing", type_name::<T>()))
}
/// Shorthand to get the messaging link
pub fn reqnot(&self) -> &ReqNot<api::ExtMsgSet> { self.get::<ReqNot<api::ExtMsgSet>>() }
/// Shorthand to get the system ID
pub fn sys_id(&self) -> api::SysId { *self.get::<api::SysId>() }
/// Spawn a task that will eventually be executed asynchronously
pub fn spawn(&self, f: impl Future<Output = ()> + 'static) {
(self.get::<Spawner>())(Box::pin(CTX.scope(self.clone(), f)))
}
/// Shorthand to get the logger
pub fn logger(&self) -> &Logger { self.get::<Logger>() }
/// Shorthand to get the constructed system object
pub fn cted(&self) -> &CtedObj { self.get::<CtedObj>() }
}
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<api::ExtMsgSet> {}
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<F: Future>(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::<Interner>().clone() }
pub fn mock_ctx() -> SysCtx {
let ctx = SysCtx(Rc::default());
ctx
.add(Logger::new(api::LogStrategy::StdErr))
.add(Interner::new_master())
.add::<Spawner>(Rc::new(|_| panic!("Cannot fork in test environment")))
.add(api::SysId(NonZero::<u16>::MIN));
ctx
}