netidx_bscript/stdfn/
time.rs

1use crate::{
2    arity2, deftype, err, errf,
3    expr::{Expr, ExprId},
4    stdfn::CachedVals,
5    Apply, BindId, BuiltIn, BuiltInInitFn, Ctx, Event, ExecCtx, Node, UserEvent,
6};
7use anyhow::{bail, Result};
8use arcstr::{literal, ArcStr};
9use compact_str::format_compact;
10use netidx::{publisher::FromValue, subscriber::Value};
11use std::{ops::SubAssign, sync::Arc, time::Duration};
12
13#[derive(Debug)]
14struct AfterIdle {
15    args: CachedVals,
16    id: Option<BindId>,
17    eid: ExprId,
18}
19
20impl<C: Ctx, E: UserEvent> BuiltIn<C, E> for AfterIdle {
21    const NAME: &str = "after_idle";
22    deftype!("time", "fn([duration, Number], 'a) -> 'a");
23
24    fn init(_: &mut ExecCtx<C, E>) -> BuiltInInitFn<C, E> {
25        Arc::new(|_, _, _, from, eid| {
26            Ok(Box::new(AfterIdle { args: CachedVals::new(from), id: None, eid }))
27        })
28    }
29}
30
31impl<C: Ctx, E: UserEvent> Apply<C, E> for AfterIdle {
32    fn update(
33        &mut self,
34        ctx: &mut ExecCtx<C, E>,
35        from: &mut [Node<C, E>],
36        event: &mut Event<E>,
37    ) -> Option<Value> {
38        let mut up = [false; 2];
39        self.args.update_diff(&mut up, ctx, from, event);
40        let ((timeout, val), (timeout_up, val_up)) = arity2!(self.args.0, &up);
41        match ((timeout, val), (timeout_up, val_up)) {
42            ((Some(secs), _), (true, _)) | ((Some(secs), _), (_, true)) => match secs
43                .clone()
44                .cast_to::<Duration>()
45            {
46                Err(e) => {
47                    self.id = None;
48                    return errf!("after_idle(timeout, cur): expected duration {e:?}");
49                }
50                Ok(dur) => {
51                    let id = BindId::new();
52                    self.id = Some(id);
53                    ctx.user.ref_var(id, self.eid);
54                    ctx.user.set_timer(id, dur);
55                    return None;
56                }
57            },
58            ((None, _), (_, _))
59            | ((_, None), (_, _))
60            | ((Some(_), Some(_)), (false, _)) => (),
61        };
62        self.id.and_then(|id| {
63            if event.variables.contains_key(&id) {
64                self.id = None;
65                ctx.user.unref_var(id, self.eid);
66                self.args.0.get(1).and_then(|v| v.clone())
67            } else {
68                None
69            }
70        })
71    }
72
73    fn delete(&mut self, ctx: &mut ExecCtx<C, E>) {
74        if let Some(id) = self.id.take() {
75            ctx.user.unref_var(id, self.eid)
76        }
77    }
78}
79
80#[derive(Debug, Clone, Copy)]
81enum Repeat {
82    Yes,
83    No,
84    N(u64),
85}
86
87impl FromValue for Repeat {
88    fn from_value(v: Value) -> Result<Self> {
89        match v {
90            Value::Bool(true) => Ok(Repeat::Yes),
91            Value::Bool(false) => Ok(Repeat::No),
92            v => match v.cast_to::<u64>() {
93                Ok(n) => Ok(Repeat::N(n)),
94                Err(_) => bail!("could not cast to repeat"),
95            },
96        }
97    }
98}
99
100impl SubAssign<u64> for Repeat {
101    fn sub_assign(&mut self, rhs: u64) {
102        match self {
103            Repeat::Yes | Repeat::No => (),
104            Repeat::N(n) => *n -= rhs,
105        }
106    }
107}
108
109impl Repeat {
110    fn will_repeat(&self) -> bool {
111        match self {
112            Repeat::No => false,
113            Repeat::Yes => true,
114            Repeat::N(n) => *n > 0,
115        }
116    }
117}
118
119#[derive(Debug)]
120struct Timer {
121    args: CachedVals,
122    timeout: Option<Duration>,
123    repeat: Repeat,
124    id: Option<BindId>,
125    eid: ExprId,
126}
127
128impl<C: Ctx, E: UserEvent> BuiltIn<C, E> for Timer {
129    const NAME: &str = "timer";
130    deftype!("time", "fn([duration, Number], [bool, Number]) -> datetime");
131
132    fn init(_: &mut ExecCtx<C, E>) -> BuiltInInitFn<C, E> {
133        Arc::new(|_, _, _, from, eid| {
134            Ok(Box::new(Self {
135                args: CachedVals::new(from),
136                timeout: None,
137                repeat: Repeat::No,
138                id: None,
139                eid,
140            }))
141        })
142    }
143}
144
145impl<C: Ctx, E: UserEvent> Apply<C, E> for Timer {
146    fn update(
147        &mut self,
148        ctx: &mut ExecCtx<C, E>,
149        from: &mut [Node<C, E>],
150        event: &mut Event<E>,
151    ) -> Option<Value> {
152        macro_rules! error {
153            () => {{
154                self.id = None;
155                self.timeout = None;
156                self.repeat = Repeat::No;
157                return err!("timer(per, rep): expected duration, bool or number >= 0");
158            }};
159        }
160        macro_rules! schedule {
161            ($dur:expr) => {{
162                let id = BindId::new();
163                self.id = Some(id);
164                ctx.user.ref_var(id, self.eid);
165                ctx.user.set_timer(id, $dur);
166            }};
167        }
168        let mut up = [false; 2];
169        self.args.update_diff(&mut up, ctx, from, event);
170        let ((timeout, repeat), (timeout_up, repeat_up)) = arity2!(self.args.0, &up);
171        match ((timeout, repeat), (timeout_up, repeat_up)) {
172            ((None, Some(r)), (true, true)) | ((_, Some(r)), (false, true)) => {
173                match r.clone().cast_to::<Repeat>() {
174                    Err(_) => error!(),
175                    Ok(repeat) => {
176                        self.repeat = repeat;
177                        if let Some(dur) = self.timeout {
178                            if self.id.is_none() && repeat.will_repeat() {
179                                schedule!(dur)
180                            }
181                        }
182                    }
183                }
184            }
185            ((Some(s), None), (true, _)) => match s.clone().cast_to::<Duration>() {
186                Err(_) => error!(),
187                Ok(dur) => self.timeout = Some(dur),
188            },
189            ((Some(s), Some(r)), (true, _)) => {
190                match (s.clone().cast_to::<Duration>(), r.clone().cast_to::<Repeat>()) {
191                    (Err(_), _) | (_, Err(_)) => error!(),
192                    (Ok(dur), Ok(repeat)) => {
193                        self.timeout = Some(dur);
194                        self.repeat = repeat;
195                        schedule!(dur)
196                    }
197                }
198            }
199            ((_, _), (false, false))
200            | ((None, None), (_, _))
201            | ((None, _), (true, false))
202            | ((_, None), (false, true)) => (),
203        }
204        self.id.and_then(|id| event.variables.get(&id).map(|now| (id, now))).map(
205            |(id, now)| {
206                ctx.user.unref_var(id, self.eid);
207                self.id = None;
208                self.repeat -= 1;
209                if let Some(dur) = self.timeout {
210                    if self.repeat.will_repeat() {
211                        schedule!(dur)
212                    }
213                }
214                now.clone()
215            },
216        )
217    }
218}
219
220const MOD: &str = r#"
221pub mod time {
222    /// When v updates wait timeout and then return it. If v updates again
223    /// before timeout expires, reset the timeout and continue waiting.
224    pub let after_idle = |timeout, v| 'after_idle;
225
226    /// timer will wait timeout and then update with the current time.
227    /// If repeat is true, it will do this forever. If repeat is a number n,
228    /// it will do this n times and then stop. If repeat is false, it will do
229    /// this once.
230    pub let timer = |timeout, repeat| 'timer
231}
232"#;
233
234pub fn register<C: Ctx, E: UserEvent>(ctx: &mut ExecCtx<C, E>) -> Expr {
235    ctx.register_builtin::<AfterIdle>().unwrap();
236    ctx.register_builtin::<Timer>().unwrap();
237    MOD.parse().unwrap()
238}