1use std::cell::RefCell;
8use std::panic::{catch_unwind, AssertUnwindSafe};
9use std::rc::Rc;
10
11use crate::ctx::{Ctx, DepTerminal, WaveData};
12use crate::higher_order::{merge_map_with_ctx, switch_map_with_ctx};
13use crate::node::{Core, Node, NodeOpts};
14use crate::operators::Operator;
15use crate::protocol::Message;
16use crate::sources::{interval, timer};
17
18type Body = Rc<dyn Fn(&Ctx)>;
19type BodyCell = Rc<RefCell<Option<Body>>>;
20type AuditProject<S> = Rc<dyn Fn(&Ctx, &S) -> Core>;
21
22fn delayed_value<S: Clone + 'static>(ctx: &Ctx, value: S, ms: u64) -> Node<S> {
23 let tick = ctx.init_node_in_scope(timer(ms), vec![]);
24 ctx.init_node_in_scope(
25 Operator::with_opts(
26 "delayedValue",
27 crate::node::NodeOpts {
28 partial: true,
29 error_when_deps_error: false,
30 complete_when_deps_complete: false,
31 terminal_as_real_input: true,
32 ..crate::node::NodeOpts::default()
33 },
34 move |ctx| {
35 if let Some(DepTerminal::Error(error)) = ctx.terminal(0) {
36 ctx.down(vec![Message::Error(error.to_string().into())]);
37 } else if dep_has_data(ctx, 0) || is_complete(ctx.terminal(0)) {
38 ctx.down(vec![
39 Message::Data(Rc::new(value.clone())),
40 Message::Complete,
41 ]);
42 }
43 },
44 ),
45 vec![tick.erased()],
46 )
47}
48
49pub fn delay<S: Clone + 'static>(ms: u64) -> Operator<S> {
51 merge_map_with_ctx("delay", move |ctx, value: &S| {
52 delayed_value(ctx, value.clone(), ms)
53 })
54}
55
56pub fn debounce<S: Clone + 'static>(ms: u64) -> Operator<S> {
58 switch_map_with_ctx("debounce", move |ctx, value: &S| {
59 delayed_value(ctx, value.clone(), ms)
60 })
61}
62
63pub fn debounce_time<S: Clone + 'static>(ms: u64) -> Operator<S> {
65 switch_map_with_ctx("debounceTime", move |ctx, value: &S| {
66 delayed_value(ctx, value.clone(), ms)
67 })
68}
69
70pub fn throttle<S: Clone + 'static>(ms: u64) -> Operator<S> {
72 throttle_with_factory("throttle", ms)
73}
74
75pub fn throttle_time<S: Clone + 'static>(ms: u64) -> Operator<S> {
77 throttle_with_factory("throttleTime", ms)
78}
79
80#[derive(Clone)]
81struct AuditState<S> {
82 window_open: bool,
83 latest: Option<S>,
84 notifier: Option<Core>,
85 suppress_next_notifier: bool,
86}
87
88#[derive(Clone)]
89struct TimeoutState {
90 timer: Option<Core>,
91}
92
93#[derive(Clone)]
94struct BufferTimeState<S> {
95 buffer: Vec<S>,
96 interval: Option<Core>,
97}
98
99#[derive(Clone)]
100struct ThrottleState {
101 timer: Option<Core>,
102 source_done: bool,
103}
104
105fn throttle_with_factory<S: Clone + 'static>(factory: &'static str, ms: u64) -> Operator<S> {
106 let body_cell: BodyCell = Rc::new(RefCell::new(None));
107 let body_cell_for_body = body_cell.clone();
108 let body: Body = Rc::new(move |ctx| {
109 run_throttle_body::<S>(ctx, ms, &body_cell_for_body);
110 });
111 *body_cell.borrow_mut() = Some(body.clone());
112
113 Operator::with_opts(
114 factory,
115 crate::node::NodeOpts {
116 partial: true,
117 error_when_deps_error: false,
118 complete_when_deps_complete: false,
119 terminal_as_real_input: true,
120 ..crate::node::NodeOpts::default()
121 },
122 move |ctx| body(ctx),
123 )
124}
125
126fn run_throttle_body<S: Clone + 'static>(ctx: &Ctx, ms: u64, body_cell: &BodyCell) {
127 ctx.state_persist(true);
128 let mut st = ctx
129 .state_get::<ThrottleState>()
130 .map(|v| (*v).clone())
131 .unwrap_or(ThrottleState {
132 timer: None,
133 source_done: false,
134 });
135
136 let timer_dep = if st.source_done { 0 } else { 1 };
137 let mut to_remove = Vec::new();
138 let mut to_add = None;
139 let mut set_to_timer_only = false;
140 let mut complete = false;
141
142 if st.timer.is_some() && (dep_has_data(ctx, timer_dep) || is_complete(ctx.terminal(timer_dep)))
143 {
144 if let Some(timer) = st.timer.take() {
145 to_remove.push(timer);
146 }
147 if st.source_done {
148 complete = true;
149 }
150 }
151
152 if let Some(error) = first_error(ctx) {
153 if let Some(timer) = st.timer.take() {
154 to_remove.push(timer);
155 }
156 ctx.state_set(ThrottleState {
157 timer: None,
158 source_done: true,
159 });
160 for timer in to_remove {
161 ctx.rewire_next_unsubscribe_dep(timer, rewire_body(body_cell));
162 }
163 ctx.down(vec![Message::Error(error.into())]);
164 return;
165 }
166
167 if !st.source_done {
168 let source_batch = ctx.batch::<S>(0);
169 if st.timer.is_none() {
170 if let Some(value) = source_batch.first() {
171 ctx.down(vec![Message::Data(Rc::new((**value).clone()))]);
172 let timer = ctx.init_node_in_scope(timer(ms), vec![]).erased();
173 st.timer = Some(timer.clone());
174 to_add = Some(timer);
175 }
176 }
177
178 if is_complete(ctx.terminal(0)) {
179 st.source_done = true;
180 if st.timer.is_some() {
181 set_to_timer_only = true;
182 } else {
183 complete = true;
184 }
185 }
186 }
187
188 ctx.state_set(st.clone());
189 for timer in to_remove {
190 ctx.rewire_next_unsubscribe_dep(timer, rewire_body(body_cell));
191 }
192 if set_to_timer_only {
193 let timer = st
194 .timer
195 .clone()
196 .expect("source_done with live throttle timer");
197 ctx.rewire_next_replace_deps(vec![timer], rewire_body(body_cell));
198 } else if let Some(timer) = to_add {
199 ctx.rewire_next_subscribe_dep(timer, rewire_body(body_cell));
200 }
201 if complete {
202 ctx.down(vec![Message::Complete]);
203 }
204}
205
206pub fn audit<S: Clone + 'static, N: 'static>(
208 duration_selector: impl Fn(&S) -> Node<N> + 'static,
209) -> Operator<S> {
210 let project: AuditProject<S> = Rc::new(move |_ctx, value| duration_selector(value).erased());
211 audit_with_ctx("audit", project)
212}
213
214pub fn audit_time<S: Clone + 'static>(ms: u64) -> Operator<S> {
216 let project: AuditProject<S> =
217 Rc::new(move |ctx, _value| ctx.init_node_in_scope(timer(ms), vec![]).erased());
218 audit_with_ctx("auditTime", project)
219}
220
221fn audit_with_ctx<S: Clone + 'static>(
222 factory: &'static str,
223 duration_selector: AuditProject<S>,
224) -> Operator<S> {
225 let body_cell: BodyCell = Rc::new(RefCell::new(None));
226 let body_cell_for_body = body_cell.clone();
227 let body: Body = Rc::new(move |ctx| {
228 run_audit_body(ctx, &duration_selector, &body_cell_for_body);
229 });
230 *body_cell.borrow_mut() = Some(body.clone());
231
232 Operator::with_opts(
233 factory,
234 crate::node::NodeOpts {
235 partial: true,
236 error_when_deps_error: false,
237 complete_when_deps_complete: false,
238 terminal_as_real_input: true,
239 ..crate::node::NodeOpts::default()
240 },
241 move |ctx| body(ctx),
242 )
243}
244
245fn run_audit_body<S: Clone + 'static>(
246 ctx: &Ctx,
247 duration_selector: &AuditProject<S>,
248 body_cell: &BodyCell,
249) {
250 let mut st = ctx
251 .state_get::<AuditState<S>>()
252 .map(|v| (*v).clone())
253 .unwrap_or(AuditState {
254 window_open: false,
255 latest: None,
256 notifier: None,
257 suppress_next_notifier: false,
258 });
259
260 let source_batch = ctx.batch::<S>(0);
261 let notifier_signaled = dep_has_data(ctx, 1) || is_complete(ctx.terminal(1));
262 let notifier_fired = st.window_open && notifier_signaled && !st.suppress_next_notifier;
263 let fired_notifier = notifier_fired.then(|| st.notifier.clone()).flatten();
264 if st.window_open && notifier_signaled && st.suppress_next_notifier {
265 st.suppress_next_notifier = false;
266 }
267 if notifier_fired {
268 if let Some(value) = st.latest.clone() {
269 ctx.down(vec![Message::Data(Rc::new(value))]);
270 }
271 let old = st.notifier.take();
272 st.window_open = false;
273 st.latest = None;
274 st.suppress_next_notifier = false;
275 if let Some(old) = old {
276 ctx.rewire_next_unsubscribe_dep(old, rewire_body(body_cell));
277 }
278 }
279
280 if let Some(error) = first_error(ctx) {
281 if let Some(old) = st.notifier.take() {
282 ctx.rewire_next_unsubscribe_dep(old, rewire_body(body_cell));
283 }
284 ctx.state_set(AuditState::<S> {
285 window_open: false,
286 latest: None,
287 notifier: None,
288 suppress_next_notifier: false,
289 });
290 ctx.down(vec![Message::Error(error.into())]);
291 return;
292 }
293
294 for value in &source_batch {
295 st.latest = Some((**value).clone());
296 }
297
298 if is_complete(ctx.terminal(0)) {
299 if let Some(value) = st.latest.clone() {
300 ctx.down(vec![Message::Data(Rc::new(value))]);
301 }
302 if let Some(old) = st.notifier.take() {
303 ctx.rewire_next_unsubscribe_dep(old, rewire_body(body_cell));
304 }
305 ctx.state_set(AuditState::<S> {
306 window_open: false,
307 latest: None,
308 notifier: None,
309 suppress_next_notifier: false,
310 });
311 ctx.down(vec![Message::Complete]);
312 return;
313 }
314
315 if !st.window_open && !source_batch.is_empty() {
316 if let Some(value) = st.latest.as_ref() {
317 match catch_unwind(AssertUnwindSafe(|| duration_selector(ctx, value))) {
318 Ok(notifier) => {
319 st.window_open = true;
320 st.suppress_next_notifier = fired_notifier
321 .as_ref()
322 .is_some_and(|old| old.ptr_eq(¬ifier));
323 st.notifier = Some(notifier.clone());
324 ctx.rewire_next_subscribe_dep(notifier, rewire_body(body_cell));
325 }
326 Err(payload) => {
327 ctx.state_set(AuditState::<S> {
328 window_open: false,
329 latest: None,
330 notifier: None,
331 suppress_next_notifier: false,
332 });
333 ctx.down(vec![Message::Error(panic_payload(payload).into())]);
334 return;
335 }
336 }
337 }
338 }
339
340 ctx.state_set(st);
341}
342
343pub fn timeout<S: Clone + 'static>(source: &Node<S>, ms: u64) -> Node<S> {
346 let source_core = source.erased();
347 let initial_timer = scoped_timer(&source_core, ms).erased();
348 let body_cell: BodyCell = Rc::new(RefCell::new(None));
349 let body_cell_for_body = body_cell.clone();
350 let initial_for_body = initial_timer.clone();
351 let source_for_body = source_core.clone();
352 let body: Body = Rc::new(move |ctx| {
353 run_timeout_body::<S>(
354 ctx,
355 ms,
356 &initial_for_body,
357 &source_for_body,
358 &body_cell_for_body,
359 );
360 });
361 *body_cell.borrow_mut() = Some(body.clone());
362
363 let node = crate::operators::init_node_in_arena_with_dispatcher(
364 Operator::with_opts("timeout", time_helper_opts(), move |ctx| {
365 body(ctx);
366 }),
367 &source_core.arena(),
368 source_core.dispatcher(),
369 vec![initial_timer, source_core.clone()],
370 NodeOpts::default(),
371 );
372 node.erased().set_environment(source_core.environment());
373 node
374}
375
376fn run_timeout_body<S: Clone + 'static>(
377 ctx: &Ctx,
378 ms: u64,
379 initial_timer: &Core,
380 source: &Core,
381 body_cell: &BodyCell,
382) {
383 let mut st = ctx
384 .state_get::<TimeoutState>()
385 .map(|v| (*v).clone())
386 .unwrap_or_else(|| TimeoutState {
387 timer: Some(initial_timer.clone()),
388 });
389
390 let source_batch = ctx.batch::<S>(1);
391 for value in &source_batch {
392 ctx.down(vec![Message::Data(Rc::new((**value).clone()))]);
393 }
394
395 if is_complete(ctx.terminal(1)) {
396 if let Some(timer) = st.timer.take() {
397 ctx.rewire_next_unsubscribe_dep(timer, rewire_body(body_cell));
398 }
399 ctx.state_set(TimeoutState { timer: None });
400 ctx.down(vec![Message::Complete]);
401 return;
402 }
403
404 if let Some(DepTerminal::Error(error)) = ctx.terminal(1) {
405 if let Some(timer) = st.timer.take() {
406 ctx.rewire_next_unsubscribe_dep(timer, rewire_body(body_cell));
407 }
408 ctx.state_set(TimeoutState { timer: None });
409 ctx.down(vec![Message::Error(error.to_string().into())]);
410 return;
411 }
412
413 if !source_batch.is_empty() {
414 let old = st.timer.take();
415 let next = ctx.init_node_in_scope(timer(ms), vec![]).erased();
416 st.timer = Some(next.clone());
417 ctx.state_set(st);
418 if old.is_some() {
419 ctx.rewire_next_replace_deps(vec![next, source.clone()], rewire_body(body_cell));
420 } else {
421 ctx.rewire_next_subscribe_dep(next, rewire_body(body_cell));
422 }
423 return;
424 }
425
426 if let Some(DepTerminal::Error(error)) = ctx.terminal(0) {
427 ctx.state_set(TimeoutState { timer: None });
428 ctx.down(vec![Message::Error(error.to_string().into())]);
429 return;
430 }
431
432 if dep_has_data(ctx, 0) || is_complete(ctx.terminal(0)) {
433 ctx.state_set(TimeoutState { timer: None });
434 ctx.down(vec![Message::Error(
435 format!("timeout: no value within {ms}ms").into(),
436 )]);
437 return;
438 }
439
440 ctx.state_set(st);
441}
442
443pub fn buffer_time<S: Clone + 'static>(source: &Node<S>, ms: u64) -> Node<Vec<S>> {
445 let source_core = source.erased();
446 let interval_node = scoped_interval(&source_core, ms).erased();
447 let body_cell: BodyCell = Rc::new(RefCell::new(None));
448 let body_cell_for_body = body_cell.clone();
449 let interval_for_body = interval_node.clone();
450 let body: Body = Rc::new(move |ctx| {
451 run_buffer_time_body::<S>(ctx, &interval_for_body, &body_cell_for_body);
452 });
453 *body_cell.borrow_mut() = Some(body.clone());
454
455 let node = crate::operators::init_node_in_arena_with_dispatcher(
456 Operator::with_opts("bufferTime", time_helper_opts(), move |ctx| {
457 body(ctx);
458 }),
459 &source_core.arena(),
460 source_core.dispatcher(),
461 vec![interval_node, source_core.clone()],
462 NodeOpts::default(),
463 );
464 node.erased().set_environment(source_core.environment());
465 node
466}
467
468fn run_buffer_time_body<S: Clone + 'static>(
469 ctx: &Ctx,
470 initial_interval: &Core,
471 body_cell: &BodyCell,
472) {
473 let mut st = ctx
474 .state_get::<BufferTimeState<S>>()
475 .map(|v| (*v).clone())
476 .unwrap_or_else(|| BufferTimeState {
477 buffer: Vec::new(),
478 interval: Some(initial_interval.clone()),
479 });
480
481 for value in ctx.batch::<S>(1) {
482 st.buffer.push((*value).clone());
483 }
484
485 if is_complete(ctx.terminal(1)) {
486 if !st.buffer.is_empty() {
487 ctx.down(vec![Message::Data(Rc::new(st.buffer.clone()))]);
488 }
489 if let Some(interval) = st.interval.take() {
490 ctx.rewire_next_unsubscribe_dep(interval, rewire_body(body_cell));
491 }
492 ctx.state_set(BufferTimeState::<S> {
493 buffer: Vec::new(),
494 interval: None,
495 });
496 ctx.down(vec![Message::Complete]);
497 return;
498 }
499
500 if let Some(DepTerminal::Error(error)) = ctx.terminal(1) {
501 if let Some(interval) = st.interval.take() {
502 ctx.rewire_next_unsubscribe_dep(interval, rewire_body(body_cell));
503 }
504 ctx.state_set(BufferTimeState::<S> {
505 buffer: Vec::new(),
506 interval: None,
507 });
508 ctx.down(vec![Message::Error(error.to_string().into())]);
509 return;
510 }
511
512 if let Some(DepTerminal::Error(error)) = ctx.terminal(0) {
513 ctx.state_set(BufferTimeState::<S> {
514 buffer: Vec::new(),
515 interval: None,
516 });
517 ctx.down(vec![Message::Error(error.to_string().into())]);
518 return;
519 }
520
521 if dep_has_data(ctx, 0) {
522 ctx.down(vec![Message::Data(Rc::new(st.buffer.clone()))]);
523 st.buffer.clear();
524 }
525
526 ctx.state_set(st);
527}
528
529fn scoped_timer(anchor: &Core, ms: u64) -> Node<u64> {
530 scoped_time_source(anchor, timer(ms))
531}
532
533fn scoped_interval(anchor: &Core, ms: u64) -> Node<u64> {
534 scoped_time_source(anchor, interval(ms))
535}
536
537fn scoped_time_source(anchor: &Core, op: Operator<u64>) -> Node<u64> {
538 let node = crate::operators::init_node_in_arena_with_dispatcher(
539 op,
540 &anchor.arena(),
541 anchor.dispatcher(),
542 vec![],
543 NodeOpts::default(),
544 );
545 node.erased().set_environment(anchor.environment());
546 node
547}
548
549fn time_helper_opts() -> NodeOpts {
550 NodeOpts {
551 partial: true,
552 complete_when_deps_complete: false,
553 error_when_deps_error: false,
554 terminal_as_real_input: true,
555 ..NodeOpts::default()
556 }
557}
558
559fn rewire_body(body_cell: &BodyCell) -> impl Fn(&Ctx) + 'static {
560 let body_cell = body_cell.clone();
561 move |ctx| {
562 let body = body_cell
563 .borrow()
564 .as_ref()
565 .expect("audit body initialized")
566 .clone();
567 body(ctx);
568 }
569}
570
571fn dep_has_data(ctx: &Ctx, dep: usize) -> bool {
572 ctx.wave_data().get(dep).is_some_and(|waves| {
573 waves
574 .iter()
575 .flatten()
576 .any(|v| matches!(v, WaveData::Data(_)))
577 })
578}
579
580fn is_complete(term: Option<&DepTerminal>) -> bool {
581 matches!(term, Some(DepTerminal::Complete))
582}
583
584fn first_error(ctx: &Ctx) -> Option<String> {
585 (0..ctx.dep_len()).find_map(|i| match ctx.terminal(i) {
586 Some(DepTerminal::Error(error)) => Some(error.to_string()),
587 _ => None,
588 })
589}
590
591fn panic_payload(payload: Box<dyn std::any::Any + Send>) -> String {
592 if let Some(s) = payload.downcast_ref::<&str>() {
593 (*s).to_owned()
594 } else if let Some(s) = payload.downcast_ref::<String>() {
595 s.clone()
596 } else {
597 "time operator panicked".to_owned()
598 }
599}