Files
orchid/orchid-extension/src/system.rs
Lawrence Bethlenfalvy 32d6237dc5 task_local context over context objects
- interner impls logically separate from API in orchid-base (default host interner still in base for testing)
- error reporting, logging, and a variety of other features passed down via context in extension, not yet in host to maintain library-ish profile, should consider options
- no global spawn mechanic, the host has a spawn function but extensions only get a stash for enqueuing async work in sync callbacks which is then explicitly, manually, and with strict order popped and awaited
- still deadlocks nondeterministically for some ungodly reason
2026-01-01 14:54:29 +00:00

173 lines
5.8 KiB
Rust

use std::any::{Any, TypeId};
use std::fmt::Debug;
use std::future::Future;
use std::num::NonZero;
use std::pin::Pin;
use futures::FutureExt;
use futures::future::LocalBoxFuture;
use orchid_api_traits::{Coding, Decode, Encode, Request};
use orchid_base::boxed_iter::BoxedIter;
use orchid_base::name::Sym;
use orchid_base::reqnot::{Receipt, ReqHandle, ReqReader, ReqReaderExt};
use task_local::task_local;
use crate::api;
use crate::atom::{AtomCtx, AtomDynfo, AtomTypeId, AtomicFeatures, ForeignAtom, TAtom, get_info};
use crate::coroutine_exec::Replier;
use crate::entrypoint::request;
use crate::func_atom::{Fun, Lambda};
use crate::lexer::LexerObj;
use crate::parser::ParserObj;
use crate::system_ctor::{CtedObj, SystemCtor};
use crate::tree::GenMember;
/// System as consumed by foreign code
pub trait SystemCard: Debug + Default + Send + Sync + 'static {
type Ctor: SystemCtor;
type Req: Coding;
fn atoms() -> impl IntoIterator<Item = Option<Box<dyn AtomDynfo>>>;
}
pub trait DynSystemCard: Send + Sync + Any + 'static {
fn name(&self) -> &'static str;
/// Atoms explicitly defined by the system card. Do not rely on this for
/// querying atoms as it doesn't include the general atom types
fn atoms(&'_ self) -> BoxedIter<'_, Option<Box<dyn AtomDynfo>>>;
}
/// Atoms supported by this package which may appear in all extensions.
/// The indices of these are bitwise negated, such that the MSB of an atom index
/// marks whether it belongs to this package (0) or the importer (1)
fn general_atoms() -> impl Iterator<Item = Option<Box<dyn AtomDynfo>>> {
[Some(Fun::dynfo()), Some(Lambda::dynfo()), Some(Replier::dynfo())].into_iter()
}
pub fn atom_info_for(
sys: &(impl DynSystemCard + ?Sized),
tid: TypeId,
) -> Option<(AtomTypeId, Box<dyn AtomDynfo>)> {
(sys.atoms().enumerate().map(|(i, o)| (NonZero::new(i as u32 + 1).unwrap(), o)))
.chain(general_atoms().enumerate().map(|(i, o)| (NonZero::new(!(i as u32)).unwrap(), o)))
.filter_map(|(i, o)| o.map(|a| (AtomTypeId(i), a)))
.find(|ent| ent.1.tid() == tid)
}
pub fn atom_by_idx(
sys: &(impl DynSystemCard + ?Sized),
tid: AtomTypeId,
) -> Option<Box<dyn AtomDynfo>> {
if (u32::from(tid.0) >> (u32::BITS - 1)) & 1 == 1 {
general_atoms().nth(!u32::from(tid.0) as usize).unwrap()
} else {
sys.atoms().nth(u32::from(tid.0) as usize - 1).unwrap()
}
}
pub async fn resolv_atom(
sys: &(impl DynSystemCard + ?Sized),
atom: &api::Atom,
) -> Box<dyn AtomDynfo> {
let tid = AtomTypeId::decode(Pin::new(&mut &atom.data.0[..])).await.unwrap();
atom_by_idx(sys, tid).expect("Value of nonexistent type found")
}
impl<T: SystemCard> DynSystemCard for T {
fn name(&self) -> &'static str { T::Ctor::NAME }
fn atoms(&'_ self) -> BoxedIter<'_, Option<Box<dyn AtomDynfo>>> {
Box::new(Self::atoms().into_iter())
}
}
/// System as defined by author
pub trait System: Send + Sync + SystemCard + 'static {
fn prelude() -> impl Future<Output = Vec<Sym>>;
fn env() -> impl Future<Output = Vec<GenMember>>;
fn lexers() -> Vec<LexerObj>;
fn parsers() -> Vec<ParserObj>;
fn request<'a>(
hand: Box<dyn ReqHandle<'a> + 'a>,
req: Self::Req,
) -> impl Future<Output = Receipt<'a>>;
}
pub trait DynSystem: Send + Sync + DynSystemCard + 'static {
fn dyn_prelude(&self) -> LocalBoxFuture<'_, Vec<Sym>>;
fn dyn_env(&self) -> LocalBoxFuture<'_, Vec<GenMember>>;
fn dyn_lexers(&self) -> Vec<LexerObj>;
fn dyn_parsers(&self) -> Vec<ParserObj>;
fn dyn_request<'a>(&self, hand: Box<dyn ReqReader<'a> + 'a>) -> LocalBoxFuture<'a, Receipt<'a>>;
fn card(&self) -> &dyn DynSystemCard;
}
impl<T: System> DynSystem for T {
fn dyn_prelude(&self) -> LocalBoxFuture<'_, Vec<Sym>> { Box::pin(Self::prelude()) }
fn dyn_env(&self) -> LocalBoxFuture<'_, Vec<GenMember>> { Self::env().boxed_local() }
fn dyn_lexers(&self) -> Vec<LexerObj> { Self::lexers() }
fn dyn_parsers(&self) -> Vec<ParserObj> { Self::parsers() }
fn dyn_request<'a>(
&self,
mut hand: Box<dyn ReqReader<'a> + 'a>,
) -> LocalBoxFuture<'a, Receipt<'a>> {
Box::pin(async move {
let value = hand.read_req::<<Self as SystemCard>::Req>().await.unwrap();
Self::request(hand.finish().await, value).await
})
}
fn card(&self) -> &dyn DynSystemCard { self }
}
#[derive(Clone)]
pub(crate) struct SysCtx(pub api::SysId, pub CtedObj);
task_local! {
static SYS_CTX: SysCtx;
}
pub(crate) async fn with_sys<F: Future>(sys: SysCtx, fut: F) -> F::Output {
SYS_CTX.scope(sys, fut).await
}
pub fn sys_id() -> api::SysId { SYS_CTX.with(|cx| cx.0) }
pub fn cted() -> CtedObj { SYS_CTX.with(|cx| cx.1.clone()) }
pub async fn downcast_atom<A>(foreign: ForeignAtom) -> Result<TAtom<A>, ForeignAtom>
where A: AtomicFeatures {
let mut data = &foreign.atom.data.0[..];
let value = AtomTypeId::decode_slice(&mut data);
let cted = cted();
let own_inst = cted.inst();
let owner = if sys_id() == foreign.atom.owner {
own_inst.card()
} else {
cted.deps().find(|s| s.id() == foreign.atom.owner).ok_or_else(|| foreign.clone())?.get_card()
};
if owner.atoms().flatten().all(|dynfo| dynfo.tid() != TypeId::of::<A>()) {
return Err(foreign);
}
let (typ_id, dynfo) = get_info::<A>(owner);
if value != typ_id {
return Err(foreign);
}
let val = dynfo.decode(AtomCtx(data, foreign.atom.drop)).await;
let Ok(value) = val.downcast::<A::Data>() else {
panic!("decode of {} returned wrong type.", dynfo.name());
};
Ok(TAtom { value: *value, untyped: foreign })
}
pub async fn dep_req<Sys: SystemCard, Req: Request + Into<Sys::Req>>(req: Req) -> Req::Response {
let mut msg = Vec::new();
req.into().encode_vec(&mut msg);
let cted = cted();
let own_inst = cted.inst();
let owner = if own_inst.card().type_id() == TypeId::of::<Sys>() {
sys_id()
} else {
(cted.deps().find(|s| s.get_card().type_id() == TypeId::of::<Sys>()))
.expect("System not in dependency array")
.id()
};
let reply = request(api::SysFwd(owner, msg)).await;
Req::Response::decode(std::pin::pin!(&reply[..])).await.unwrap()
}