graphix_stdlib/
lib.rs

1use anyhow::Result;
2use arcstr::ArcStr;
3use enumflags2::{bitflags, BitFlags};
4use fxhash::FxHashMap;
5use graphix_compiler::{
6    expr::ModuleResolver, typ::FnType, Apply, BuiltIn, BuiltInInitFn, Event, ExecCtx,
7    Node, Rt, UserEvent,
8};
9use netidx::{path::Path, subscriber::Value};
10use netidx_core::utils::Either;
11use std::{
12    fmt::Debug,
13    iter,
14    sync::{Arc, LazyLock},
15};
16
17mod array;
18mod core;
19mod net;
20mod rand;
21mod re;
22mod str;
23#[cfg(test)]
24mod test;
25mod time;
26
27#[macro_export]
28macro_rules! deftype {
29    ($scope:literal, $s:literal) => {
30        const TYP: ::std::sync::LazyLock<graphix_compiler::typ::FnType> =
31            ::std::sync::LazyLock::new(|| {
32                let scope =
33                    graphix_compiler::expr::ModPath(::netidx::path::Path::from($scope));
34                graphix_compiler::expr::parser::parse_fn_type($s)
35                    .expect("failed to parse fn type {s}")
36                    .scope_refs(&scope)
37            });
38    };
39}
40
41#[macro_export]
42macro_rules! arity1 {
43    ($from:expr, $updates:expr) => {
44        match (&*$from, &*$updates) {
45            ([arg], [arg_up]) => (arg, arg_up),
46            (_, _) => unreachable!(),
47        }
48    };
49}
50
51#[macro_export]
52macro_rules! arity2 {
53    ($from:expr, $updates:expr) => {
54        match (&*$from, &*$updates) {
55            ([arg0, arg1], [arg0_up, arg1_up]) => ((arg0, arg1), (arg0_up, arg1_up)),
56            (_, _) => unreachable!(),
57        }
58    };
59}
60
61#[derive(Debug)]
62pub struct CachedVals(pub Box<[Option<Value>]>);
63
64impl CachedVals {
65    pub fn new<R: Rt, E: UserEvent>(from: &[Node<R, E>]) -> CachedVals {
66        CachedVals(from.into_iter().map(|_| None).collect())
67    }
68
69    pub fn clear(&mut self) {
70        for v in &mut self.0 {
71            *v = None
72        }
73    }
74
75    pub fn update<R: Rt, E: UserEvent>(
76        &mut self,
77        ctx: &mut ExecCtx<R, E>,
78        from: &mut [Node<R, E>],
79        event: &mut Event<E>,
80    ) -> bool {
81        from.into_iter().enumerate().fold(false, |res, (i, src)| {
82            match src.update(ctx, event) {
83                None => res,
84                v @ Some(_) => {
85                    self.0[i] = v;
86                    true
87                }
88            }
89        })
90    }
91
92    /// Like update, but return the indexes of the nodes that updated
93    /// instead of a consolidated bool
94    pub fn update_diff<R: Rt, E: UserEvent>(
95        &mut self,
96        up: &mut [bool],
97        ctx: &mut ExecCtx<R, E>,
98        from: &mut [Node<R, E>],
99        event: &mut Event<E>,
100    ) {
101        for (i, n) in from.iter_mut().enumerate() {
102            match n.update(ctx, event) {
103                None => (),
104                v => {
105                    self.0[i] = v;
106                    up[i] = true
107                }
108            }
109        }
110    }
111
112    pub fn flat_iter<'a>(&'a self) -> impl Iterator<Item = Option<Value>> + 'a {
113        self.0.iter().flat_map(|v| match v {
114            None => Either::Left(iter::once(None)),
115            Some(v) => Either::Right(v.clone().flatten().map(Some)),
116        })
117    }
118}
119
120pub trait EvalCached: Debug + Default + Send + Sync + 'static {
121    const NAME: &str;
122    const TYP: LazyLock<FnType>;
123
124    fn eval(&mut self, from: &CachedVals) -> Option<Value>;
125}
126
127#[derive(Debug)]
128pub struct CachedArgs<T: EvalCached> {
129    cached: CachedVals,
130    t: T,
131}
132
133impl<R: Rt, E: UserEvent, T: EvalCached> BuiltIn<R, E> for CachedArgs<T> {
134    const NAME: &str = T::NAME;
135    const TYP: LazyLock<FnType> = T::TYP;
136
137    fn init(_: &mut ExecCtx<R, E>) -> BuiltInInitFn<R, E> {
138        Arc::new(|_, _, _, from, _| {
139            let t = CachedArgs::<T> { cached: CachedVals::new(from), t: T::default() };
140            Ok(Box::new(t))
141        })
142    }
143}
144
145impl<R: Rt, E: UserEvent, T: EvalCached> Apply<R, E> for CachedArgs<T> {
146    fn update(
147        &mut self,
148        ctx: &mut ExecCtx<R, E>,
149        from: &mut [Node<R, E>],
150        event: &mut Event<E>,
151    ) -> Option<Value> {
152        if self.cached.update(ctx, from, event) {
153            self.t.eval(&self.cached)
154        } else {
155            None
156        }
157    }
158
159    fn sleep(&mut self, _ctx: &mut ExecCtx<R, E>) {
160        self.cached.clear()
161    }
162}
163
164#[bitflags]
165#[derive(Clone, Copy)]
166#[repr(u64)]
167pub enum Module {
168    Array,
169    NetAndTime,
170    Rand,
171    Re,
172    Str,
173}
174
175/// Register selected modules of the standard graphix library
176///
177/// and return a root module that will load them along with a module resolver
178/// that contains the necessary code. You need both of these for the `rt`
179/// module.
180///
181/// Note, core is always included and registered, all the other
182/// modules are optional
183///
184/// # Example
185///
186/// ```no_run
187/// use netidx::{publisher::Publisher, subscriber::Subscriber};
188/// use anyhow::Result;
189/// use netidx_core::pool::Pooled;
190/// use graphix_compiler::ExecCtx;
191/// use graphix_rt::{GXRt, GXConfigBuilder, GXHandle, GXEvent, NoExt};
192/// use tokio::sync::mpsc;
193/// use enumflags2::BitFlags;
194///
195/// async fn start_runtime(
196///     publisher: Publisher,
197///     subscriber: Subscriber,
198///     sub: mpsc::Sender<Pooled<Vec<GXEvent<NoExt>>>>
199/// ) -> Result<GXHandle<NoExt>> {
200///     let mut ctx = ExecCtx::new(GXRt::<NoExt>::new(publisher, subscriber));
201///     let (root, mods) = graphix_stdlib::register(&mut ctx, BitFlags::all())?;
202///     GXConfigBuilder::default()
203///        .ctx(ctx)
204///        .root(root)
205///        .resolvers(vec![mods])
206///        .sub(sub)
207///        .build()?
208///        .start()
209///        .await
210/// }
211/// ```
212pub fn register<R: Rt, E: UserEvent>(
213    ctx: &mut ExecCtx<R, E>,
214    modules: BitFlags<Module>,
215) -> Result<(ArcStr, ModuleResolver)> {
216    let mut tbl = FxHashMap::default();
217    tbl.insert(Path::from("/core"), core::register(ctx)?);
218    let mut root = String::from("pub mod core;\nuse core;\n");
219    for module in modules {
220        match module {
221            Module::Array => {
222                root.push_str("pub mod array;\n");
223                tbl.insert(Path::from("/array"), array::register(ctx)?);
224            }
225            Module::NetAndTime => {
226                root.push_str("pub mod time;\n");
227                tbl.insert(Path::from("/time"), time::register(ctx)?);
228                root.push_str("pub mod net;\n");
229                tbl.insert(Path::from("/net"), net::register(ctx)?);
230            }
231            Module::Rand => {
232                root.push_str("pub mod rand;\n");
233                tbl.insert(Path::from("/rand"), rand::register(ctx)?);
234            }
235            Module::Re => {
236                root.push_str("pub mod re;\n");
237                tbl.insert(Path::from("/re"), re::register(ctx)?);
238            }
239            Module::Str => {
240                root.push_str("pub mod str;\n");
241                tbl.insert(Path::from("/str"), str::register(ctx)?);
242            }
243        }
244    }
245    root.pop();
246    root.pop();
247    Ok((ArcStr::from(root), ModuleResolver::VFS(tbl)))
248}