1use std::cell::RefCell;
8use std::collections::VecDeque;
9use std::panic::{catch_unwind, AssertUnwindSafe};
10use std::rc::Rc;
11
12use crate::ctx::{Ctx, DepTerminal, WaveData};
13use crate::node::{Core, Node, NodeOpts};
14use crate::operators::Operator;
15use crate::protocol::Message;
16
17type Body = Rc<dyn Fn(&Ctx)>;
18type BodyCell = Rc<RefCell<Option<Body>>>;
19type Project<TIn, TOut> = Rc<dyn Fn(&Ctx, &TIn) -> Node<TOut>>;
20
21#[derive(Clone, Copy)]
22enum Mode {
23 Merge,
24 Switch,
25 Concat,
26 Exhaust,
27}
28
29#[derive(Clone)]
30struct MapState<TIn> {
31 inners: Vec<Core>,
32 queue: VecDeque<TIn>,
33 source_done: bool,
34}
35
36#[derive(Clone, Copy, Debug, Default)]
38pub struct MergeMapOptions {
39 pub concurrent: Option<usize>,
41}
42
43pub fn switch_map<TIn: Clone + 'static, TOut: 'static>(
45 project: impl Fn(&TIn) -> Node<TOut> + 'static,
46) -> Operator<TOut> {
47 map_operator(
48 "switchMap",
49 move |_ctx, value| project(value),
50 Mode::Switch,
51 None,
52 )
53}
54
55pub fn merge_map<TIn: Clone + 'static, TOut: 'static>(
57 project: impl Fn(&TIn) -> Node<TOut> + 'static,
58) -> Operator<TOut> {
59 map_operator(
60 "mergeMap",
61 move |_ctx, value| project(value),
62 Mode::Merge,
63 None,
64 )
65}
66
67#[must_use]
72pub fn merge_map_with_options<TIn: Clone + 'static, TOut: 'static>(
73 project: impl Fn(&TIn) -> Node<TOut> + 'static,
74 opts: MergeMapOptions,
75) -> Operator<TOut> {
76 if let Some(0) = opts.concurrent {
77 panic!("merge_map_with_options: concurrent must be positive");
78 }
79 map_operator(
80 "mergeMap",
81 move |_ctx, value| project(value),
82 Mode::Merge,
83 opts.concurrent,
84 )
85}
86
87pub fn flat_map<TIn: Clone + 'static, TOut: 'static>(
89 project: impl Fn(&TIn) -> Node<TOut> + 'static,
90) -> Operator<TOut> {
91 map_operator(
92 "flatMap",
93 move |_ctx, value| project(value),
94 Mode::Merge,
95 None,
96 )
97}
98
99pub fn concat_map<TIn: Clone + 'static, TOut: 'static>(
101 project: impl Fn(&TIn) -> Node<TOut> + 'static,
102) -> Operator<TOut> {
103 map_operator(
104 "concatMap",
105 move |_ctx, value| project(value),
106 Mode::Concat,
107 None,
108 )
109}
110
111pub fn exhaust_map<TIn: Clone + 'static, TOut: 'static>(
113 project: impl Fn(&TIn) -> Node<TOut> + 'static,
114) -> Operator<TOut> {
115 map_operator(
116 "exhaustMap",
117 move |_ctx, value| project(value),
118 Mode::Exhaust,
119 None,
120 )
121}
122
123pub(crate) fn switch_map_with_ctx<TIn: Clone + 'static, TOut: 'static>(
124 factory: &'static str,
125 project: impl Fn(&Ctx, &TIn) -> Node<TOut> + 'static,
126) -> Operator<TOut> {
127 map_operator(factory, project, Mode::Switch, None)
128}
129
130pub(crate) fn merge_map_with_ctx<TIn: Clone + 'static, TOut: 'static>(
131 factory: &'static str,
132 project: impl Fn(&Ctx, &TIn) -> Node<TOut> + 'static,
133) -> Operator<TOut> {
134 map_operator(factory, project, Mode::Merge, None)
135}
136
137#[derive(Clone)]
138struct RepeatState {
139 started: bool,
140 round: usize,
141 inner: Option<Core>,
142}
143
144pub fn repeat<T: 'static>(factory: impl Fn() -> Node<T> + 'static, count: usize) -> Operator<T> {
152 assert!(count > 0, "repeat: count must be positive");
153
154 let factory: Rc<dyn Fn() -> Node<T>> = Rc::new(factory);
155 let body_cell: BodyCell = Rc::new(RefCell::new(None));
156 let body_cell_for_body = body_cell.clone();
157 let body: Body = Rc::new(move |ctx| {
158 run_repeat_body(ctx, &factory, count, &body_cell_for_body);
159 });
160 *body_cell.borrow_mut() = Some(body.clone());
161
162 Operator::with_opts(
163 "repeat",
164 NodeOpts {
165 error_when_deps_error: false,
166 complete_when_deps_complete: false,
167 terminal_as_real_input: true,
168 ..NodeOpts::default()
169 },
170 move |ctx| body(ctx),
171 )
172}
173
174fn map_operator<TIn: Clone + 'static, TOut: 'static>(
175 factory: &'static str,
176 project: impl Fn(&Ctx, &TIn) -> Node<TOut> + 'static,
177 mode: Mode,
178 concurrent: Option<usize>,
179) -> Operator<TOut> {
180 let project: Project<TIn, TOut> = Rc::new(project);
181 let body_cell: BodyCell = Rc::new(RefCell::new(None));
182 let body_cell_for_body = body_cell.clone();
183 let body: Body = Rc::new(move |ctx| {
184 run_map_body(ctx, &project, mode, concurrent, &body_cell_for_body);
185 });
186 *body_cell.borrow_mut() = Some(body.clone());
187 Operator::with_opts(
188 factory,
189 NodeOpts {
190 partial: true,
191 error_when_deps_error: false,
192 complete_when_deps_complete: false,
193 terminal_as_real_input: true,
194 ..NodeOpts::default()
195 },
196 move |ctx| body(ctx),
197 )
198}
199
200fn run_map_body<TIn: Clone + 'static, TOut: 'static>(
201 ctx: &Ctx,
202 project: &Project<TIn, TOut>,
203 mode: Mode,
204 concurrent: Option<usize>,
205 body_cell: &BodyCell,
206) {
207 let mut st = ctx
208 .state_get::<MapState<TIn>>()
209 .map(|v| (*v).clone())
210 .unwrap_or(MapState {
211 inners: Vec::new(),
212 queue: VecDeque::new(),
213 source_done: false,
214 });
215
216 for i in 1..ctx.dep_len() {
217 forward_data(ctx, i);
218 }
219
220 if let Some(error) = first_error_terminal(ctx) {
221 cleanup_all_inners(ctx, &mut st, body_cell);
222 ctx.state_set(st);
223 ctx.down(vec![Message::Error(error.into())]);
224 return;
225 }
226
227 if is_complete(ctx.terminal(0)) {
228 st.source_done = true;
229 }
230
231 let mut to_remove = Vec::new();
232 let mut survivors = Vec::new();
233 for (i, inner) in st.inners.iter().enumerate() {
234 if is_complete(ctx.terminal(i + 1)) {
235 push_unique(&mut to_remove, inner.clone());
236 } else {
237 survivors.push(inner.clone());
238 }
239 }
240 st.inners = survivors;
241
242 let mut to_add = Vec::new();
243 let source_batch = ctx.batch::<TIn>(0);
244 if !source_batch.is_empty() {
245 match mode {
246 Mode::Switch => {
247 let latest = source_batch
248 .last()
249 .expect("source_batch is not empty")
250 .as_ref()
251 .clone();
252 let Some(inner) = project_inner(ctx, project, &latest, &mut st, body_cell) else {
253 return;
254 };
255 for live in &st.inners {
256 if !live.ptr_eq(&inner) {
257 push_unique(&mut to_remove, live.clone());
258 }
259 }
260 if contains_core(&to_remove, &inner) {
261 st.inners.clear();
262 } else {
263 if !contains_core(&st.inners, &inner) {
264 to_add.push(inner.clone());
265 }
266 st.inners = vec![inner];
267 }
268 }
269 Mode::Merge => {
270 if concurrent.is_some() {
271 for value in source_batch {
272 st.queue.push_back(value.as_ref().clone());
273 }
274 } else {
275 for value in source_batch {
276 let Some(inner) =
277 project_inner(ctx, project, value.as_ref(), &mut st, body_cell)
278 else {
279 return;
280 };
281 if !contains_core(&st.inners, &inner) && !contains_core(&to_remove, &inner)
282 {
283 st.inners.push(inner.clone());
284 to_add.push(inner);
285 }
286 }
287 }
288 }
289 Mode::Concat => {
290 for value in source_batch {
291 st.queue.push_back(value.as_ref().clone());
292 }
293 }
294 Mode::Exhaust => {
295 if st.inners.is_empty() {
296 let first = source_batch
297 .first()
298 .expect("source_batch is not empty")
299 .as_ref()
300 .clone();
301 let Some(inner) = project_inner(ctx, project, &first, &mut st, body_cell)
302 else {
303 return;
304 };
305 if !contains_core(&to_remove, &inner) {
306 st.inners.push(inner.clone());
307 to_add.push(inner);
308 }
309 }
310 }
311 }
312 }
313
314 if matches!(mode, Mode::Merge) {
315 while !st.queue.is_empty() && concurrent.is_none_or(|max| st.inners.len() < max) {
316 let value = st.queue.pop_front().expect("queue checked as non-empty");
317 let Some(inner) = project_inner(ctx, project, &value, &mut st, body_cell) else {
318 return;
319 };
320 if !contains_core(&st.inners, &inner) && !contains_core(&to_remove, &inner) {
321 st.inners.push(inner.clone());
322 to_add.push(inner);
323 }
324 }
325 }
326
327 if matches!(mode, Mode::Concat) && st.inners.is_empty() && to_add.is_empty() {
328 if let Some(value) = st.queue.pop_front() {
329 let Some(inner) = project_inner(ctx, project, &value, &mut st, body_cell) else {
330 return;
331 };
332 if !contains_core(&to_remove, &inner) {
333 st.inners.push(inner.clone());
334 to_add.push(inner);
335 }
336 }
337 }
338
339 ctx.state_set(st.clone());
340 for dep in to_remove {
341 ctx.rewire_next_unsubscribe_dep(dep, rewire_body(body_cell));
342 }
343 for dep in to_add {
344 ctx.rewire_next_subscribe_dep(dep, rewire_body(body_cell));
345 }
346
347 if st.source_done && st.inners.is_empty() && st.queue.is_empty() {
348 ctx.down(vec![Message::Complete]);
349 }
350}
351
352fn run_repeat_body<T: 'static>(
353 ctx: &Ctx,
354 factory: &Rc<dyn Fn() -> Node<T>>,
355 count: usize,
356 body_cell: &BodyCell,
357) {
358 let mut st = ctx
359 .state_get::<RepeatState>()
360 .map(|v| (*v).clone())
361 .unwrap_or(RepeatState {
362 started: false,
363 round: 0,
364 inner: None,
365 });
366
367 forward_data(ctx, 0);
368
369 if let Some(error) = first_error_terminal(ctx) {
370 if let Some(inner) = st.inner.take() {
371 ctx.rewire_next_unsubscribe_dep(inner, rewire_body(body_cell));
372 }
373 ctx.state_set(st);
374 ctx.down(vec![Message::Error(error.into())]);
375 return;
376 }
377
378 if !st.started {
379 let Some(inner) = make_repeat_inner(ctx, factory) else {
380 return;
381 };
382 st.started = true;
383 st.round = 0;
384 st.inner = Some(inner.clone());
385 ctx.state_set(st);
386 ctx.rewire_next_subscribe_dep(inner, rewire_body(body_cell));
387 return;
388 }
389
390 if st.inner.is_some() && is_complete(ctx.terminal(0)) {
391 let old = st.inner.take().expect("repeat inner was checked as Some");
392 ctx.rewire_next_unsubscribe_dep(old, rewire_body(body_cell));
393 if st.round + 1 < count {
394 st.round += 1;
395 let Some(next) = make_repeat_inner(ctx, factory) else {
396 ctx.state_set(RepeatState {
397 started: true,
398 round: st.round,
399 inner: None,
400 });
401 return;
402 };
403 st.inner = Some(next.clone());
404 ctx.state_set(st);
405 ctx.rewire_next_subscribe_dep(next, rewire_body(body_cell));
406 } else {
407 ctx.state_set(RepeatState {
408 started: true,
409 round: st.round,
410 inner: None,
411 });
412 ctx.down(vec![Message::Complete]);
413 }
414 }
415}
416
417fn make_repeat_inner<T: 'static>(ctx: &Ctx, factory: &Rc<dyn Fn() -> Node<T>>) -> Option<Core> {
418 match catch_unwind(AssertUnwindSafe(|| factory().erased())) {
419 Ok(core) => Some(core),
420 Err(payload) => {
421 ctx.down(vec![Message::Error(panic_payload(payload).into())]);
422 None
423 }
424 }
425}
426
427fn project_inner<TIn: Clone + 'static, TOut: 'static>(
428 ctx: &Ctx,
429 project: &Project<TIn, TOut>,
430 value: &TIn,
431 st: &mut MapState<TIn>,
432 body_cell: &BodyCell,
433) -> Option<Core> {
434 match catch_unwind(AssertUnwindSafe(|| project(ctx, value).erased())) {
435 Ok(core) => Some(core),
436 Err(payload) => {
437 cleanup_all_inners(ctx, st, body_cell);
438 ctx.state_set(st.clone());
439 ctx.down(vec![Message::Error(panic_payload(payload).into())]);
440 None
441 }
442 }
443}
444
445fn cleanup_all_inners<TIn: Clone + 'static>(
446 ctx: &Ctx,
447 st: &mut MapState<TIn>,
448 body_cell: &BodyCell,
449) {
450 let mut seen = Vec::new();
451 for inner in st.inners.drain(..) {
452 if !contains_core(&seen, &inner) {
453 seen.push(inner);
454 }
455 }
456 st.queue.clear();
457 st.source_done = true;
458 for inner in seen {
459 ctx.rewire_next_unsubscribe_dep(inner, rewire_body(body_cell));
460 }
461}
462
463fn rewire_body(body_cell: &BodyCell) -> impl Fn(&Ctx) + 'static {
464 let body_cell = body_cell.clone();
465 move |ctx| {
466 let body = body_cell
467 .borrow()
468 .as_ref()
469 .expect("higher-order body initialized")
470 .clone();
471 body(ctx);
472 }
473}
474
475fn forward_data(ctx: &Ctx, dep: usize) {
476 for wave in ctx
477 .wave_data()
478 .get(dep)
479 .into_iter()
480 .flat_map(|waves| waves.iter())
481 {
482 for item in wave.iter() {
483 if let WaveData::Data(value) = item {
484 ctx.down(vec![Message::Data(value.clone())]);
485 }
486 }
487 }
488}
489
490fn first_error_terminal(ctx: &Ctx) -> Option<String> {
491 for i in 0..ctx.dep_len() {
492 if let Some(DepTerminal::Error(error)) = ctx.terminal(i) {
493 return Some(error.to_string());
494 }
495 }
496 None
497}
498
499fn is_complete(terminal: Option<&DepTerminal>) -> bool {
500 matches!(terminal, Some(DepTerminal::Complete))
501}
502
503fn push_unique(out: &mut Vec<Core>, core: Core) {
504 if !contains_core(out, &core) {
505 out.push(core);
506 }
507}
508
509fn contains_core(haystack: &[Core], needle: &Core) -> bool {
510 haystack.iter().any(|core| core.ptr_eq(needle))
511}
512
513fn panic_payload(payload: Box<dyn std::any::Any + Send>) -> String {
514 if let Some(s) = payload.downcast_ref::<&str>() {
515 (*s).to_owned()
516 } else if let Some(s) = payload.downcast_ref::<String>() {
517 s.clone()
518 } else {
519 "higher-order projector panicked".to_owned()
520 }
521}