1use std::marker::PhantomData;
8use std::rc::Rc;
9
10use crate::ctx::{Ctx, DepTerminal};
11use crate::dispatcher::Dispatcher;
12use crate::node::{Core, GraphArena, Node, NodeOpts, Pausable};
13use crate::protocol::{AnyValue, Message};
14
15pub struct Operator<T> {
17 pub factory: &'static str,
19 pub body: Rc<dyn Fn(&Ctx)>,
21 pub opts: NodeOpts,
23 _t: PhantomData<fn() -> T>,
24}
25
26impl<T> Clone for Operator<T> {
27 fn clone(&self) -> Self {
28 Self {
29 factory: self.factory,
30 body: self.body.clone(),
31 opts: self.opts.clone(),
32 _t: PhantomData,
33 }
34 }
35}
36
37impl<T> Operator<T> {
38 pub fn new(factory: &'static str, body: impl Fn(&Ctx) + 'static) -> Self {
40 Self::with_opts(factory, NodeOpts::default(), body)
41 }
42
43 pub fn with_opts(
45 factory: &'static str,
46 mut opts: NodeOpts,
47 body: impl Fn(&Ctx) + 'static,
48 ) -> Self {
49 if opts.factory.is_none() {
50 opts.factory = Some(factory.to_owned());
51 }
52 Self {
53 factory,
54 body: Rc::new(body),
55 opts,
56 _t: PhantomData,
57 }
58 }
59}
60
61pub fn init_node<T: 'static>(op: Operator<T>, deps: Vec<Core>, caller_opts: NodeOpts) -> Node<T> {
64 init_node_in_arena(op, &GraphArena::default(), deps, caller_opts)
65}
66
67pub(crate) fn init_node_in_arena<T: 'static>(
68 op: Operator<T>,
69 arena: &GraphArena,
70 deps: Vec<Core>,
71 caller_opts: NodeOpts,
72) -> Node<T> {
73 init_node_in_arena_with_dispatcher(
74 op,
75 arena,
76 crate::dispatcher::default_dispatcher(),
77 deps,
78 caller_opts,
79 )
80}
81
82pub(crate) fn init_node_in_arena_with_dispatcher<T: 'static>(
83 op: Operator<T>,
84 arena: &GraphArena,
85 dispatcher: Dispatcher,
86 deps: Vec<Core>,
87 caller_opts: NodeOpts,
88) -> Node<T> {
89 let mut opts = merge_node_opts(&op.opts, caller_opts);
90 if opts.factory.is_none() {
91 opts.factory = Some(op.factory.to_owned());
92 }
93 Node::derived_opts_in_arena_with_dispatcher(arena, dispatcher, deps, opts, move |ctx| {
94 (op.body)(ctx)
95 })
96}
97
98pub fn map<S: 'static, T: 'static>(f: impl Fn(&S) -> T + 'static) -> Operator<T> {
100 Operator::new("map", move |ctx| {
101 for value in ctx.batch::<S>(0) {
102 ctx.emit(f(value.as_ref()));
103 }
104 })
105}
106
107pub fn filter<S: Clone + 'static>(pred: impl Fn(&S) -> bool + 'static) -> Operator<S> {
109 Operator::new("filter", move |ctx| {
110 for value in ctx.batch::<S>(0) {
111 if pred(value.as_ref()) {
112 ctx.emit((*value).clone());
113 }
114 }
115 })
116}
117
118pub fn scan<S: 'static, T: Clone + 'static>(
120 reducer: impl Fn(T, &S) -> T + 'static,
121 seed: T,
122) -> Operator<T> {
123 Operator::new("scan", move |ctx| {
124 let mut acc = ctx
125 .state_get::<T>()
126 .map(|v| (*v).clone())
127 .unwrap_or_else(|| seed.clone());
128 for value in ctx.batch::<S>(0) {
129 acc = reducer(acc, value.as_ref());
130 ctx.emit(acc.clone());
131 }
132 ctx.state_set(acc);
133 })
134}
135
136pub fn take<S: Clone + 'static>(n: usize) -> Operator<S> {
138 Operator::new("take", move |ctx| {
139 if n == 0 {
140 ctx.down(vec![Message::Complete]);
141 return;
142 }
143 let mut count = ctx.state_get::<usize>().map_or(0, |v| *v);
144 if count >= n {
145 return;
146 }
147 for value in ctx.batch::<S>(0) {
148 if count >= n {
149 break;
150 }
151 count += 1;
152 let out: AnyValue = Rc::new((*value).clone());
153 if count >= n {
154 ctx.down(vec![Message::Data(out), Message::Complete]);
155 } else {
156 ctx.down(vec![Message::Data(out)]);
157 }
158 }
159 ctx.state_set(count);
160 })
161}
162
163pub fn distinct_until_changed<S: Clone + 'static>(
165 eq: impl Fn(&S, &S) -> bool + 'static,
166) -> Operator<S> {
167 Operator::new("distinctUntilChanged", move |ctx| {
168 let mut last = ctx.state_get::<S>().map(|v| (*v).clone());
169 for value in ctx.batch::<S>(0) {
170 if last.as_ref().is_some_and(|prev| eq(prev, value.as_ref())) {
171 continue;
172 }
173 last = Some((*value).clone());
174 ctx.emit((*value).clone());
175 }
176 if let Some(value) = last {
177 ctx.state_set(value);
178 }
179 })
180}
181
182pub fn merge<T: Clone + 'static>() -> Operator<T> {
184 Operator::with_opts(
185 "merge",
186 NodeOpts {
187 partial: true,
188 ..NodeOpts::default()
189 },
190 move |ctx| {
191 for i in 0..ctx.dep_len() {
192 for value in ctx.batch::<T>(i) {
193 ctx.emit((*value).clone());
194 }
195 }
196 },
197 )
198}
199
200pub fn reduce<S: 'static, T: Clone + 'static>(
202 reducer: impl Fn(T, &S) -> T + 'static,
203 seed: T,
204) -> Operator<T> {
205 Operator::with_opts(
206 "reduce",
207 NodeOpts {
208 complete_when_deps_complete: false,
209 terminal_as_real_input: true,
210 ..NodeOpts::default()
211 },
212 move |ctx| {
213 let mut acc = ctx
214 .state_get::<T>()
215 .map(|v| (*v).clone())
216 .unwrap_or_else(|| seed.clone());
217 for value in ctx.batch::<S>(0) {
218 acc = reducer(acc, value.as_ref());
219 }
220 ctx.state_set(acc.clone());
221 if is_complete(ctx.terminal(0)) {
222 ctx.down(vec![Message::Data(Rc::new(acc)), Message::Complete]);
223 }
224 },
225 )
226}
227
228pub fn pairwise<S: Clone + 'static>() -> Operator<(S, S)> {
230 Operator::new("pairwise", move |ctx| {
231 let mut prev = ctx.state_get::<S>().map(|v| (*v).clone());
232 for value in ctx.batch::<S>(0) {
233 if let Some(p) = prev.clone() {
234 ctx.emit((p, (*value).clone()));
235 }
236 prev = Some((*value).clone());
237 }
238 if let Some(value) = prev {
239 ctx.state_set(value);
240 }
241 })
242}
243
244pub fn skip<S: Clone + 'static>(n: usize) -> Operator<S> {
246 Operator::new("skip", move |ctx| {
247 let mut count = ctx.state_get::<usize>().map_or(0, |v| *v);
248 for value in ctx.batch::<S>(0) {
249 if count < n {
250 count += 1;
251 } else {
252 ctx.emit((*value).clone());
253 }
254 }
255 ctx.state_set(count);
256 })
257}
258
259pub fn take_while<S: Clone + 'static>(pred: impl Fn(&S) -> bool + 'static) -> Operator<S> {
261 Operator::new("takeWhile", move |ctx| {
262 for value in ctx.batch::<S>(0) {
263 if pred(value.as_ref()) {
264 ctx.emit((*value).clone());
265 } else {
266 ctx.down(vec![Message::Complete]);
267 return;
268 }
269 }
270 })
271}
272
273pub fn first<S: Clone + 'static>(pred: impl Fn(&S) -> bool + 'static) -> Operator<S> {
275 Operator::new("first", move |ctx| {
276 for value in ctx.batch::<S>(0) {
277 if pred(value.as_ref()) {
278 ctx.down(vec![
279 Message::Data(Rc::new((*value).clone())),
280 Message::Complete,
281 ]);
282 return;
283 }
284 }
285 })
286}
287
288pub fn first_any<S: Clone + 'static>() -> Operator<S> {
290 first(|_: &S| true)
291}
292
293pub fn last<S: Clone + 'static>(pred: impl Fn(&S) -> bool + 'static) -> Operator<S> {
295 Operator::with_opts(
296 "last",
297 NodeOpts {
298 complete_when_deps_complete: false,
299 terminal_as_real_input: true,
300 ..NodeOpts::default()
301 },
302 move |ctx| {
303 for value in ctx.batch::<S>(0) {
304 if pred(value.as_ref()) {
305 ctx.state_set((*value).clone());
306 }
307 }
308 if is_complete(ctx.terminal(0)) {
309 if let Some(value) = ctx.state_get::<S>() {
310 ctx.down(vec![
311 Message::Data(Rc::new((*value).clone())),
312 Message::Complete,
313 ]);
314 } else {
315 ctx.down(vec![Message::Complete]);
316 }
317 }
318 },
319 )
320}
321
322pub fn last_any<S: Clone + 'static>() -> Operator<S> {
324 last(|_: &S| true)
325}
326
327pub fn find<S: Clone + 'static>(pred: impl Fn(&S) -> bool + 'static) -> Operator<S> {
329 Operator::with_opts(
330 "find",
331 NodeOpts {
332 complete_when_deps_complete: false,
333 terminal_as_real_input: true,
334 ..NodeOpts::default()
335 },
336 move |ctx| {
337 for value in ctx.batch::<S>(0) {
338 if pred(value.as_ref()) {
339 ctx.down(vec![
340 Message::Data(Rc::new((*value).clone())),
341 Message::Complete,
342 ]);
343 return;
344 }
345 }
346 if is_complete(ctx.terminal(0)) {
347 ctx.down(vec![Message::Complete]);
348 }
349 },
350 )
351}
352
353pub fn element_at<S: Clone + 'static>(index: usize) -> Operator<S> {
355 Operator::with_opts(
356 "elementAt",
357 NodeOpts {
358 complete_when_deps_complete: false,
359 terminal_as_real_input: true,
360 ..NodeOpts::default()
361 },
362 move |ctx| {
363 let mut count = ctx.state_get::<usize>().map_or(0, |v| *v);
364 for value in ctx.batch::<S>(0) {
365 if count == index {
366 ctx.down(vec![
367 Message::Data(Rc::new((*value).clone())),
368 Message::Complete,
369 ]);
370 return;
371 }
372 count += 1;
373 }
374 ctx.state_set(count);
375 if is_complete(ctx.terminal(0)) {
376 ctx.down(vec![Message::Complete]);
377 }
378 },
379 )
380}
381
382pub fn tap<S: Clone + 'static>(f: impl Fn(&S) + 'static) -> Operator<S> {
384 Operator::new("tap", move |ctx| {
385 for value in ctx.batch::<S>(0) {
386 f(value.as_ref());
387 ctx.emit((*value).clone());
388 }
389 })
390}
391
392pub fn on_first_data<S: Clone + 'static>(f: impl Fn(&S) + 'static) -> Operator<S> {
394 on_first_data_where(f, |_| true)
395}
396
397pub fn on_first_data_where<S: Clone + 'static>(
399 f: impl Fn(&S) + 'static,
400 where_pred: impl Fn(&S) -> bool + 'static,
401) -> Operator<S> {
402 Operator::new("onFirstData", move |ctx| {
403 let mut fired = ctx.state_get::<bool>().is_some_and(|v| *v);
404 for value in ctx.batch::<S>(0) {
405 if !fired && where_pred(value.as_ref()) {
406 fired = true;
407 f(value.as_ref());
408 }
409 ctx.emit((*value).clone());
410 }
411 ctx.state_set(fired);
412 })
413}
414
415pub fn tap_first<S: Clone + 'static>(f: impl Fn(&S) + 'static) -> Operator<S> {
417 on_first_data(f)
418}
419
420#[derive(Clone)]
421struct SettleState<S> {
422 last: Option<S>,
423 quiet: usize,
424 waves: usize,
425 done: bool,
426}
427
428pub fn settle_by<S: Clone + 'static>(
430 quiet_waves: usize,
431 max_waves: Option<usize>,
432 equals: impl Fn(&S, &S) -> bool + 'static,
433) -> Operator<S> {
434 assert!(quiet_waves > 0, "settle: quiet_waves must be positive");
435 if let Some(max) = max_waves {
436 assert!(max > 0, "settle: max_waves must be positive when set");
437 }
438 Operator::new("settle", move |ctx| {
439 let mut st = ctx
440 .state_get::<SettleState<S>>()
441 .map(|v| (*v).clone())
442 .unwrap_or(SettleState {
443 last: None,
444 quiet: 0,
445 waves: 0,
446 done: false,
447 });
448 if st.done {
449 return;
450 }
451 st.waves += 1;
452 let mut saw_change = false;
453 for value in ctx.batch::<S>(0) {
454 let next = (*value).clone();
455 if st.last.as_ref().is_none_or(|prev| !equals(prev, &next)) {
456 saw_change = true;
457 }
458 st.last = Some(next.clone());
459 ctx.emit(next);
460 }
461 st.quiet = if saw_change { 0 } else { st.quiet + 1 };
462 let settled = st.last.is_some() && st.quiet >= quiet_waves;
463 let exhausted = max_waves.is_some_and(|max| st.waves >= max);
464 if settled || exhausted {
465 st.done = true;
466 ctx.state_set(st);
467 ctx.down(vec![Message::Complete]);
468 } else {
469 ctx.state_set(st);
470 }
471 })
472}
473
474pub fn settle<S: Clone + PartialEq + 'static>(
476 quiet_waves: usize,
477 max_waves: Option<usize>,
478) -> Operator<S> {
479 settle_by(quiet_waves, max_waves, |a, b| a == b)
480}
481
482pub fn rescue<S: Clone + 'static>(recover: impl Fn(&str) -> S + 'static) -> Operator<S> {
484 Operator::with_opts(
485 "rescue",
486 NodeOpts {
487 error_when_deps_error: false,
488 complete_when_deps_complete: false,
489 terminal_as_real_input: true,
490 ..NodeOpts::default()
491 },
492 move |ctx| {
493 for value in ctx.batch::<S>(0) {
494 ctx.emit((*value).clone());
495 }
496 match ctx.terminal(0) {
497 Some(DepTerminal::Complete) => ctx.down(vec![Message::Complete]),
498 Some(DepTerminal::Error(error)) => ctx.emit(recover(error.as_ref())),
499 None => {}
500 }
501 },
502 )
503}
504
505pub fn catch_error<S: Clone + 'static>(recover: impl Fn(&str) -> S + 'static) -> Operator<S> {
507 rescue(recover)
508}
509
510pub fn valve<S: Clone + 'static>() -> Operator<S> {
512 Operator::with_opts(
513 "valve",
514 NodeOpts {
515 partial: true,
516 complete_when_deps_complete: false,
517 terminal_as_real_input: true,
518 ..NodeOpts::default()
519 },
520 move |ctx| {
521 match ctx.terminal(0) {
522 Some(DepTerminal::Complete) => {
523 ctx.down(vec![Message::Complete]);
524 return;
525 }
526 Some(DepTerminal::Error(error)) => {
527 ctx.down(vec![Message::Error(error.to_string().into())]);
528 return;
529 }
530 None => {}
531 }
532
533 let control = ctx.data::<bool>(1).is_some_and(|v| *v);
534 if !control {
535 return;
536 }
537
538 let source_batch = ctx.batch::<S>(0);
539 if source_batch.is_empty() {
540 let control_fired = !ctx.batch::<bool>(1).is_empty();
541 if control_fired {
542 if let Some(latest) = ctx.data::<S>(0) {
543 ctx.emit((*latest).clone());
544 }
545 }
546 } else {
547 for value in source_batch {
548 ctx.emit((*value).clone());
549 }
550 }
551 },
552 )
553}
554
555fn is_complete(terminal: Option<&DepTerminal>) -> bool {
556 matches!(terminal, Some(DepTerminal::Complete))
557}
558
559fn is_default_node_opts(opts: &NodeOpts) -> bool {
560 opts.factory.is_none()
561 && opts.pool == Default::default()
562 && opts.pausable == Pausable::True
563 && !opts.partial
564 && opts.pull_id.is_none()
565 && opts.complete_when_deps_complete
566 && opts.error_when_deps_error
567 && !opts.terminal_as_real_input
568 && opts.versioning.is_none()
569}
570
571fn merge_node_opts(base: &NodeOpts, caller: NodeOpts) -> NodeOpts {
572 if is_default_node_opts(&caller) {
573 return base.clone();
574 }
575 let default = NodeOpts::default();
576 let mut opts = base.clone();
577 if caller.factory.is_some() {
578 opts.factory = caller.factory;
579 }
580 if caller.pool != default.pool {
581 opts.pool = caller.pool;
582 }
583 if caller.pausable != default.pausable {
584 opts.pausable = caller.pausable;
585 }
586 if caller.partial {
587 opts.partial = true;
588 }
589 if caller.pull_id.is_some() {
590 opts.pull_id = caller.pull_id;
591 }
592 if caller.complete_when_deps_complete != default.complete_when_deps_complete {
593 opts.complete_when_deps_complete = caller.complete_when_deps_complete;
594 }
595 if caller.error_when_deps_error != default.error_when_deps_error {
596 opts.error_when_deps_error = caller.error_when_deps_error;
597 }
598 if caller.terminal_as_real_input {
599 opts.terminal_as_real_input = true;
600 }
601 if caller.versioning.is_some() {
602 opts.versioning = caller.versioning;
603 }
604 opts
605}