1#![doc(
2 html_logo_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg",
3 html_favicon_url = "https://graphix-lang.github.io/graphix/graphix-icon.svg"
4)]
5use anyhow::{bail, Result};
6use arcstr::literal;
7use chrono::Utc;
8use graphix_compiler::{
9 err, expr::ExprId, Apply, BindId, BuiltIn, Event, ExecCtx, Node, Rt, Scope, UserEvent,
10};
11use graphix_package_core::{arity2, deftype, CachedVals};
12use netidx::{publisher::FromValue, subscriber::Value};
13use std::{ops::SubAssign, time::Duration};
14
15#[derive(Debug)]
16struct AfterIdle {
17 args: CachedVals,
18 id: Option<BindId>,
19 eid: ExprId,
20}
21
22impl<R: Rt, E: UserEvent> BuiltIn<R, E> for AfterIdle {
23 const NAME: &str = "time_after_idle";
24 deftype!("fn([duration, Number], 'a) -> 'a");
25
26 fn init<'a, 'b, 'c>(
27 _ctx: &'a mut ExecCtx<R, E>,
28 _typ: &'a graphix_compiler::typ::FnType,
29 _scope: &'b Scope,
30 from: &'c [Node<R, E>],
31 top_id: ExprId,
32 ) -> Result<Box<dyn Apply<R, E>>> {
33 Ok(Box::new(AfterIdle { args: CachedVals::new(from), id: None, eid: top_id }))
34 }
35}
36
37impl<R: Rt, E: UserEvent> Apply<R, E> for AfterIdle {
38 fn update(
39 &mut self,
40 ctx: &mut ExecCtx<R, E>,
41 from: &mut [Node<R, E>],
42 event: &mut Event<E>,
43 ) -> Option<Value> {
44 let mut up = [false; 2];
45 self.args.update_diff(&mut up, ctx, from, event);
46 let ((timeout, val), (timeout_up, val_up)) = arity2!(self.args.0, &up);
47 match ((timeout, val), (timeout_up, val_up)) {
48 ((Some(secs), _), (true, _)) | ((Some(secs), _), (_, true)) => {
49 match secs.clone().cast_to::<Duration>() {
50 Ok(dur) => {
51 let id = BindId::new();
52 self.id = Some(id);
53 ctx.rt.ref_var(id, self.eid);
54 ctx.rt.set_timer(id, dur);
55 return None;
56 }
57 Err(_) => {
58 self.id = None;
59 return None;
60 }
61 }
62 }
63 ((None, _), (_, _))
64 | ((_, None), (_, _))
65 | ((Some(_), Some(_)), (false, _)) => (),
66 };
67 self.id.and_then(|id| {
68 if event.variables.contains_key(&id) {
69 self.id = None;
70 ctx.rt.unref_var(id, self.eid);
71 self.args.0.get(1).and_then(|v| v.clone())
72 } else {
73 None
74 }
75 })
76 }
77
78 fn delete(&mut self, ctx: &mut ExecCtx<R, E>) {
79 if let Some(id) = self.id.take() {
80 ctx.rt.unref_var(id, self.eid)
81 }
82 }
83
84 fn sleep(&mut self, ctx: &mut ExecCtx<R, E>) {
85 if let Some(id) = self.id.take() {
86 ctx.rt.unref_var(id, self.eid);
87 }
88 self.args.clear()
89 }
90}
91
92#[derive(Debug, Clone, Copy)]
93enum Repeat {
94 Yes,
95 No,
96 N(u64),
97}
98
99impl FromValue for Repeat {
100 fn from_value(v: Value) -> Result<Self> {
101 match v {
102 Value::Bool(true) => Ok(Repeat::Yes),
103 Value::Bool(false) => Ok(Repeat::No),
104 v => match v.cast_to::<u64>() {
105 Ok(n) => Ok(Repeat::N(n)),
106 Err(_) => bail!("could not cast to repeat"),
107 },
108 }
109 }
110}
111
112impl SubAssign<u64> for Repeat {
113 fn sub_assign(&mut self, rhs: u64) {
114 match self {
115 Repeat::Yes | Repeat::No => (),
116 Repeat::N(n) => *n -= rhs,
117 }
118 }
119}
120
121impl Repeat {
122 fn will_repeat(&self) -> bool {
123 match self {
124 Repeat::No => false,
125 Repeat::Yes => true,
126 Repeat::N(n) => *n > 0,
127 }
128 }
129}
130
131#[derive(Debug)]
132struct Timer {
133 args: CachedVals,
134 timeout: Option<Duration>,
135 repeat: Repeat,
136 id: Option<BindId>,
137 eid: ExprId,
138}
139
140impl<R: Rt, E: UserEvent> BuiltIn<R, E> for Timer {
141 const NAME: &str = "time_timer";
142 deftype!(
143 "fn([duration, Number], [bool, Number]) -> Result<datetime, `TimerError(string)>"
144 );
145
146 fn init<'a, 'b, 'c>(
147 _ctx: &'a mut ExecCtx<R, E>,
148 _typ: &'a graphix_compiler::typ::FnType,
149 _scope: &'b Scope,
150 from: &'c [Node<R, E>],
151 top_id: ExprId,
152 ) -> Result<Box<dyn Apply<R, E>>> {
153 Ok(Box::new(Self {
154 args: CachedVals::new(from),
155 timeout: None,
156 repeat: Repeat::No,
157 id: None,
158 eid: top_id,
159 }))
160 }
161}
162
163impl<R: Rt, E: UserEvent> Apply<R, E> for Timer {
164 fn update(
165 &mut self,
166 ctx: &mut ExecCtx<R, E>,
167 from: &mut [Node<R, E>],
168 event: &mut Event<E>,
169 ) -> Option<Value> {
170 macro_rules! error {
171 () => {{
172 self.id = None;
173 self.timeout = None;
174 self.repeat = Repeat::No;
175 return Some(err!(
176 literal!("TimerError"),
177 "timer(per, rep): expected duration, bool or number >= 0"
178 ));
179 }};
180 }
181 macro_rules! schedule {
182 ($dur:expr) => {{
183 let id = BindId::new();
184 self.id = Some(id);
185 ctx.rt.ref_var(id, self.eid);
186 ctx.rt.set_timer(id, $dur);
187 }};
188 }
189 let mut up = [false; 2];
190 self.args.update_diff(&mut up, ctx, from, event);
191 let ((timeout, repeat), (timeout_up, repeat_up)) = arity2!(self.args.0, &up);
192 match ((timeout, repeat), (timeout_up, repeat_up)) {
193 ((None, Some(r)), (true, true)) | ((_, Some(r)), (false, true)) => {
194 match r.clone().cast_to::<Repeat>() {
195 Err(_) => error!(),
196 Ok(repeat) => {
197 self.repeat = repeat;
198 if let Some(dur) = self.timeout {
199 if self.id.is_none() && repeat.will_repeat() {
200 schedule!(dur)
201 }
202 }
203 }
204 }
205 }
206 ((Some(s), None), (true, _)) => match s.clone().cast_to::<Duration>() {
207 Err(_) => error!(),
208 Ok(dur) => self.timeout = Some(dur),
209 },
210 ((Some(s), Some(r)), (true, _)) => {
211 match (s.clone().cast_to::<Duration>(), r.clone().cast_to::<Repeat>()) {
212 (Err(_), _) | (_, Err(_)) => error!(),
213 (Ok(dur), Ok(repeat)) => {
214 self.timeout = Some(dur);
215 self.repeat = repeat;
216 schedule!(dur)
217 }
218 }
219 }
220 ((_, _), (false, false))
221 | ((None, None), (_, _))
222 | ((None, _), (true, false))
223 | ((_, None), (false, true)) => (),
224 }
225 self.id.and_then(|id| event.variables.get(&id).map(|now| (id, now))).map(
226 |(id, now)| {
227 ctx.rt.unref_var(id, self.eid);
228 self.id = None;
229 self.repeat -= 1;
230 if let Some(dur) = self.timeout {
231 if self.repeat.will_repeat() {
232 schedule!(dur)
233 }
234 }
235 now.clone()
236 },
237 )
238 }
239
240 fn delete(&mut self, ctx: &mut ExecCtx<R, E>) {
241 if let Some(id) = self.id.take() {
242 ctx.rt.unref_var(id, self.eid);
243 }
244 }
245
246 fn sleep(&mut self, ctx: &mut ExecCtx<R, E>) {
247 self.args.clear();
248 self.timeout = None;
249 self.repeat = Repeat::No;
250 if let Some(id) = self.id.take() {
251 ctx.rt.unref_var(id, self.eid);
252 }
253 }
254}
255
256#[derive(Debug)]
257struct Now;
258
259impl<R: Rt, E: UserEvent> BuiltIn<R, E> for Now {
260 const NAME: &str = "time_now";
261 deftype!("fn(Any) -> datetime");
262
263 fn init<'a, 'b, 'c>(
264 _ctx: &'a mut ExecCtx<R, E>,
265 _typ: &'a graphix_compiler::typ::FnType,
266 _scope: &'b Scope,
267 _from: &'c [Node<R, E>],
268 _top_id: ExprId,
269 ) -> Result<Box<dyn Apply<R, E>>> {
270 Ok(Box::new(Self))
271 }
272}
273
274impl<R: Rt, E: UserEvent> Apply<R, E> for Now {
275 fn update(
276 &mut self,
277 ctx: &mut ExecCtx<R, E>,
278 from: &mut [Node<R, E>],
279 event: &mut Event<E>,
280 ) -> Option<Value> {
281 if from[0].update(ctx, event).is_some() {
282 Some(Value::from(Utc::now()))
283 } else {
284 None
285 }
286 }
287
288 fn delete(&mut self, _ctx: &mut ExecCtx<R, E>) {}
289 fn sleep(&mut self, _ctx: &mut ExecCtx<R, E>) {}
290}
291
292graphix_derive::defpackage! {
293 builtins => [
294 AfterIdle,
295 Timer,
296 Now,
297 ],
298}