Separated orchid-host and orchid-extension

This is an architectural change that allows me to implment specifics first and generalize along observed symmetries in orchid-base
This commit is contained in:
2024-05-01 21:20:17 +02:00
parent aa3f7e99ab
commit bc3b10674b
25 changed files with 562 additions and 357 deletions

42
Cargo.lock generated
View File

@@ -4,11 +4,12 @@ version = 3
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.8.9" version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"getrandom",
"once_cell", "once_cell",
"version_check", "version_check",
"zerocopy", "zerocopy",
@@ -164,10 +165,21 @@ dependencies = [
] ]
[[package]] [[package]]
name = "hashbrown" name = "getrandom"
version = "0.14.3" version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
dependencies = [ dependencies = [
"ahash", "ahash",
"allocator-api2", "allocator-api2",
@@ -273,14 +285,28 @@ dependencies = [
name = "orchid-extension" name = "orchid-extension"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ahash",
"hashbrown",
"itertools",
"orchid-api", "orchid-api",
"orchid-api-traits", "orchid-api-traits",
"orchid-base", "orchid-base",
"ordered-float",
] ]
[[package]] [[package]]
name = "orchid-host" name = "orchid-host"
version = "0.1.0" version = "0.1.0"
dependencies = [
"derive_destructure",
"hashbrown",
"itertools",
"lazy_static",
"orchid-api",
"orchid-api-traits",
"orchid-base",
"ordered-float",
]
[[package]] [[package]]
name = "orchid-std" name = "orchid-std"
@@ -498,6 +524,12 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"

View File

@@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::hash::Hash; use std::hash::Hash;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::iter; use std::iter;
use std::ops::Range; use std::ops::{Range, RangeInclusive};
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
@@ -24,8 +24,8 @@ pub trait Encode {
vec vec
} }
} }
pub trait Coding: Encode + Decode {} pub trait Coding: Encode + Decode + Clone {}
impl<T: Encode + Decode> Coding for T {} impl<T: Encode + Decode + Clone> Coding for T {}
macro_rules! num_impl { macro_rules! num_impl {
($number:ty, $size:expr) => { ($number:ty, $size:expr) => {
@@ -81,7 +81,7 @@ nonzero_impl!(std::num::NonZeroI32);
nonzero_impl!(std::num::NonZeroI64); nonzero_impl!(std::num::NonZeroI64);
nonzero_impl!(std::num::NonZeroI128); nonzero_impl!(std::num::NonZeroI128);
impl<'a, T: Encode> Encode for &'a T { impl<'a, T: Encode + 'a> Encode for &'a T {
fn encode<W: Write>(&self, write: &mut W) { (**self).encode(write) } fn encode<W: Write>(&self, write: &mut W) { (**self).encode(write) }
} }
impl<T: Decode + FloatCore> Decode for NotNan<T> { impl<T: Decode + FloatCore> Decode for NotNan<T> {
@@ -233,16 +233,25 @@ impl<T: Decode, const N: usize> Decode for [T; N] {
impl<T: Encode, const N: usize> Encode for [T; N] { impl<T: Encode, const N: usize> Encode for [T; N] {
fn encode<W: Write>(&self, write: &mut W) { self.iter().for_each(|t| t.encode(write)) } fn encode<W: Write>(&self, write: &mut W) { self.iter().for_each(|t| t.encode(write)) }
} }
impl<T: Decode> Decode for Range<T> {
fn decode<R: Read>(read: &mut R) -> Self { T::decode(read)..T::decode(read) } macro_rules! two_end_range {
} ($this:ident, $name:tt, $op:tt, $start:expr, $end:expr) => {
impl<T: Encode> Encode for Range<T> { impl<T: Decode> Decode for $name<T> {
fn decode<R: Read>(read: &mut R) -> Self { T::decode(read) $op T::decode(read) }
}
impl<T: Encode> Encode for $name<T> {
fn encode<W: Write>(&self, write: &mut W) { fn encode<W: Write>(&self, write: &mut W) {
self.start.encode(write); let $this = self;
self.end.encode(write); ($start).encode(write);
($end).encode(write);
}
}
} }
} }
two_end_range!(x, Range, .., x.start, x.end);
two_end_range!(x, RangeInclusive, ..=, x.start(), x.end());
macro_rules! smart_ptr { macro_rules! smart_ptr {
($name:tt) => { ($name:tt) => {
impl<T: Decode> Decode for $name<T> { impl<T: Decode> Decode for $name<T> {

View File

@@ -9,4 +9,4 @@ pub use hierarchy::{
Base, Extends, HierarchyRole, InHierarchy, Subtype, TLBool, TLFalse, TLTrue, UnderRoot, Base, Extends, HierarchyRole, InHierarchy, Subtype, TLBool, TLFalse, TLTrue, UnderRoot,
UnderRootImpl, UnderRootImpl,
}; };
pub use relations::{MsgSet, Request}; pub use relations::{Channel, MsgSet, Request};

View File

@@ -5,9 +5,12 @@ pub trait Request: Coding + Sized + Send + 'static {
fn respond(&self, rep: Self::Response) -> Vec<u8> { rep.enc_vec() } fn respond(&self, rep: Self::Response) -> Vec<u8> { rep.enc_vec() }
} }
pub trait MsgSet { pub trait Channel: 'static {
type InReq: Coding + Sized + Send + 'static; type Req: Coding + Sized + Send + 'static;
type InNot: Coding + Sized + Send + 'static; type Notif: Coding + Sized + Send + 'static;
type OutReq: Coding + Sized + Send + 'static; }
type OutNot: Coding + Sized + Send + 'static;
pub trait MsgSet {
type In: Channel;
type Out: Channel;
} }

View File

@@ -1,3 +1,5 @@
use std::ops::RangeInclusive;
use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request; use orchid_api_traits::Request;
@@ -16,11 +18,18 @@ pub enum ParserReq {
pub type LexerId = u16; pub type LexerId = u16;
#[derive(Clone, Debug, Coding)]
pub struct Lexer {
id: LexerId,
drop: bool,
notify_chars: Option<Vec<RangeInclusive<char>>>,
}
#[derive(Clone, Debug, Coding, Hierarchy)] #[derive(Clone, Debug, Coding, Hierarchy)]
#[extends(ParserReq, HostExtReq)] #[extends(ParserReq, HostExtReq)]
pub struct MkLexer(pub SysId, pub TStr); pub struct MkLexer(pub SysId, pub TStr);
impl Request for MkLexer { impl Request for MkLexer {
type Response = LexerId; type Response = Lexer;
} }
#[derive(Clone, Debug, Coding, Hierarchy)] #[derive(Clone, Debug, Coding, Hierarchy)]
@@ -50,6 +59,7 @@ impl Request for SubLex {
type Response = SubLex; type Response = SubLex;
} }
pub struct ParseLine { #[derive(Clone, Debug, Coding)]
pub struct LexerDrop;
} pub struct ParseLine {}

View File

@@ -26,7 +26,7 @@ use std::io::{Read, Write};
use derive_more::{From, TryInto}; use derive_more::{From, TryInto};
use orchid_api_derive::{Coding, Hierarchy}; use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::{read_exact, write_exact, Decode, Encode, MsgSet, Request}; use orchid_api_traits::{read_exact, write_exact, Channel, Decode, Encode, MsgSet, Request};
use crate::{atom, expr, intern, parser, system, tree}; use crate::{atom, expr, intern, parser, system, tree};
@@ -77,12 +77,18 @@ pub enum ExtHostReq {
} }
/// Notifications sent from the extension to the host /// Notifications sent from the extension to the host
#[derive(Coding, From, TryInto)] #[derive(Debug, Clone, Coding, From, TryInto)]
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
pub enum ExtHostNotif { pub enum ExtHostNotif {
Expr(expr::ExprNotif), Expr(expr::ExprNotif),
} }
pub struct ExtHostChannel;
impl Channel for ExtHostChannel {
type Notif = ExtHostNotif;
type Req = ExtHostReq;
}
/// Requests running from the host to the extension /// Requests running from the host to the extension
#[derive(Clone, Debug, Coding, Hierarchy)] #[derive(Clone, Debug, Coding, Hierarchy)]
#[extendable] #[extendable]
@@ -101,25 +107,28 @@ pub enum HostExtReq {
pub enum HostExtNotif { pub enum HostExtNotif {
SystemDrop(system::SystemDrop), SystemDrop(system::SystemDrop),
AtomDrop(atom::AtomDrop), AtomDrop(atom::AtomDrop),
LexerDrop(parser::LexerDrop),
/// The host can assume that after this notif is sent, a correctly written /// The host can assume that after this notif is sent, a correctly written
/// extension will eventually exit. /// extension will eventually exit.
Exit, Exit,
} }
pub struct HostExtChannel;
impl Channel for HostExtChannel {
type Notif = HostExtNotif;
type Req = HostExtReq;
}
/// Message set viewed from the extension's perspective /// Message set viewed from the extension's perspective
pub struct ExtMsgSet; pub struct ExtMsgSet;
impl MsgSet for ExtMsgSet { impl MsgSet for ExtMsgSet {
type InNot = HostExtNotif; type In = HostExtChannel;
type InReq = HostExtReq; type Out = ExtHostChannel;
type OutNot = ExtHostNotif;
type OutReq = ExtHostReq;
} }
/// Message Set viewed from the host's perspective /// Message Set viewed from the host's perspective
pub struct HostMsgSet; pub struct HostMsgSet;
impl MsgSet for HostMsgSet { impl MsgSet for HostMsgSet {
type InNot = ExtHostNotif; type In = ExtHostChannel;
type InReq = ExtHostReq; type Out = HostExtChannel;
type OutNot = HostExtNotif;
type OutReq = HostExtReq;
} }

View File

@@ -11,7 +11,7 @@ pub type SysDeclId = u16;
pub type SysId = u16; pub type SysId = u16;
/// Details about a system provided by this library /// Details about a system provided by this library
#[derive(Coding)] #[derive(Debug, Clone, Coding)]
pub struct SystemDecl { pub struct SystemDecl {
/// ID of the system, unique within the library /// ID of the system, unique within the library
pub id: SysDeclId, pub id: SysDeclId,

View File

@@ -5,8 +5,6 @@ use orchid_api::expr::{Clause, Expr};
use orchid_api::location::Location; use orchid_api::location::Location;
use super::traits::{GenClause, Generable}; use super::traits::{GenClause, Generable};
use crate::expr::RtExpr;
use crate::host::AtomHand;
use crate::intern::{deintern, intern}; use crate::intern::{deintern, intern};
fn safely_reinterpret<In: 'static, Out: 'static>(x: In) -> Result<Out, In> { fn safely_reinterpret<In: 'static, Out: 'static>(x: In) -> Result<Out, In> {
@@ -24,7 +22,7 @@ impl GenClause for Expr {
fn generate<T: super::traits::Generable>(&self, ctx: T::Ctx<'_>, pop: &impl Fn() -> T) -> T { fn generate<T: super::traits::Generable>(&self, ctx: T::Ctx<'_>, pop: &impl Fn() -> T) -> T {
match &self.clause { match &self.clause {
Clause::Arg(arg) => T::arg(ctx, deintern(*arg).as_str()), Clause::Arg(arg) => T::arg(ctx, deintern(*arg).as_str()),
Clause::Atom(atom) => T::atom(ctx, AtomHand::from_api(atom.clone())), Clause::Atom(atom) => T::atom(ctx, atom.clone()),
Clause::Call(f, x) => T::apply(ctx, |c| f.generate(c, pop), |c| x.generate(c, pop)), Clause::Call(f, x) => T::apply(ctx, |c| f.generate(c, pop), |c| x.generate(c, pop)),
Clause::Lambda(arg, b) => T::lambda(ctx, deintern(*arg).as_str(), |ctx| b.generate(ctx, pop)), Clause::Lambda(arg, b) => T::lambda(ctx, deintern(*arg).as_str(), |ctx| b.generate(ctx, pop)),
Clause::Seq(n1, n2) => T::seq(ctx, |c| n1.generate(c, pop), |c| n2.generate(c, pop)), Clause::Seq(n1, n2) => T::seq(ctx, |c| n1.generate(c, pop), |c| n2.generate(c, pop)),

View File

@@ -1,12 +1,13 @@
//! Various elemental components to build expression trees that all implement //! Various elemental components to build expression trees that all implement
//! [GenClause]. //! [GenClause].
use orchid_api::atom::Atom;
use super::traits::{GenClause, Generable}; use super::traits::{GenClause, Generable};
use crate::host::AtomHand;
/// A trivial atom /// A trivial atom
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SysAtom(pub AtomHand); pub struct SysAtom(pub Atom);
impl GenClause for SysAtom { impl GenClause for SysAtom {
fn generate<T: Generable>(&self, ctx: T::Ctx<'_>, _: &impl Fn() -> T) -> T { fn generate<T: Generable>(&self, ctx: T::Ctx<'_>, _: &impl Fn() -> T) -> T {
T::atom(ctx, self.0.clone()) T::atom(ctx, self.0.clone())

View File

@@ -5,7 +5,7 @@ use std::cell::RefCell;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::fmt; use std::fmt;
use crate::host::AtomHand; use orchid_api::atom::Atom;
/// Representations of the Orchid expression tree that can describe basic /// Representations of the Orchid expression tree that can describe basic
/// language elements. /// language elements.
@@ -13,7 +13,7 @@ pub trait Generable: Sized + 'static {
/// Context information defined by parents. Generators just forward this. /// Context information defined by parents. Generators just forward this.
type Ctx<'a>: Sized; type Ctx<'a>: Sized;
/// Wrap external data. /// Wrap external data.
fn atom(ctx: Self::Ctx<'_>, a: AtomHand) -> Self; fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self;
/// Generate a reference to a constant /// Generate a reference to a constant
fn constant<'a>(ctx: Self::Ctx<'_>, name: impl IntoIterator<Item = &'a str>) -> Self; fn constant<'a>(ctx: Self::Ctx<'_>, name: impl IntoIterator<Item = &'a str>) -> Self;
/// Generate a function call given the function and its argument /// Generate a function call given the function and its argument

View File

@@ -4,13 +4,13 @@
use std::fmt; use std::fmt;
use dyn_clone::{clone_box, DynClone}; use dyn_clone::{clone_box, DynClone};
use orchid_api::atom::Atom;
use orchid_api::expr::Expr; use orchid_api::expr::Expr;
use trait_set::trait_set; use trait_set::trait_set;
use super::tpl; use super::tpl;
use super::traits::{Gen, GenClause}; use super::traits::{Gen, GenClause};
use crate::combine::Combine; use crate::combine::Combine;
use crate::host::AtomHand;
use crate::tree::{ModEntry, ModMember, TreeConflict}; use crate::tree::{ModEntry, ModMember, TreeConflict};
trait_set! { trait_set! {
@@ -64,14 +64,14 @@ pub fn ent<K: AsRef<str>>(
/// Describe an [Atomic] /// Describe an [Atomic]
#[must_use] #[must_use]
pub fn atom_leaf(atom: AtomHand) -> ConstTree { leaf(tpl::SysAtom(atom)) } pub fn atom_leaf(atom: Atom) -> ConstTree { leaf(tpl::SysAtom(atom)) }
/// Describe an [Atomic] which appears as an entry in a [ConstTree::tree] /// Describe an [Atomic] which appears as an entry in a [ConstTree::tree]
/// ///
/// The unarray is used to trick rustfmt into breaking the atom into a block /// The unarray is used to trick rustfmt into breaking the atom into a block
/// without breaking this call into a block /// without breaking this call into a block
#[must_use] #[must_use]
pub fn atom_ent<K: AsRef<str>>(key: K, [atom]: [AtomHand; 1]) -> (K, ConstTree) { pub fn atom_ent<K: AsRef<str>>(key: K, [atom]: [Atom; 1]) -> (K, ConstTree) {
(key, atom_leaf(atom)) (key, atom_leaf(atom))
} }

View File

@@ -1,10 +1,9 @@
pub mod boxed_iter; pub mod boxed_iter;
pub mod child; pub mod msg;
pub mod clone; pub mod clone;
pub mod combine; pub mod combine;
pub mod event; pub mod event;
pub mod expr; // pub mod gen;
pub mod gen;
pub mod intern; pub mod intern;
pub mod location; pub mod location;
pub mod name; pub mod name;

16
orchid-base/src/msg.rs Normal file
View File

@@ -0,0 +1,16 @@
use std::io;
pub fn send_msg(write: &mut impl io::Write, msg: &[u8]) -> io::Result<()> {
write.write_all(&(u32::try_from(msg.len()).unwrap()).to_be_bytes())?;
write.write_all(msg)?;
write.flush()
}
pub fn recv_msg(read: &mut impl io::Read) -> io::Result<Vec<u8>> {
let mut len = [0u8; 4];
read.read_exact(&mut len)?;
let len = u32::from_be_bytes(len);
let mut msg = vec![0u8; len as usize];
read.read_exact(&mut msg)?;
Ok(msg)
}

View File

@@ -6,13 +6,13 @@ use std::sync::{Arc, Mutex};
use dyn_clone::{clone_box, DynClone}; use dyn_clone::{clone_box, DynClone};
use hashbrown::HashMap; use hashbrown::HashMap;
use orchid_api_traits::{Coding, Decode, Encode, MsgSet, Request}; use orchid_api_traits::{Channel, Coding, Decode, Encode, MsgSet, Request};
use trait_set::trait_set; use trait_set::trait_set;
trait_set! { trait_set! {
pub trait SendFn<T: MsgSet> = for<'a> FnMut(&'a [u8], ReqNot<T>) + DynClone + Send + 'static; pub trait SendFn<T: MsgSet> = for<'a> FnMut(&'a [u8], ReqNot<T>) + DynClone + Send + 'static;
pub trait ReqFn<T: MsgSet> = FnMut(RequestHandle<T>) + Send + 'static; pub trait ReqFn<T: MsgSet> = FnMut(RequestHandle<T>) + Send + 'static;
pub trait NotifFn<T: MsgSet> = for<'a> FnMut(T::InNot, ReqNot<T>) + Send + Sync + 'static; pub trait NotifFn<T: MsgSet> = for<'a> FnMut(<T::In as Channel>::Notif, ReqNot<T>) + Send + Sync + 'static;
} }
fn get_id(message: &[u8]) -> (u64, &[u8]) { fn get_id(message: &[u8]) -> (u64, &[u8]) {
@@ -21,14 +21,14 @@ fn get_id(message: &[u8]) -> (u64, &[u8]) {
pub struct RequestHandle<T: MsgSet> { pub struct RequestHandle<T: MsgSet> {
id: u64, id: u64,
message: T::InReq, message: <T::In as Channel>::Req,
send: Box<dyn SendFn<T>>, send: Box<dyn SendFn<T>>,
parent: ReqNot<T>, parent: ReqNot<T>,
fulfilled: AtomicBool, fulfilled: AtomicBool,
} }
impl<MS: MsgSet> RequestHandle<MS> { impl<MS: MsgSet> RequestHandle<MS> {
pub fn reqnot(&self) -> ReqNot<MS> { self.parent.clone() } pub fn reqnot(&self) -> ReqNot<MS> { self.parent.clone() }
pub fn req(&self) -> &MS::InReq { &self.message } pub fn req(&self) -> &<MS::In as Channel>::Req { &self.message }
fn respond(&self, response: &impl Encode) { fn respond(&self, response: &impl Encode) {
assert!(!self.fulfilled.swap(true, Ordering::Relaxed), "Already responded"); assert!(!self.fulfilled.swap(true, Ordering::Relaxed), "Already responded");
let mut buf = (!self.id).to_be_bytes().to_vec(); let mut buf = (!self.id).to_be_bytes().to_vec();
@@ -78,21 +78,21 @@ impl<T: MsgSet> ReqNot<T> {
let mut g = self.0.lock().unwrap(); let mut g = self.0.lock().unwrap();
let (id, payload) = get_id(&message[..]); let (id, payload) = get_id(&message[..]);
if id == 0 { if id == 0 {
(g.notif)(T::InNot::decode(&mut &payload[..]), self.clone()) (g.notif)(<T::In as Channel>::Notif::decode(&mut &payload[..]), self.clone())
} else if 0 < id.bitand(1 << 63) { } else if 0 < id.bitand(1 << 63) {
let sender = g.responses.remove(&!id).expect("Received response for invalid message"); let sender = g.responses.remove(&!id).expect("Received response for invalid message");
sender.send(message).unwrap(); sender.send(message).unwrap();
} else { } else {
let send = clone_box(&*g.send); let send = clone_box(&*g.send);
let message = T::InReq::decode(&mut &payload[..]); let message = <T::In as Channel>::Req::decode(&mut &payload[..]);
(g.req)(RequestHandle { id, message, send, fulfilled: false.into(), parent: self.clone() }) (g.req)(RequestHandle { id, message, send, fulfilled: false.into(), parent: self.clone() })
} }
} }
pub fn notify<N: Coding + Into<T::OutNot>>(&self, notif: N) { pub fn notify<N: Coding + Into<<T::Out as Channel>::Notif>>(&self, notif: N) {
let mut send = clone_box(&*self.0.lock().unwrap().send); let mut send = clone_box(&*self.0.lock().unwrap().send);
let mut buf = vec![0; 8]; let mut buf = vec![0; 8];
let msg: T::OutNot = notif.into(); let msg: <T::Out as Channel>::Notif = notif.into();
msg.encode(&mut buf); msg.encode(&mut buf);
send(&buf, self.clone()) send(&buf, self.clone())
} }
@@ -112,7 +112,7 @@ impl<'a, T> DynRequester for MappedRequester<'a, T> {
} }
impl<T: MsgSet> DynRequester for ReqNot<T> { impl<T: MsgSet> DynRequester for ReqNot<T> {
type Transfer = T::OutReq; type Transfer = <T::Out as Channel>::Req;
fn raw_request(&self, req: Self::Transfer) -> RawReply { fn raw_request(&self, req: Self::Transfer) -> RawReply {
let mut g = self.0.lock().unwrap(); let mut g = self.0.lock().unwrap();
let id = g.id; let id = g.id;
@@ -156,23 +156,27 @@ mod test {
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use orchid_api_derive::Coding; use orchid_api_derive::Coding;
use orchid_api_traits::Request; use orchid_api_traits::{Channel, Request};
use super::{MsgSet, ReqNot}; use super::{MsgSet, ReqNot};
use crate::{clone, reqnot::Requester as _}; use crate::{clone, reqnot::Requester as _};
#[derive(Coding, Debug, PartialEq)] #[derive(Clone, Debug, Coding, PartialEq)]
pub struct TestReq(u8); pub struct TestReq(u8);
impl Request for TestReq { impl Request for TestReq {
type Response = u8; type Response = u8;
} }
pub struct TestChan;
impl Channel for TestChan {
type Notif = u8;
type Req = TestReq;
}
pub struct TestMsgSet; pub struct TestMsgSet;
impl MsgSet for TestMsgSet { impl MsgSet for TestMsgSet {
type InNot = u8; type In = TestChan;
type InReq = TestReq; type Out = TestChan;
type OutNot = u8;
type OutReq = TestReq;
} }
#[test] #[test]

View File

@@ -6,6 +6,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
ahash = "0.8.11"
hashbrown = "0.14.5"
itertools = "0.12.1"
orchid-api = { version = "0.1.0", path = "../orchid-api" } orchid-api = { version = "0.1.0", path = "../orchid-api" }
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" } orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
orchid-base = { version = "0.1.0", path = "../orchid-base" } orchid-base = { version = "0.1.0", path = "../orchid-base" }
ordered-float = "4.2.0"

View File

@@ -0,0 +1,137 @@
use std::any::TypeId;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use itertools::Itertools;
use orchid_api::expr::Expr;
use orchid_api::proto::ExtMsgSet;
use orchid_api::system::{NewSystem, SysId, SystemDecl};
use orchid_api_traits::Coding;
use orchid_base::reqnot::ReqNot;
use ordered_float::NotNan;
pub struct SystemHandle<T: SystemDepCard> {
_t: PhantomData<T>,
id: SysId,
reqnot: ReqNot<ExtMsgSet>,
}
impl<T: SystemDepCard> SystemHandle<T> {
fn new(id: SysId, reqnot: ReqNot<ExtMsgSet>) -> Self { Self { _t: PhantomData, id, reqnot } }
}
pub trait System: Send {
fn consts(&self) -> Expr;
}
pub struct SystemParams<Ctor: SystemCtor + ?Sized> {
pub deps: <Ctor::Deps as DepSet>::Sat,
pub id: SysId,
pub reqnot: ReqNot<ExtMsgSet>,
}
pub trait SystemDepCard {
type IngressReq: Coding;
type IngressNotif: Coding;
const NAME: &'static str;
}
pub trait DepSet {
type Sat;
fn report(names: &mut impl FnMut(&'static str));
fn create(take: &mut impl FnMut() -> SysId, reqnot: ReqNot<ExtMsgSet>) -> Self::Sat;
}
impl<T: SystemDepCard> DepSet for T {
type Sat = SystemHandle<Self>;
fn report(names: &mut impl FnMut(&'static str)) { names(T::NAME) }
fn create(take: &mut impl FnMut() -> SysId, reqnot: ReqNot<ExtMsgSet>) -> Self::Sat {
SystemHandle::new(take(), reqnot)
}
}
pub trait SystemCtor: Send {
type Deps: DepSet;
const NAME: &'static str;
const VERSION: f64;
#[allow(clippy::new_ret_no_self)]
fn new(params: SystemParams<Self>) -> Box<dyn System>;
}
pub trait DynSystemCtor: Send {
fn decl(&self) -> SystemDecl;
fn new_system(&self, new: &NewSystem, reqnot: ReqNot<ExtMsgSet>) -> Box<dyn System>;
}
impl<T: SystemCtor + 'static> DynSystemCtor for T {
fn decl(&self) -> SystemDecl {
// Version is equivalent to priority for all practical purposes
let priority = NotNan::new(T::VERSION).unwrap();
// aggregate depends names
let mut depends = Vec::new();
T::Deps::report(&mut |n| depends.push(n.to_string()));
// generate definitely unique id
let mut ahash = ahash::AHasher::default();
TypeId::of::<T>().hash(&mut ahash);
let id = (ahash.finish().to_be_bytes().into_iter().tuples())
.map(|(l, b)| u16::from_be_bytes([l, b]))
.fold(0, |a, b| a ^ b);
SystemDecl { name: T::NAME.to_string(), depends, id, priority }
}
fn new_system(&self, new: &NewSystem, reqnot: ReqNot<ExtMsgSet>) -> Box<dyn System> {
let mut ids = new.depends.iter().copied();
T::new(SystemParams {
deps: T::Deps::create(&mut || ids.next().unwrap(), reqnot.clone()),
id: new.id,
reqnot,
})
}
}
pub struct ExtensionData {
pub systems: Vec<Box<dyn DynSystemCtor>>,
}
mod dep_set_tuple_impls {
use orchid_api::proto::ExtMsgSet;
use orchid_api::system::SysId;
use orchid_base::reqnot::ReqNot;
use super::DepSet;
macro_rules! dep_set_tuple_impl {
($($name:ident),*) => {
impl<$( $name :DepSet ),*> DepSet for ( $( $name , )* ) {
type Sat = ( $( $name ::Sat , )* );
fn report(names: &mut impl FnMut(&'static str)) {
$(
$name ::report(names);
)*
}
fn create(take: &mut impl FnMut() -> SysId, reqnot: ReqNot<ExtMsgSet>) -> Self::Sat {
(
$(
$name ::create(take, reqnot.clone()),
)*
)
}
}
};
}
dep_set_tuple_impl!(A);
dep_set_tuple_impl!(A, B); // 2
dep_set_tuple_impl!(A, B, C);
dep_set_tuple_impl!(A, B, C, D); // 4
dep_set_tuple_impl!(A, B, C, D, E);
dep_set_tuple_impl!(A, B, C, D, E, F);
dep_set_tuple_impl!(A, B, C, D, E, F, G);
dep_set_tuple_impl!(A, B, C, D, E, F, G, H); // 8
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I);
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I, J);
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I, J, K);
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I, J, K, L); // 12
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I, J, K, L, M);
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
dep_set_tuple_impl!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); // 16
}

View File

@@ -1,19 +1,23 @@
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::{Arc, Mutex};
use hashbrown::HashMap;
use itertools::Itertools;
use orchid_api::proto::{ExtMsgSet, ExtensionHeader, HostExtNotif, HostExtReq, HostHeader}; use orchid_api::proto::{ExtMsgSet, ExtensionHeader, HostExtNotif, HostExtReq, HostHeader};
use orchid_api_traits::{Decode, Encode}; use orchid_api_traits::{Decode, Encode};
use orchid_base::child::{recv_parent_msg, send_parent_msg};
use orchid_base::clone; use orchid_base::clone;
use orchid_base::intern::{init_replica, sweep_replica}; use orchid_base::intern::{init_replica, sweep_replica};
use orchid_base::reqnot::{ReqNot, Requester}; use orchid_base::reqnot::{ReqNot, Requester};
pub struct ExtensionData {} use crate::data::ExtensionData;
use crate::msg::{recv_parent_msg, send_parent_msg};
pub fn main(data: &mut ExtensionData) { pub fn main(data: ExtensionData) {
HostHeader::decode(&mut &recv_parent_msg().unwrap()[..]); HostHeader::decode(&mut &recv_parent_msg().unwrap()[..]);
let mut buf = Vec::new(); let mut buf = Vec::new();
ExtensionHeader { systems: vec![] }.encode(&mut buf); let decls = data.systems.iter().map(|sys| sys.decl()).collect_vec();
let systems = Arc::new(Mutex::new(HashMap::new()));
ExtensionHeader { systems: decls.clone() }.encode(&mut buf);
send_parent_msg(&buf).unwrap(); send_parent_msg(&buf).unwrap();
let exiting = Arc::new(AtomicBool::new(false)); let exiting = Arc::new(AtomicBool::new(false));
let rn = ReqNot::<ExtMsgSet>::new( let rn = ReqNot::<ExtMsgSet>::new(
@@ -22,11 +26,17 @@ pub fn main(data: &mut ExtensionData) {
HostExtNotif::Exit => exiting.store(true, Ordering::Relaxed), HostExtNotif::Exit => exiting.store(true, Ordering::Relaxed),
_ => todo!(), _ => todo!(),
}), }),
|req| match req.req() { clone!(systems; move |req| match req.req() {
HostExtReq::Ping(ping) => req.handle(ping, &()), HostExtReq::Ping(ping) => req.handle(ping, &()),
HostExtReq::Sweep(sweep) => req.handle(sweep, &sweep_replica()), HostExtReq::Sweep(sweep) => req.handle(sweep, &sweep_replica()),
_ => todo!(), HostExtReq::NewSystem(new_sys) => {
let i = decls.iter().enumerate().find(|(_, s)| s.id == new_sys.system).unwrap().0;
let system = data.systems[i].new_system(new_sys, req.reqnot());
systems.lock().unwrap().insert(new_sys.id, system);
req.handle(new_sys, &())
}, },
_ => todo!(),
}),
); );
init_replica(rn.clone().map()); init_replica(rn.clone().map());
while !exiting.load(Ordering::Relaxed) { while !exiting.load(Ordering::Relaxed) {

View File

@@ -1,30 +0,0 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use orchid_api::proto::{ExtMsgSet, ExtensionHeader, HostExtNotif, HostExtReq, HostHeader};
use orchid_api_traits::{Decode, Encode};
use ordered_float::NotNan;
use crate::child::{recv_parent_msg, send_parent_msg};
use crate::clone;
use crate::intern::{init_replica, sweep_replica};
use crate::reqnot::{ReqNot, Requester as _};
pub struct SystemParams {
deps: Vec<SystemHandle>,
}
pub struct SystemCtor {
deps: Vec<String>,
make: Box<dyn FnMut(SystemParams) -> System>,
name: String,
prio: NotNan<f64>,
dependencies: Vec<String>,
}
pub struct ExtensionData {
systems: Vec<SystemCtor>
}

View File

@@ -1 +1,3 @@
pub mod entrypoint; pub mod entrypoint;
pub mod data;
pub mod msg;

View File

@@ -0,0 +1,6 @@
use std::io;
use orchid_base::msg::{recv_msg, send_msg};
pub fn send_parent_msg(msg: &[u8]) -> io::Result<()> { send_msg(&mut io::stdout().lock(), msg) }
pub fn recv_parent_msg() -> io::Result<Vec<u8>> { recv_msg(&mut io::stdin().lock()) }

View File

@@ -6,3 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
derive_destructure = "1.0.0"
hashbrown = "0.14.5"
itertools = "0.12.1"
lazy_static = "1.4.0"
orchid-api = { version = "0.1.0", path = "../orchid-api" }
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
orchid-base = { version = "0.1.0", path = "../orchid-base" }
ordered-float = "4.2.0"

View File

@@ -1,7 +1,9 @@
use std::io::{self, Read, Write}; use std::io;
use std::sync::Mutex; use std::sync::Mutex;
use std::{mem, process}; use std::{mem, process};
use orchid_base::msg::{recv_msg, send_msg};
pub struct SharedChild { pub struct SharedChild {
child: process::Child, child: process::Child,
stdin: Mutex<process::ChildStdin>, stdin: Mutex<process::ChildStdin>,
@@ -24,21 +26,3 @@ impl SharedChild {
impl Drop for SharedChild { impl Drop for SharedChild {
fn drop(&mut self) { mem::drop(self.child.kill()) } fn drop(&mut self) { mem::drop(self.child.kill()) }
} }
pub fn send_msg(write: &mut impl Write, msg: &[u8]) -> io::Result<()> {
write.write_all(&(u32::try_from(msg.len()).unwrap()).to_be_bytes())?;
write.write_all(msg)?;
write.flush()
}
pub fn recv_msg(read: &mut impl Read) -> io::Result<Vec<u8>> {
let mut len = [0u8; 4];
read.read_exact(&mut len)?;
let len = u32::from_be_bytes(len);
let mut msg = vec![0u8; len as usize];
read.read_exact(&mut msg)?;
Ok(msg)
}
pub fn send_parent_msg(msg: &[u8]) -> io::Result<()> { send_msg(&mut io::stdout().lock(), msg) }
pub fn recv_parent_msg() -> io::Result<Vec<u8>> { recv_msg(&mut io::stdin().lock()) }

View File

@@ -2,10 +2,10 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use hashbrown::HashMap; use hashbrown::HashMap;
use lazy_static::lazy_static;
use orchid_api::expr::ExprTicket; use orchid_api::expr::ExprTicket;
use crate::host::AtomHand; use crate::extension::AtomHand;
use lazy_static::lazy_static;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RtExpr { pub struct RtExpr {
@@ -36,6 +36,6 @@ impl Drop for RtExpr {
} }
} }
lazy_static!{ lazy_static! {
static ref KNOWN_EXPRS: RwLock<HashMap<ExprTicket, RtExpr>> = RwLock::default(); static ref KNOWN_EXPRS: RwLock<HashMap<ExprTicket, RtExpr>> = RwLock::default();
} }

View File

@@ -0,0 +1,234 @@
use std::io::Write as _;
use std::sync::atomic::{AtomicU16, AtomicU32, Ordering};
use std::sync::{Arc, Mutex, RwLock, Weak};
use std::{fmt, io, process, thread};
use derive_destructure::destructure;
use hashbrown::HashMap;
use itertools::Itertools;
use lazy_static::lazy_static;
use orchid_api::atom::{Atom, AtomDrop, AtomSame, CallRef, FinalCall, Fwd, Fwded};
use orchid_api::expr::{Acquire, Expr, ExprNotif, ExprTicket, Release, Relocate};
use orchid_api::intern::IntReq;
use orchid_api::proto::{
ExtHostNotif, ExtHostReq, ExtensionHeader, HostExtNotif, HostHeader, HostMsgSet,
};
use orchid_api::system::{NewSystem, SysDeclId, SysId, SystemDecl, SystemDrop};
use orchid_api::tree::{GetConstTree, TreeModule};
use orchid_api_traits::{Decode, Encode};
use orchid_base::clone;
use orchid_base::intern::{deintern, intern};
use orchid_base::reqnot::{ReqNot, Requester as _};
use ordered_float::NotNan;
use crate::expr::RtExpr;
#[derive(Debug, destructure)]
pub struct AtomData {
owner: System,
drop: bool,
data: Vec<u8>,
}
impl AtomData {
fn api(self) -> Atom {
let (owner, drop, data) = self.destructure();
Atom { data, drop, owner: owner.0.id }
}
fn api_ref(&self) -> Atom {
Atom { data: self.data.clone(), drop: self.drop, owner: self.owner.0.id }
}
}
impl Drop for AtomData {
fn drop(&mut self) {
self.owner.0.ext.0.reqnot.notify(AtomDrop(Atom {
owner: self.owner.0.id,
data: self.data.clone(),
drop: true,
}))
}
}
#[derive(Clone, Debug)]
pub struct AtomHand(Arc<AtomData>);
impl AtomHand {
pub fn from_api(Atom { data, drop, owner }: Atom) -> Self {
let owner = System::resolve(owner).expect("Atom owned by non-existing system");
Self(Arc::new(AtomData { data, drop, owner }))
}
pub fn call(self, arg: RtExpr) -> Expr {
let owner_sys = self.0.owner.clone();
let ext = &owner_sys.0.ext;
let ticket = owner_sys.give_expr(arg.canonicalize(), || arg);
match Arc::try_unwrap(self.0) {
Ok(data) => ext.0.reqnot.request(FinalCall(data.api(), ticket)),
Err(hand) => ext.0.reqnot.request(CallRef(hand.api_ref(), ticket)),
}
}
pub fn same(&self, other: &AtomHand) -> bool {
let owner = self.0.owner.0.id;
if other.0.owner.0.id != owner {
return false;
}
self.0.owner.0.ext.0.reqnot.request(AtomSame(self.0.api_ref(), other.0.api_ref()))
}
pub fn req(&self, req: Vec<u8>) -> Vec<u8> {
self.0.owner.0.ext.0.reqnot.request(Fwded(self.0.api_ref(), req))
}
pub fn api_ref(&self) -> Atom { self.0.api_ref() }
}
/// Data held about an Extension. This is refcounted within [Extension]. It's
/// important to only ever access parts of this struct through the [Arc] because
/// the components reference each other through [Weak]s of it, and will panic if
/// upgrading fails.
#[derive(destructure)]
pub struct ExtensionData {
child: Mutex<process::Child>,
reqnot: ReqNot<HostMsgSet>,
systems: Vec<SystemCtor>,
}
impl Drop for ExtensionData {
fn drop(&mut self) { self.reqnot.notify(HostExtNotif::Exit) }
}
fn acq_expr(sys: SysId, extk: ExprTicket) {
(System::resolve(sys).expect("Expr acq'd by invalid system"))
.give_expr(extk, || RtExpr::resolve(extk).expect("Invalid expr acq'd"));
}
fn rel_expr(sys: SysId, extk: ExprTicket) {
let sys = System::resolve(sys).unwrap();
let mut exprs = sys.0.exprs.write().unwrap();
exprs.entry(extk).and_replace_entry_with(|_, (rc, rt)| {
(0 < rc.fetch_sub(1, Ordering::Relaxed)).then_some((rc, rt))
});
}
#[derive(Clone)]
pub struct Extension(Arc<ExtensionData>);
impl Extension {
pub fn new(mut cmd: process::Command) -> io::Result<Self> {
let mut child = cmd.stdin(process::Stdio::piped()).stdout(process::Stdio::piped()).spawn()?;
HostHeader.encode(child.stdin.as_mut().unwrap());
let eh = ExtensionHeader::decode(child.stdout.as_mut().unwrap());
Ok(Self(Arc::new_cyclic(|weak| ExtensionData {
child: Mutex::new(child),
reqnot: ReqNot::new(
clone!(weak; move |sfn, _| {
let arc: Arc<ExtensionData> = weak.upgrade().unwrap();
let mut g = arc.child.lock().unwrap();
g.stdin.as_mut().unwrap().write_all(sfn).unwrap();
}),
|notif, _| match notif {
ExtHostNotif::Expr(expr) => match expr {
ExprNotif::Acquire(Acquire(sys, extk)) => acq_expr(sys, extk),
ExprNotif::Release(Release(sys, extk)) => rel_expr(sys, extk),
ExprNotif::Relocate(Relocate { inc, dec, expr }) => {
acq_expr(inc, expr);
rel_expr(dec, expr);
},
},
},
|req| match req.req() {
ExtHostReq::Ping(ping) => req.handle(ping, &()),
ExtHostReq::IntReq(intreq) => match intreq {
IntReq::InternStr(s) => req.handle(s, &intern(&**s.0).marker()),
IntReq::InternStrv(v) => req.handle(v, &intern(&*v.0).marker()),
IntReq::ExternStr(si) => req.handle(si, &deintern(si.0).arc()),
IntReq::ExternStrv(vi) =>
req.handle(vi, &Arc::new(deintern(vi.0).iter().map(|t| t.marker()).collect_vec())),
},
ExtHostReq::Fwd(fw @ Fwd(atom, _body)) => {
let sys = System::resolve(atom.owner).unwrap();
thread::spawn(clone!(fw; move || {
req.handle(&fw, &sys.0.ext.0.reqnot.request(Fwded(fw.0.clone(), fw.1.clone())))
}));
},
_ => todo!(),
},
),
systems: eh.systems.into_iter().map(|decl| SystemCtor { decl, ext: weak.clone() }).collect(),
})))
}
pub fn systems(&self) -> impl Iterator<Item = &SystemCtor> { self.0.systems.iter() }
}
pub struct SystemCtor {
decl: SystemDecl,
ext: Weak<ExtensionData>,
}
impl SystemCtor {
pub fn name(&self) -> &str { &self.decl.name }
pub fn priority(&self) -> NotNan<f64> { self.decl.priority }
pub fn depends(&self) -> impl ExactSizeIterator<Item = &str> {
self.decl.depends.iter().map(|s| &**s)
}
pub fn run<'a>(&self, depends: impl IntoIterator<Item = &'a System>) -> System {
let mut inst_g = SYSTEM_INSTS.write().unwrap();
let depends = depends.into_iter().map(|si| si.0.id).collect_vec();
debug_assert_eq!(depends.len(), self.decl.depends.len(), "Wrong number of deps provided");
let ext = self.ext.upgrade().expect("SystemCtor should be freed before Extension");
static NEXT_ID: AtomicU16 = AtomicU16::new(0);
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
let () = ext.reqnot.request(NewSystem { depends, id, system: self.decl.id });
let data = System(Arc::new(SystemInstData {
decl_id: self.decl.id,
ext: Extension(ext),
exprs: RwLock::default(),
id,
}));
inst_g.insert(id, data.clone());
data
}
}
lazy_static! {
static ref SYSTEM_INSTS: RwLock<HashMap<u16, System>> = RwLock::default();
}
#[derive(destructure)]
pub struct SystemInstData {
exprs: RwLock<HashMap<ExprTicket, (AtomicU32, RtExpr)>>,
ext: Extension,
decl_id: SysDeclId,
id: u16,
}
impl Drop for SystemInstData {
fn drop(&mut self) {
self.ext.0.reqnot.notify(SystemDrop(self.id));
if let Ok(mut g) = SYSTEM_INSTS.write() {
g.remove(&self.id);
}
}
}
#[derive(Clone)]
pub struct System(Arc<SystemInstData>);
impl System {
fn resolve(id: u16) -> Option<System> { SYSTEM_INSTS.read().unwrap().get(&id).cloned() }
fn give_expr(&self, ticket: ExprTicket, get_expr: impl FnOnce() -> RtExpr) -> ExprTicket {
let mut exprs = self.0.exprs.write().unwrap();
exprs
.entry(ticket)
.and_modify(|(c, _)| {
c.fetch_add(1, Ordering::Relaxed);
})
.or_insert((AtomicU32::new(1), get_expr()));
ticket
}
pub fn const_tree(&self) -> TreeModule { self.0.ext.0.reqnot.request(GetConstTree(self.0.id)) }
}
impl fmt::Debug for System {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ctor = (self.0.ext.0.systems.iter().find(|c| c.decl.id == self.0.decl_id))
.expect("System instance with no associated constructor");
write!(f, "System({} @ {} #{}, ", ctor.decl.name, ctor.decl.priority, self.0.id)?;
match self.0.exprs.read() {
Err(_) => write!(f, "expressions unavailable"),
Ok(r) => {
let rc: u32 = r.values().map(|v| v.0.load(Ordering::Relaxed)).sum();
write!(f, "{rc} refs to {} exprs", r.len())
},
}
}
}

View File

@@ -1,234 +1,3 @@
use std::io::Write as _; pub mod child;
use std::sync::atomic::{AtomicU16, AtomicU32, Ordering}; pub mod expr;
use std::sync::{Arc, Mutex, RwLock, Weak}; pub mod extension;
use std::{fmt, io, process, thread};
use derive_destructure::destructure;
use hashbrown::HashMap;
use itertools::Itertools;
use lazy_static::lazy_static;
use orchid_api::atom::{Atom, AtomDrop, AtomSame, CallRef, FinalCall, Fwd, Fwded};
use orchid_api::expr::{Acquire, Expr, ExprNotif, ExprTicket, Release, Relocate};
use orchid_api::intern::IntReq;
use orchid_api::proto::{
ExtHostNotif, ExtHostReq, ExtensionHeader, HostExtNotif, HostHeader, HostMsgSet,
};
use orchid_api::system::{NewSystem, SysDeclId, SysId, SystemDecl, SystemDrop};
use orchid_api::tree::{GetConstTree, TreeModule};
use orchid_api_traits::{Decode, Encode};
use ordered_float::NotNan;
use crate::clone;
use crate::expr::RtExpr;
use crate::intern::{deintern, intern};
use crate::reqnot::{ReqNot, Requester as _};
#[derive(Debug, destructure)]
pub struct AtomData {
owner: System,
drop: bool,
data: Vec<u8>,
}
impl AtomData {
fn api(self) -> Atom {
let (owner, drop, data) = self.destructure();
Atom { data, drop, owner: owner.0.id }
}
fn api_ref(&self) -> Atom {
Atom { data: self.data.clone(), drop: self.drop, owner: self.owner.0.id }
}
}
impl Drop for AtomData {
fn drop(&mut self) {
self.owner.0.ext.0.reqnot.notify(AtomDrop(Atom {
owner: self.owner.0.id,
data: self.data.clone(),
drop: true,
}))
}
}
#[derive(Clone, Debug)]
pub struct AtomHand(Arc<AtomData>);
impl AtomHand {
pub fn from_api(Atom { data, drop, owner }: Atom) -> Self {
let owner = System::resolve(owner).expect("Atom owned by non-existing system");
Self(Arc::new(AtomData { data, drop, owner }))
}
pub fn call(self, arg: RtExpr) -> Expr {
let owner_sys = self.0.owner.clone();
let ext = &owner_sys.0.ext;
let ticket = owner_sys.give_expr(arg.canonicalize(), || arg);
match Arc::try_unwrap(self.0) {
Ok(data) => ext.0.reqnot.request(FinalCall(data.api(), ticket)),
Err(hand) => ext.0.reqnot.request(CallRef(hand.api_ref(), ticket)),
}
}
pub fn same(&self, other: &AtomHand) -> bool {
let owner = self.0.owner.0.id;
if other.0.owner.0.id != owner {
return false;
}
self.0.owner.0.ext.0.reqnot.request(AtomSame(self.0.api_ref(), other.0.api_ref()))
}
pub fn req(&self, req: Vec<u8>) -> Vec<u8> {
self.0.owner.0.ext.0.reqnot.request(Fwded(self.0.api_ref(), req))
}
pub fn api_ref(&self) -> Atom { self.0.api_ref() }
}
/// Data held about an Extension. This is refcounted within [Extension]. It's
/// important to only ever access parts of this struct through the [Arc] because
/// the components reference each other through [Weak]s of it, and will panic if
/// upgrading fails.
#[derive(destructure)]
pub struct ExtensionData {
child: Mutex<process::Child>,
reqnot: ReqNot<HostMsgSet>,
systems: Vec<SystemCtor>,
}
impl Drop for ExtensionData {
fn drop(&mut self) { self.reqnot.notify(HostExtNotif::Exit) }
}
fn acq_expr(sys: SysId, extk: ExprTicket) {
(System::resolve(sys).expect("Expr acq'd by invalid system"))
.give_expr(extk, || RtExpr::resolve(extk).expect("Invalid expr acq'd"));
}
fn rel_expr(sys: SysId, extk: ExprTicket) {
let sys = System::resolve(sys).unwrap();
let mut exprs = sys.0.exprs.write().unwrap();
exprs.entry(extk).and_replace_entry_with(|_, (rc, rt)| {
(0 < rc.fetch_sub(1, Ordering::Relaxed)).then_some((rc, rt))
});
}
#[derive(Clone)]
pub struct Extension(Arc<ExtensionData>);
impl Extension {
pub fn new(mut cmd: process::Command) -> io::Result<Self> {
let mut child = cmd.stdin(process::Stdio::piped()).stdout(process::Stdio::piped()).spawn()?;
HostHeader.encode(child.stdin.as_mut().unwrap());
let eh = ExtensionHeader::decode(child.stdout.as_mut().unwrap());
Ok(Self(Arc::new_cyclic(|weak| ExtensionData {
child: Mutex::new(child),
reqnot: ReqNot::new(
clone!(weak; move |sfn, _| {
let arc: Arc<ExtensionData> = weak.upgrade().unwrap();
let mut g = arc.child.lock().unwrap();
g.stdin.as_mut().unwrap().write_all(sfn).unwrap();
}),
|notif, _| match notif {
ExtHostNotif::Expr(expr) => match expr {
ExprNotif::Acquire(Acquire(sys, extk)) => acq_expr(sys, extk),
ExprNotif::Release(Release(sys, extk)) => rel_expr(sys, extk),
ExprNotif::Relocate(Relocate { inc, dec, expr }) => {
acq_expr(inc, expr);
rel_expr(dec, expr);
},
},
},
|req| match req.req() {
ExtHostReq::Ping(ping) => req.handle(ping, &()),
ExtHostReq::IntReq(intreq) => match intreq {
IntReq::InternStr(s) => req.handle(s, &intern(&**s.0).marker()),
IntReq::InternStrv(v) => req.handle(v, &intern(&*v.0).marker()),
IntReq::ExternStr(si) => req.handle(si, &deintern(si.0).arc()),
IntReq::ExternStrv(vi) =>
req.handle(vi, &Arc::new(deintern(vi.0).iter().map(|t| t.marker()).collect_vec())),
},
ExtHostReq::Fwd(fw @ Fwd(atom, _body)) => {
let sys = System::resolve(atom.owner).unwrap();
thread::spawn(clone!(fw; move || {
req.handle(&fw, &sys.0.ext.0.reqnot.request(Fwded(fw.0.clone(), fw.1.clone())))
}));
},
_ => todo!(),
},
),
systems: eh.systems.into_iter().map(|decl| SystemCtor { decl, ext: weak.clone() }).collect(),
})))
}
pub fn systems(&self) -> impl Iterator<Item = &SystemCtor> { self.0.systems.iter() }
}
pub struct SystemCtor {
decl: SystemDecl,
ext: Weak<ExtensionData>,
}
impl SystemCtor {
pub fn name(&self) -> &str { &self.decl.name }
pub fn priority(&self) -> NotNan<f64> { self.decl.priority }
pub fn depends(&self) -> impl ExactSizeIterator<Item = &str> {
self.decl.depends.iter().map(|s| &**s)
}
pub fn run<'a>(&self, depends: impl IntoIterator<Item = &'a System>) -> System {
let mut inst_g = SYSTEM_INSTS.write().unwrap();
let depends = depends.into_iter().map(|si| si.0.id).collect_vec();
debug_assert_eq!(depends.len(), self.decl.depends.len(), "Wrong number of deps provided");
let ext = self.ext.upgrade().expect("SystemCtor should be freed before Extension");
static NEXT_ID: AtomicU16 = AtomicU16::new(0);
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
let () = ext.reqnot.request(NewSystem { depends, id, system: self.decl.id });
let data = System(Arc::new(SystemInstData {
decl_id: self.decl.id,
ext: Extension(ext),
exprs: RwLock::default(),
id,
}));
inst_g.insert(id, data.clone());
data
}
}
lazy_static! {
static ref SYSTEM_INSTS: RwLock<HashMap<u16, System>> = RwLock::default();
}
#[derive(destructure)]
pub struct SystemInstData {
exprs: RwLock<HashMap<ExprTicket, (AtomicU32, RtExpr)>>,
ext: Extension,
decl_id: SysDeclId,
id: u16,
}
impl Drop for SystemInstData {
fn drop(&mut self) {
self.ext.0.reqnot.notify(SystemDrop(self.id));
if let Ok(mut g) = SYSTEM_INSTS.write() {
g.remove(&self.id);
}
}
}
#[derive(Clone)]
pub struct System(Arc<SystemInstData>);
impl System {
fn resolve(id: u16) -> Option<System> { SYSTEM_INSTS.read().unwrap().get(&id).cloned() }
fn give_expr(&self, ticket: ExprTicket, get_expr: impl FnOnce() -> RtExpr) -> ExprTicket {
let mut exprs = self.0.exprs.write().unwrap();
exprs
.entry(ticket)
.and_modify(|(c, _)| {
c.fetch_add(1, Ordering::Relaxed);
})
.or_insert((AtomicU32::new(1), get_expr()));
ticket
}
pub fn const_tree(&self) -> TreeModule { self.0.ext.0.reqnot.request(GetConstTree(self.0.id)) }
}
impl fmt::Debug for System {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ctor = (self.0.ext.0.systems.iter().find(|c| c.decl.id == self.0.decl_id))
.expect("System instance with no associated constructor");
write!(f, "System({} @ {} #{}, ", ctor.decl.name, ctor.decl.priority, self.0.id)?;
match self.0.exprs.read() {
Err(_) => write!(f, "expressions unavailable"),
Ok(r) => {
let rc: u32 = r.values().map(|v| v.0.load(Ordering::Relaxed)).sum();
write!(f, "{rc} refs to {} exprs", r.len())
},
}
}
}