Skip to main content

datum/graph/
junctions.rs

1//! Built-in graph stages: the linear helpers (`Identity`, `MapStage`, `Buffer`,
2//! `TakeWhile`), the fan-out/fan-in junctions (`Broadcast`, `Balance`,
3//! `Partition`, `Merge` and its variants, `Concat`, `Interleave`, `OrElse`,
4//! `Zip`, `Unzip`, `UnzipWith`), and the `AsyncBoundary` marker.
5//!
6//! Each type is a [`GraphStage`] blueprint. Simple junctions (`Broadcast`,
7//! `Merge`, `Zip`, …) carry their behavior in a `StageKind` the executor
8//! switches on; stateful ones (`Buffer`, `TakeWhile`, `MergeSorted`,
9//! `MergeSequence`, `MergeLatest`, `Partition`, `Unzip`) provide a full
10//! `GraphStageLogic` via `create_logic`, which is the erased-executor path. Some
11//! also surface typed metadata (`TypedCyclicOp`, or the `typed_*` fns inside
12//! `StageKind`) so the typed executor kernels can run them without boxing. See
13//! `docs/guides/working-with-graphs.md` for the per-junction semantics table.
14
15use super::*;
16use crate::ScalarArithmeticOp;
17use crate::stream::scalar::{ScalarChunkStep, ScalarI64Op};
18
19/// Pass elements through unchanged (one inlet, one outlet). Akka's `identity`.
20#[derive(Clone, Debug)]
21pub struct Identity<T: 'static> {
22    _marker: PhantomData<fn() -> T>,
23}
24
25impl<T: 'static> Identity<T> {
26    #[must_use]
27    pub fn new() -> Self {
28        Self {
29            _marker: PhantomData,
30        }
31    }
32}
33
34impl<T: 'static> Default for Identity<T> {
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40impl<T> GraphStage for Identity<T>
41where
42    T: Clone + Send + 'static,
43{
44    type Shape = FlowShape<T, T>;
45
46    fn name(&self) -> &str {
47        "Identity"
48    }
49
50    fn allocate_shape(&self, _allocator: &mut PortAllocator) -> Self::Shape {
51        let first_id = next_port_id_block(2);
52        FlowShape::new(
53            Inlet::with_arc_name(first_id, identity_inlet_name()),
54            Outlet::with_arc_name(first_id.offset(1), identity_outlet_name()),
55        )
56    }
57
58    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
59        StageSpec::identity(shape.inlets(), shape.outlets())
60    }
61
62    fn stage_spec_with_ports(
63        &self,
64        _shape: &Self::Shape,
65        inlets: Vec<AnyInlet>,
66        outlets: Vec<AnyOutlet>,
67    ) -> StageSpec {
68        StageSpec::identity(inlets, outlets)
69    }
70}
71
72/// Apply `f` to each element, mapping `In` to `Out` (one inlet, one outlet).
73#[derive(Clone)]
74pub struct MapStage<In: 'static, Out: 'static> {
75    f: Arc<dyn Fn(In) -> Out + Send + Sync>,
76    scalar_erased: Option<Arc<StageMapFn>>,
77    scalar_i64: Option<Arc<StageTypedMapFn>>,
78    scalar_finish: Option<Arc<StageTypedMapFn>>,
79    _marker: PhantomData<fn(In) -> Out>,
80}
81
82impl<In: 'static, Out: 'static> fmt::Debug for MapStage<In, Out> {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        f.debug_struct("MapStage")
85            .field("name", &"Map")
86            .finish_non_exhaustive()
87    }
88}
89
90impl<In: 'static, Out: 'static> MapStage<In, Out> {
91    #[must_use]
92    pub fn new<F>(f: F) -> Self
93    where
94        F: Fn(In) -> Out + Send + Sync + 'static,
95    {
96        Self {
97            f: Arc::new(f),
98            scalar_erased: None,
99            scalar_i64: None,
100            scalar_finish: None,
101            _marker: PhantomData,
102        }
103    }
104}
105
106impl MapStage<i64, i64> {
107    fn scalar(op: ScalarArithmeticOp, operand: i64) -> Self {
108        let step = ScalarChunkStep::new(ScalarI64Op::Arithmetic(op), operand);
109        let checked_step = step.clone();
110        let scalar_erased: Arc<StageMapFn> = Arc::new(move |value| {
111            let value: i64 = downcast_datum(value, "map", || "Map.in")?;
112            let value = checked_step
113                .apply_checked(value)?
114                .expect("arithmetic maps always retain their input");
115            Ok(datum(value))
116        });
117        let scalar_i64 = Arc::new(step) as Arc<StageTypedMapFn>;
118        let finish: Arc<dyn Fn(i64) -> i64 + Send + Sync> = Arc::new(|value| value);
119        let scalar_finish = Arc::new(finish) as Arc<StageTypedMapFn>;
120        let f: Arc<dyn Fn(i64) -> i64 + Send + Sync> = Arc::new(move |value| match op {
121            ScalarArithmeticOp::Add => value.wrapping_add(operand),
122            ScalarArithmeticOp::Subtract => value.wrapping_sub(operand),
123            ScalarArithmeticOp::Multiply => value.wrapping_mul(operand),
124        });
125        Self {
126            f,
127            scalar_erased: Some(scalar_erased),
128            scalar_i64: Some(scalar_i64),
129            scalar_finish: Some(scalar_finish),
130            _marker: PhantomData,
131        }
132    }
133
134    /// Construct a planner-visible checked integer map stage.
135    #[must_use]
136    pub fn map_op(op: ScalarArithmeticOp, operand: i64) -> Self {
137        Self::scalar(op, operand)
138    }
139
140    #[must_use]
141    pub fn map_add(operand: i64) -> Self {
142        Self::scalar(ScalarArithmeticOp::Add, operand)
143    }
144
145    #[must_use]
146    pub fn map_subtract(operand: i64) -> Self {
147        Self::scalar(ScalarArithmeticOp::Subtract, operand)
148    }
149
150    #[must_use]
151    pub fn map_multiply(operand: i64) -> Self {
152        Self::scalar(ScalarArithmeticOp::Multiply, operand)
153    }
154}
155
156impl<In, Out> GraphStage for MapStage<In, Out>
157where
158    In: Clone + Send + 'static,
159    Out: Clone + Send + 'static,
160{
161    type Shape = FlowShape<In, Out>;
162
163    fn name(&self) -> &str {
164        "Map"
165    }
166
167    fn allocate_shape(&self, _allocator: &mut PortAllocator) -> Self::Shape {
168        let first_id = next_port_id_block(2);
169        FlowShape::new(
170            Inlet::with_arc_name(first_id, map_inlet_name()),
171            Outlet::with_arc_name(first_id.offset(1), map_outlet_name()),
172        )
173    }
174
175    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
176        self.stage_spec_with_ports(shape, shape.inlets(), shape.outlets())
177    }
178
179    fn stage_spec_with_ports(
180        &self,
181        _shape: &Self::Shape,
182        inlets: Vec<AnyInlet>,
183        outlets: Vec<AnyOutlet>,
184    ) -> StageSpec {
185        let f = Arc::clone(&self.f);
186        let typed = Arc::new(Arc::clone(&self.f)) as Arc<StageTypedMapFn>;
187        let mapper = self.scalar_erased.clone().unwrap_or_else(|| {
188            Arc::new(move |value: DatumValue| {
189                let value: In = downcast_datum(value, "map", || "Map.in")?;
190                Ok(datum(f(value)))
191            })
192        });
193        StageSpec::map(
194            map_stage_name(),
195            inlets,
196            outlets,
197            mapper,
198            typed,
199            self.scalar_i64.clone(),
200            self.scalar_finish.clone(),
201        )
202    }
203}
204
205/// A bounded in-stage buffer of `capacity` elements, applying `strategy` on
206/// overflow. In the synchronous fused executor it drains one element per step so
207/// it never overflows (a pass-through on the typed cyclic path); the erased
208/// executor runs the full buffering logic.
209#[derive(Clone, Debug)]
210pub struct Buffer<T: 'static> {
211    capacity: usize,
212    strategy: OverflowStrategy,
213    _marker: PhantomData<fn() -> T>,
214}
215
216impl<T: 'static> Buffer<T> {
217    #[must_use]
218    pub fn new(capacity: usize, strategy: OverflowStrategy) -> Self {
219        assert!(capacity > 0, "buffer capacity must be greater than zero");
220        Self {
221            capacity,
222            strategy,
223            _marker: PhantomData,
224        }
225    }
226}
227
228impl<T> GraphStage for Buffer<T>
229where
230    T: Clone + Send + 'static,
231{
232    type Shape = FlowShape<T, T>;
233
234    fn name(&self) -> &str {
235        "Buffer"
236    }
237
238    fn allocate_shape(&self, _allocator: &mut PortAllocator) -> Self::Shape {
239        let first_id = next_port_id_block(2);
240        FlowShape::new(
241            Inlet::with_id(first_id, "Buffer.in"),
242            Outlet::with_id(first_id.offset(1), "Buffer.out"),
243        )
244    }
245
246    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
247        // In the synchronous fused executor a buffer drains one element per step
248        // and never overflows, so it is a pass-through on the typed cyclic path.
249        // The erased executor ignores this and still runs the full buffer logic.
250        StageSpec::opaque(self.name(), shape.inlets(), shape.outlets())
251            .with_typed_cyclic(TypedCyclicOp::BufferPassthrough)
252    }
253
254    fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
255        struct State<T> {
256            queue: VecDeque<T>,
257            upstream_closed: bool,
258        }
259
260        fn lock_state<T>(
261            state: &Arc<Mutex<State<T>>>,
262        ) -> StreamResult<std::sync::MutexGuard<'_, State<T>>> {
263            state
264                .lock()
265                .map_err(|_| StreamError::Failed("graph buffer state poisoned".into()))
266        }
267
268        fn enqueue<T>(
269            state: &mut State<T>,
270            value: T,
271            capacity: usize,
272            strategy: OverflowStrategy,
273        ) -> StreamResult<()> {
274            if state.queue.len() < capacity {
275                state.queue.push_back(value);
276                return Ok(());
277            }
278
279            match strategy {
280                OverflowStrategy::Backpressure | OverflowStrategy::Fail => Err(
281                    StreamError::Failed(format!("Buffer overflow (max capacity was: {capacity})!")),
282                ),
283                OverflowStrategy::DropHead => {
284                    let _ = state.queue.pop_front();
285                    state.queue.push_back(value);
286                    Ok(())
287                }
288                OverflowStrategy::DropTail => {
289                    let _ = state.queue.pop_back();
290                    state.queue.push_back(value);
291                    Ok(())
292                }
293                OverflowStrategy::DropBuffer => {
294                    state.queue.clear();
295                    state.queue.push_back(value);
296                    Ok(())
297                }
298                OverflowStrategy::DropNew => Ok(()),
299            }
300        }
301
302        fn drain_one<T>(
303            logic: &mut GraphStageLogic,
304            outlet: &Outlet<T>,
305            state: &Arc<Mutex<State<T>>>,
306        ) -> StreamResult<()>
307        where
308            T: Clone + Send + 'static,
309        {
310            if !logic.is_available(outlet) {
311                return Ok(());
312            }
313            let next = lock_state(state)?.queue.pop_front();
314            if let Some(value) = next {
315                logic.push(outlet, value)?;
316            }
317            Ok(())
318        }
319
320        fn maybe_pull<T>(
321            logic: &mut GraphStageLogic,
322            inlet: &Inlet<T>,
323            state: &Arc<Mutex<State<T>>>,
324            capacity: usize,
325        ) -> StreamResult<()>
326        where
327            T: 'static,
328        {
329            let can_pull = {
330                let state = lock_state(state)?;
331                !state.upstream_closed && state.queue.len() < capacity
332            };
333            if can_pull && !logic.has_been_pulled(inlet) {
334                logic.pull(inlet)?;
335            }
336            Ok(())
337        }
338
339        fn maybe_complete<T>(
340            logic: &mut GraphStageLogic,
341            outlet: &Outlet<T>,
342            state: &Arc<Mutex<State<T>>>,
343        ) -> StreamResult<()>
344        where
345            T: 'static,
346        {
347            let done = {
348                let state = lock_state(state)?;
349                state.upstream_closed && state.queue.is_empty()
350            };
351            if done && !logic.is_closed(outlet) {
352                logic.complete(outlet)?;
353            }
354            Ok(())
355        }
356
357        struct In<T: 'static> {
358            inlet: Inlet<T>,
359            outlet: Outlet<T>,
360            state: Arc<Mutex<State<T>>>,
361            capacity: usize,
362            strategy: OverflowStrategy,
363        }
364
365        impl<T> InHandler for In<T>
366        where
367            T: Clone + Send + 'static,
368        {
369            fn on_push(
370                &mut self,
371                logic: &mut GraphStageLogic,
372                _inlet: AnyInlet,
373            ) -> StreamResult<()> {
374                let value = logic.grab(&self.inlet)?;
375                {
376                    let mut state = lock_state(&self.state)?;
377                    enqueue(&mut state, value, self.capacity, self.strategy)?;
378                }
379                drain_one(logic, &self.outlet, &self.state)?;
380                maybe_pull(logic, &self.inlet, &self.state, self.capacity)?;
381                maybe_complete(logic, &self.outlet, &self.state)
382            }
383
384            fn on_upstream_finish(
385                &mut self,
386                logic: &mut GraphStageLogic,
387                _inlet: AnyInlet,
388            ) -> StreamResult<()> {
389                lock_state(&self.state)?.upstream_closed = true;
390                drain_one(logic, &self.outlet, &self.state)?;
391                maybe_complete(logic, &self.outlet, &self.state)
392            }
393        }
394
395        struct Out<T: 'static> {
396            inlet: Inlet<T>,
397            outlet: Outlet<T>,
398            state: Arc<Mutex<State<T>>>,
399            capacity: usize,
400        }
401
402        impl<T> OutHandler for Out<T>
403        where
404            T: Clone + Send + 'static,
405        {
406            fn on_pull(
407                &mut self,
408                logic: &mut GraphStageLogic,
409                _outlet: AnyOutlet,
410            ) -> StreamResult<()> {
411                drain_one(logic, &self.outlet, &self.state)?;
412                maybe_pull(logic, &self.inlet, &self.state, self.capacity)?;
413                maybe_complete(logic, &self.outlet, &self.state)
414            }
415
416            fn on_downstream_finish(
417                &mut self,
418                logic: &mut GraphStageLogic,
419                _outlet: AnyOutlet,
420            ) -> StreamResult<()> {
421                logic.complete_stage()
422            }
423        }
424
425        let state = Arc::new(Mutex::new(State {
426            queue: VecDeque::new(),
427            upstream_closed: false,
428        }));
429        let mut logic = GraphStageLogic::new(shape);
430        logic.pull(&shape.inlet()).unwrap();
431        logic
432            .set_handler(
433                &shape.inlet(),
434                Box::new(In {
435                    inlet: shape.inlet(),
436                    outlet: shape.outlet(),
437                    state: Arc::clone(&state),
438                    capacity: self.capacity,
439                    strategy: self.strategy,
440                }),
441            )
442            .unwrap();
443        logic
444            .set_out_handler(
445                &shape.outlet(),
446                Box::new(Out {
447                    inlet: shape.inlet(),
448                    outlet: shape.outlet(),
449                    state,
450                    capacity: self.capacity,
451                }),
452            )
453            .unwrap();
454        logic
455    }
456}
457
458/// Emit elements while `predicate` holds, then complete (one inlet, one outlet).
459#[derive(Clone)]
460pub struct TakeWhile<T: 'static> {
461    predicate: Arc<dyn Fn(&T) -> bool + Send + Sync>,
462    _marker: PhantomData<fn() -> T>,
463}
464
465impl<T: 'static> fmt::Debug for TakeWhile<T> {
466    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
467        f.debug_struct("TakeWhile")
468            .field("name", &"TakeWhile")
469            .finish_non_exhaustive()
470    }
471}
472
473impl<T: 'static> TakeWhile<T> {
474    #[must_use]
475    pub fn new<F>(predicate: F) -> Self
476    where
477        F: Fn(&T) -> bool + Send + Sync + 'static,
478    {
479        Self {
480            predicate: Arc::new(predicate),
481            _marker: PhantomData,
482        }
483    }
484}
485
486impl<T> GraphStage for TakeWhile<T>
487where
488    T: Clone + Send + 'static,
489{
490    type Shape = FlowShape<T, T>;
491
492    fn name(&self) -> &str {
493        "TakeWhile"
494    }
495
496    fn allocate_shape(&self, _allocator: &mut PortAllocator) -> Self::Shape {
497        let first_id = next_port_id_block(2);
498        FlowShape::new(
499            Inlet::with_id(first_id, "TakeWhile.in"),
500            Outlet::with_id(first_id.offset(1), "TakeWhile.out"),
501        )
502    }
503
504    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
505        // Surface the typed predicate so the cyclic typed kernel can apply it
506        // directly (no `DatumValue` boxing). Stored as `Arc<dyn Any>` and
507        // down-cast at plan time, mirroring the typed Map fn. The erased path is
508        // unaffected — it keeps running the `GraphStageLogic` below.
509        let predicate: Arc<dyn Fn(&T) -> bool + Send + Sync> = Arc::clone(&self.predicate);
510        StageSpec::opaque(self.name(), shape.inlets(), shape.outlets()).with_typed_cyclic(
511            TypedCyclicOp::TakeWhile(Arc::new(predicate) as Arc<dyn Any + Send + Sync>),
512        )
513    }
514
515    fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
516        struct In<T: 'static> {
517            inlet: Inlet<T>,
518            outlet: Outlet<T>,
519            predicate: Arc<dyn Fn(&T) -> bool + Send + Sync>,
520        }
521
522        impl<T> InHandler for In<T>
523        where
524            T: Clone + Send + 'static,
525        {
526            fn on_push(
527                &mut self,
528                logic: &mut GraphStageLogic,
529                _inlet: AnyInlet,
530            ) -> StreamResult<()> {
531                let value = logic.grab(&self.inlet)?;
532                if (self.predicate)(&value) {
533                    logic.emit(&self.outlet, value)
534                } else if logic.is_closed(&self.outlet) {
535                    Ok(())
536                } else {
537                    logic.complete(&self.outlet)
538                }
539            }
540
541            fn on_upstream_finish(
542                &mut self,
543                logic: &mut GraphStageLogic,
544                _inlet: AnyInlet,
545            ) -> StreamResult<()> {
546                if logic.is_closed(&self.outlet) {
547                    Ok(())
548                } else {
549                    logic.complete(&self.outlet)
550                }
551            }
552        }
553
554        struct Out<T: 'static> {
555            inlet: Inlet<T>,
556        }
557
558        impl<T> OutHandler for Out<T>
559        where
560            T: Clone + Send + 'static,
561        {
562            fn on_pull(
563                &mut self,
564                logic: &mut GraphStageLogic,
565                _outlet: AnyOutlet,
566            ) -> StreamResult<()> {
567                if !logic.has_been_pulled(&self.inlet) {
568                    logic.pull(&self.inlet)?;
569                }
570                Ok(())
571            }
572
573            fn on_downstream_finish(
574                &mut self,
575                logic: &mut GraphStageLogic,
576                _outlet: AnyOutlet,
577            ) -> StreamResult<()> {
578                logic.complete_stage()
579            }
580        }
581
582        let mut logic = GraphStageLogic::new(shape);
583        logic
584            .set_handler(
585                &shape.inlet(),
586                Box::new(In {
587                    inlet: shape.inlet(),
588                    outlet: shape.outlet(),
589                    predicate: Arc::clone(&self.predicate),
590                }),
591            )
592            .unwrap();
593        logic
594            .set_out_handler(
595                &shape.outlet(),
596                Box::new(Out {
597                    inlet: shape.inlet(),
598                }),
599            )
600            .unwrap();
601        logic
602    }
603}
604
605/// Fan out: push each element to all `outputs` outlets (one inlet, N outlets).
606#[derive(Clone, Debug)]
607pub struct Broadcast<T: 'static> {
608    outputs: usize,
609    _marker: PhantomData<fn() -> T>,
610}
611
612impl<T: 'static> Broadcast<T> {
613    #[must_use]
614    pub fn new(outputs: usize) -> Self {
615        assert!(
616            outputs > 0,
617            "broadcast output count must be greater than zero"
618        );
619        Self {
620            outputs,
621            _marker: PhantomData,
622        }
623    }
624}
625
626impl<T> GraphStage for Broadcast<T>
627where
628    T: Clone + Send + 'static,
629{
630    type Shape = FanOutShape<T, T>;
631
632    fn name(&self) -> &str {
633        "Broadcast"
634    }
635
636    fn allocate_shape(&self, allocator: &mut PortAllocator) -> Self::Shape {
637        let inlet = allocator.inlet_arc(broadcast_inlet_name());
638        let outlets = (0..self.outputs)
639            .map(|index| allocator.outlet(format!("Broadcast.out{index}")))
640            .collect();
641        FanOutShape::new(inlet, outlets)
642    }
643
644    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
645        self.stage_spec_with_ports(shape, shape.inlets(), shape.outlets())
646    }
647
648    fn stage_spec_with_ports(
649        &self,
650        _shape: &Self::Shape,
651        inlets: Vec<AnyInlet>,
652        outlets: Vec<AnyOutlet>,
653    ) -> StageSpec {
654        StageSpec::broadcast(broadcast_stage_name(), inlets, outlets)
655    }
656}
657
658/// Fan out: route each element to one of `outputs` outlets, round-robin by
659/// demand (one inlet, N outlets).
660#[derive(Clone, Debug)]
661pub struct Balance<T: 'static> {
662    outputs: usize,
663    _marker: PhantomData<fn() -> T>,
664}
665
666impl<T: 'static> Balance<T> {
667    #[must_use]
668    pub fn new(outputs: usize) -> Self {
669        assert!(
670            outputs > 0,
671            "balance output count must be greater than zero"
672        );
673        Self {
674            outputs,
675            _marker: PhantomData,
676        }
677    }
678}
679
680impl<T> GraphStage for Balance<T>
681where
682    T: Clone + Send + 'static,
683{
684    type Shape = FanOutShape<T, T>;
685
686    fn name(&self) -> &str {
687        "Balance"
688    }
689
690    fn allocate_shape(&self, allocator: &mut PortAllocator) -> Self::Shape {
691        let inlet = allocator.inlet_arc(balance_inlet_name());
692        let outlets = (0..self.outputs)
693            .map(|index| allocator.outlet(format!("Balance.out{index}")))
694            .collect();
695        FanOutShape::new(inlet, outlets)
696    }
697
698    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
699        self.stage_spec_with_ports(shape, shape.inlets(), shape.outlets())
700    }
701
702    fn stage_spec_with_ports(
703        &self,
704        _shape: &Self::Shape,
705        inlets: Vec<AnyInlet>,
706        outlets: Vec<AnyOutlet>,
707    ) -> StageSpec {
708        StageSpec::balance(balance_stage_name(), inlets, outlets)
709    }
710}
711
712/// Fan in: emit from whichever of `inputs` inlets has an element available
713/// (N inlets, one outlet). Order across inlets is not guaranteed.
714#[derive(Clone, Debug)]
715pub struct Merge<T: 'static> {
716    inputs: usize,
717    _marker: PhantomData<fn() -> T>,
718}
719
720impl<T: 'static> Merge<T> {
721    #[must_use]
722    pub fn new(inputs: usize) -> Self {
723        assert!(inputs > 0, "merge input count must be greater than zero");
724        Self {
725            inputs,
726            _marker: PhantomData,
727        }
728    }
729}
730
731impl<T> GraphStage for Merge<T>
732where
733    T: Clone + Send + 'static,
734{
735    type Shape = FanInShape<T, T>;
736
737    fn name(&self) -> &str {
738        "Merge"
739    }
740
741    fn allocate_shape(&self, allocator: &mut PortAllocator) -> Self::Shape {
742        let inlets = (0..self.inputs)
743            .map(|index| allocator.inlet(format!("Merge.in{index}")))
744            .collect();
745        FanInShape::new(inlets, allocator.outlet_arc(merge_outlet_name()))
746    }
747
748    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
749        self.stage_spec_with_ports(shape, shape.inlets(), shape.outlets())
750    }
751
752    fn stage_spec_with_ports(
753        &self,
754        _shape: &Self::Shape,
755        inlets: Vec<AnyInlet>,
756        outlets: Vec<AnyOutlet>,
757    ) -> StageSpec {
758        StageSpec::merge(merge_stage_name(), inlets, outlets)
759    }
760}
761
762/// Fan in: fully drain each inlet in declared order before the next
763/// (N inlets, one outlet).
764#[derive(Clone, Debug)]
765pub struct Concat<T: 'static> {
766    inputs: usize,
767    _marker: PhantomData<fn() -> T>,
768}
769
770impl<T: 'static> Concat<T> {
771    #[must_use]
772    pub fn new(inputs: usize) -> Self {
773        assert!(inputs > 1, "concat input count must be greater than one");
774        Self {
775            inputs,
776            _marker: PhantomData,
777        }
778    }
779}
780
781impl<T> GraphStage for Concat<T>
782where
783    T: Clone + Send + 'static,
784{
785    type Shape = FanInShape<T, T>;
786
787    fn name(&self) -> &str {
788        "Concat"
789    }
790
791    fn allocate_shape(&self, allocator: &mut PortAllocator) -> Self::Shape {
792        let inlets = (0..self.inputs)
793            .map(|index| allocator.inlet(format!("Concat.in{index}")))
794            .collect();
795        FanInShape::new(inlets, allocator.outlet_arc(concat_outlet_name()))
796    }
797
798    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
799        self.stage_spec_with_ports(shape, shape.inlets(), shape.outlets())
800    }
801
802    fn stage_spec_with_ports(
803        &self,
804        _shape: &Self::Shape,
805        inlets: Vec<AnyInlet>,
806        outlets: Vec<AnyOutlet>,
807    ) -> StageSpec {
808        StageSpec::concat(concat_stage_name(), inlets, outlets)
809    }
810}
811
812/// Fan in: emit the primary inlet; fall back to the secondary only if the
813/// primary completes without emitting (2 inlets, one outlet).
814#[derive(Clone, Debug)]
815pub struct OrElse<T: 'static> {
816    _marker: PhantomData<fn() -> T>,
817}
818
819impl<T: 'static> OrElse<T> {
820    #[must_use]
821    pub fn new() -> Self {
822        Self {
823            _marker: PhantomData,
824        }
825    }
826}
827
828impl<T: 'static> Default for OrElse<T> {
829    fn default() -> Self {
830        Self::new()
831    }
832}
833
834impl<T> GraphStage for OrElse<T>
835where
836    T: Clone + Send + 'static,
837{
838    type Shape = FanInShape<T, T>;
839
840    fn name(&self) -> &str {
841        "OrElse"
842    }
843
844    fn allocate_shape(&self, allocator: &mut PortAllocator) -> Self::Shape {
845        let inlets = vec![
846            allocator.inlet_arc(or_else_primary_name()),
847            allocator.inlet_arc(or_else_secondary_name()),
848        ];
849        FanInShape::new(inlets, allocator.outlet_arc(or_else_outlet_name()))
850    }
851
852    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
853        self.stage_spec_with_ports(shape, shape.inlets(), shape.outlets())
854    }
855
856    fn stage_spec_with_ports(
857        &self,
858        _shape: &Self::Shape,
859        inlets: Vec<AnyInlet>,
860        outlets: Vec<AnyOutlet>,
861    ) -> StageSpec {
862        StageSpec::or_else(or_else_stage_name(), inlets, outlets)
863    }
864}
865
866/// Fan in: round-robin across `inputs` inlets in chunks of `segment_size`
867/// (N inlets, one outlet). `eager_close` stops when any inlet completes.
868#[derive(Clone, Debug)]
869pub struct Interleave<T: 'static> {
870    inputs: usize,
871    segment_size: usize,
872    eager_close: bool,
873    _marker: PhantomData<fn() -> T>,
874}
875
876impl<T: 'static> Interleave<T> {
877    #[must_use]
878    pub fn new(inputs: usize, segment_size: usize) -> Self {
879        Self::new_with_eager_close(inputs, segment_size, false)
880    }
881
882    #[must_use]
883    pub fn new_with_eager_close(inputs: usize, segment_size: usize, eager_close: bool) -> Self {
884        assert!(
885            inputs > 1,
886            "interleave input count must be greater than one"
887        );
888        assert!(
889            segment_size > 0,
890            "interleave segment size must be greater than zero"
891        );
892        Self {
893            inputs,
894            segment_size,
895            eager_close,
896            _marker: PhantomData,
897        }
898    }
899}
900
901impl<T> GraphStage for Interleave<T>
902where
903    T: Clone + Send + 'static,
904{
905    type Shape = FanInShape<T, T>;
906
907    fn name(&self) -> &str {
908        "Interleave"
909    }
910
911    fn allocate_shape(&self, allocator: &mut PortAllocator) -> Self::Shape {
912        let inlets = (0..self.inputs)
913            .map(|index| allocator.inlet(format!("Interleave.in{index}")))
914            .collect();
915        FanInShape::new(inlets, allocator.outlet_arc(interleave_outlet_name()))
916    }
917
918    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
919        self.stage_spec_with_ports(shape, shape.inlets(), shape.outlets())
920    }
921
922    fn stage_spec_with_ports(
923        &self,
924        _shape: &Self::Shape,
925        inlets: Vec<AnyInlet>,
926        outlets: Vec<AnyOutlet>,
927    ) -> StageSpec {
928        StageSpec::interleave(
929            interleave_stage_name(),
930            inlets,
931            outlets,
932            self.segment_size,
933            self.eager_close,
934        )
935    }
936}
937
938/// Fan in: always drain the single preferred inlet ahead of the
939/// `secondary_ports` secondaries (1 preferred + N secondary inlets, one outlet).
940#[derive(Clone, Debug)]
941pub struct MergePreferred<T: 'static> {
942    secondary_ports: usize,
943    _marker: PhantomData<fn() -> T>,
944}
945
946impl<T: 'static> MergePreferred<T> {
947    #[must_use]
948    pub fn new(secondary_ports: usize) -> Self {
949        assert!(
950            secondary_ports > 0,
951            "merge-preferred secondary input count must be greater than zero"
952        );
953        Self {
954            secondary_ports,
955            _marker: PhantomData,
956        }
957    }
958}
959
960impl<T> GraphStage for MergePreferred<T>
961where
962    T: Clone + Send + 'static,
963{
964    type Shape = MergePreferredShape<T>;
965
966    fn name(&self) -> &str {
967        "MergePreferred"
968    }
969
970    fn allocate_shape(&self, allocator: &mut PortAllocator) -> Self::Shape {
971        let preferred = allocator.inlet_arc(merge_preferred_preferred_name());
972        let secondary = (0..self.secondary_ports)
973            .map(|index| allocator.inlet(format!("MergePreferred.in{index}")))
974            .collect();
975        MergePreferredShape::new(
976            preferred,
977            secondary,
978            allocator.outlet_arc(merge_preferred_outlet_name()),
979        )
980    }
981
982    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
983        self.stage_spec_with_ports(shape, shape.inlets(), shape.outlets())
984    }
985
986    fn stage_spec_with_ports(
987        &self,
988        _shape: &Self::Shape,
989        inlets: Vec<AnyInlet>,
990        outlets: Vec<AnyOutlet>,
991    ) -> StageSpec {
992        StageSpec::merge_preferred(merge_preferred_stage_name(), inlets, outlets)
993    }
994}
995
996/// Fan in: emit on a deterministic weighted schedule — inlet `i` is drained
997/// `weights[i]` times per cycle (N inlets, one outlet). Unlike Akka's
998/// `MergePrioritized`, the schedule is fixed, not randomized.
999#[derive(Clone, Debug)]
1000pub struct MergePrioritized<T: 'static> {
1001    weights: Vec<usize>,
1002    _marker: PhantomData<fn() -> T>,
1003}
1004
1005impl<T: 'static> MergePrioritized<T> {
1006    #[must_use]
1007    pub fn new(weights: Vec<usize>) -> Self {
1008        assert!(!weights.is_empty(), "prioritized merge must have inputs");
1009        assert!(
1010            weights.iter().all(|weight| *weight > 0),
1011            "prioritized merge weights must be greater than zero"
1012        );
1013        Self {
1014            weights,
1015            _marker: PhantomData,
1016        }
1017    }
1018}
1019
1020impl<T> GraphStage for MergePrioritized<T>
1021where
1022    T: Clone + Send + 'static,
1023{
1024    type Shape = FanInShape<T, T>;
1025
1026    fn name(&self) -> &str {
1027        "MergePrioritized"
1028    }
1029
1030    fn allocate_shape(&self, allocator: &mut PortAllocator) -> Self::Shape {
1031        let inlets = (0..self.weights.len())
1032            .map(|index| allocator.inlet(format!("MergePrioritized.in{index}")))
1033            .collect();
1034        FanInShape::new(
1035            inlets,
1036            allocator.outlet_arc(merge_prioritized_outlet_name()),
1037        )
1038    }
1039
1040    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
1041        self.stage_spec_with_ports(shape, shape.inlets(), shape.outlets())
1042    }
1043
1044    fn stage_spec_with_ports(
1045        &self,
1046        _shape: &Self::Shape,
1047        inlets: Vec<AnyInlet>,
1048        outlets: Vec<AnyOutlet>,
1049    ) -> StageSpec {
1050        StageSpec::merge_prioritized(
1051            merge_prioritized_stage_name(),
1052            inlets,
1053            outlets,
1054            self.weights.clone(),
1055        )
1056    }
1057}
1058
1059/// Fan in: pair one element from each inlet into `(Left, Right)`; completes when
1060/// either inlet completes (2 inlets, one outlet).
1061#[derive(Clone, Debug)]
1062pub struct Zip<Left: 'static, Right: 'static> {
1063    _marker: PhantomData<fn() -> (Left, Right)>,
1064}
1065
1066impl<Left: 'static, Right: 'static> Zip<Left, Right> {
1067    #[must_use]
1068    pub fn new() -> Self {
1069        Self {
1070            _marker: PhantomData,
1071        }
1072    }
1073}
1074
1075impl<Left: 'static, Right: 'static> Default for Zip<Left, Right> {
1076    fn default() -> Self {
1077        Self::new()
1078    }
1079}
1080
1081impl<Left, Right> GraphStage for Zip<Left, Right>
1082where
1083    Left: Clone + Send + 'static,
1084    Right: Clone + Send + 'static,
1085{
1086    type Shape = ZipShape<Left, Right>;
1087
1088    fn name(&self) -> &str {
1089        "Zip"
1090    }
1091
1092    fn allocate_shape(&self, _allocator: &mut PortAllocator) -> Self::Shape {
1093        let first_id = next_port_id_block(3);
1094        ZipShape::new(
1095            Inlet::with_arc_name(first_id, zip_in0_name()),
1096            Inlet::with_arc_name(first_id.offset(1), zip_in1_name()),
1097            Outlet::with_arc_name(first_id.offset(2), zip_outlet_name()),
1098        )
1099    }
1100
1101    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
1102        self.stage_spec_with_ports(shape, shape.inlets(), shape.outlets())
1103    }
1104
1105    fn stage_spec_with_ports(
1106        &self,
1107        _shape: &Self::Shape,
1108        inlets: Vec<AnyInlet>,
1109        outlets: Vec<AnyOutlet>,
1110    ) -> StageSpec {
1111        let zip = Arc::new(move |left: DatumValue, right: DatumValue| {
1112            let left: Left = downcast_datum(left, "zip", || "Zip.in0")?;
1113            let right: Right = downcast_datum(right, "zip", || "Zip.in1")?;
1114            Ok(datum((left, right)))
1115        });
1116        StageSpec::zip(zip_stage_name(), inlets, outlets, zip)
1117    }
1118}
1119
1120/// Fan in: merge two already-sorted inlets into one sorted outlet (2 inlets, one
1121/// outlet). Requires `T: Ord`.
1122#[derive(Clone, Debug)]
1123pub struct MergeSorted<T: 'static> {
1124    _marker: PhantomData<fn() -> T>,
1125}
1126
1127impl<T: 'static> MergeSorted<T> {
1128    #[must_use]
1129    pub fn new() -> Self {
1130        Self {
1131            _marker: PhantomData,
1132        }
1133    }
1134}
1135
1136impl<T: 'static> Default for MergeSorted<T> {
1137    fn default() -> Self {
1138        Self::new()
1139    }
1140}
1141
1142impl<T> GraphStage for MergeSorted<T>
1143where
1144    T: Clone + Ord + Send + 'static,
1145{
1146    type Shape = FanInShape<T, T>;
1147
1148    fn name(&self) -> &str {
1149        "MergeSorted"
1150    }
1151
1152    fn allocate_shape(&self, allocator: &mut PortAllocator) -> Self::Shape {
1153        let inlets = vec![
1154            allocator.inlet("MergeSorted.in0"),
1155            allocator.inlet("MergeSorted.in1"),
1156        ];
1157        FanInShape::new(inlets, allocator.outlet("MergeSorted.out"))
1158    }
1159
1160    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
1161        let compare = Arc::new(
1162            move |a: &DatumValue, b: &DatumValue| -> std::cmp::Ordering {
1163                let a_t: &T = a
1164                    .as_any_ref()
1165                    .downcast_ref::<T>()
1166                    .expect("merge-sorted compare: wrong element type");
1167                let b_t: &T = b
1168                    .as_any_ref()
1169                    .downcast_ref::<T>()
1170                    .expect("merge-sorted compare: wrong element type");
1171                a_t.cmp(b_t)
1172            },
1173        );
1174        StageSpec::merge_sorted(
1175            Arc::from(self.name()),
1176            shape.inlets(),
1177            shape.outlets(),
1178            compare,
1179        )
1180    }
1181
1182    fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
1183        struct State<T> {
1184            left: VecDeque<T>,
1185            right: VecDeque<T>,
1186            left_closed: bool,
1187            right_closed: bool,
1188            pending: VecDeque<T>,
1189        }
1190
1191        impl<T> Default for State<T> {
1192            fn default() -> Self {
1193                Self {
1194                    left: VecDeque::new(),
1195                    right: VecDeque::new(),
1196                    left_closed: false,
1197                    right_closed: false,
1198                    pending: VecDeque::new(),
1199                }
1200            }
1201        }
1202
1203        fn maybe_queue_output<T>(state: &mut State<T>) -> bool
1204        where
1205            T: Clone + Ord,
1206        {
1207            let next = match (state.left.front(), state.right.front()) {
1208                (Some(left), Some(right)) => {
1209                    if left <= right {
1210                        state.left.pop_front()
1211                    } else {
1212                        state.right.pop_front()
1213                    }
1214                }
1215                (Some(_), None) if state.right_closed => state.left.pop_front(),
1216                (None, Some(_)) if state.left_closed => state.right.pop_front(),
1217                _ => None,
1218            };
1219            if let Some(value) = next {
1220                state.pending.push_back(value);
1221                true
1222            } else {
1223                false
1224            }
1225        }
1226
1227        fn maybe_complete<T>(
1228            logic: &mut GraphStageLogic,
1229            outlet: &Outlet<T>,
1230            state: &State<T>,
1231        ) -> StreamResult<()>
1232        where
1233            T: Clone + Send + 'static,
1234        {
1235            if state.left_closed
1236                && state.right_closed
1237                && state.left.is_empty()
1238                && state.right.is_empty()
1239                && state.pending.is_empty()
1240                && !logic.is_closed(outlet)
1241            {
1242                logic.complete(outlet)?;
1243            }
1244            Ok(())
1245        }
1246
1247        fn maybe_pull<T>(
1248            logic: &mut GraphStageLogic,
1249            left: &Inlet<T>,
1250            right: &Inlet<T>,
1251            state: &State<T>,
1252        ) -> StreamResult<()>
1253        where
1254            T: Clone + Send + 'static,
1255        {
1256            if state.left.is_empty() && !state.left_closed && !logic.has_been_pulled(left) {
1257                logic.pull(left)?;
1258            }
1259            if state.right.is_empty() && !state.right_closed && !logic.has_been_pulled(right) {
1260                logic.pull(right)?;
1261            }
1262            Ok(())
1263        }
1264
1265        fn maybe_drain<T>(
1266            logic: &mut GraphStageLogic,
1267            outlet: &Outlet<T>,
1268            state: &Arc<Mutex<State<T>>>,
1269        ) -> StreamResult<()>
1270        where
1271            T: Clone + Ord + Send + 'static,
1272        {
1273            let next = if logic.is_available(outlet) {
1274                state
1275                    .lock()
1276                    .expect("merge-sorted state poisoned")
1277                    .pending
1278                    .pop_front()
1279            } else {
1280                None
1281            };
1282            if let Some(value) = next {
1283                logic.push(outlet, value)?;
1284            }
1285            Ok(())
1286        }
1287
1288        struct In<T: 'static> {
1289            inlet_id: PortId,
1290            left: Inlet<T>,
1291            right: Inlet<T>,
1292            outlet: Outlet<T>,
1293            state: Arc<Mutex<State<T>>>,
1294        }
1295
1296        impl<T> InHandler for In<T>
1297        where
1298            T: Clone + Ord + Send + 'static,
1299        {
1300            fn on_push(
1301                &mut self,
1302                logic: &mut GraphStageLogic,
1303                _inlet: AnyInlet,
1304            ) -> StreamResult<()> {
1305                let value: T = logic.grab_datum(self.inlet_id).and_then(|value| {
1306                    downcast_datum(value, "grab", || {
1307                        format!("inlet#{}", self.inlet_id.as_usize())
1308                    })
1309                })?;
1310                {
1311                    let mut state = self.state.lock().expect("merge-sorted state poisoned");
1312                    if self.inlet_id == self.left.id() {
1313                        state.left.push_back(value);
1314                    } else {
1315                        state.right.push_back(value);
1316                    }
1317                    while maybe_queue_output(&mut state) {}
1318                }
1319                maybe_drain(logic, &self.outlet, &self.state)?;
1320                let state = self.state.lock().expect("merge-sorted state poisoned");
1321                maybe_complete(logic, &self.outlet, &state)?;
1322                maybe_pull(logic, &self.left, &self.right, &state)
1323            }
1324
1325            fn on_upstream_finish(
1326                &mut self,
1327                logic: &mut GraphStageLogic,
1328                _inlet: AnyInlet,
1329            ) -> StreamResult<()> {
1330                {
1331                    let mut state = self.state.lock().expect("merge-sorted state poisoned");
1332                    if self.inlet_id == self.left.id() {
1333                        state.left_closed = true;
1334                    } else {
1335                        state.right_closed = true;
1336                    }
1337                    while maybe_queue_output(&mut state) {}
1338                }
1339                maybe_drain(logic, &self.outlet, &self.state)?;
1340                let state = self.state.lock().expect("merge-sorted state poisoned");
1341                maybe_complete(logic, &self.outlet, &state)?;
1342                maybe_pull(logic, &self.left, &self.right, &state)
1343            }
1344        }
1345
1346        struct Out<T: 'static> {
1347            left: Inlet<T>,
1348            right: Inlet<T>,
1349            outlet: Outlet<T>,
1350            state: Arc<Mutex<State<T>>>,
1351        }
1352
1353        impl<T> OutHandler for Out<T>
1354        where
1355            T: Clone + Ord + Send + 'static,
1356        {
1357            fn on_pull(
1358                &mut self,
1359                logic: &mut GraphStageLogic,
1360                _outlet: AnyOutlet,
1361            ) -> StreamResult<()> {
1362                maybe_drain(logic, &self.outlet, &self.state)?;
1363                let state = self.state.lock().expect("merge-sorted state poisoned");
1364                maybe_complete(logic, &self.outlet, &state)?;
1365                maybe_pull(logic, &self.left, &self.right, &state)
1366            }
1367        }
1368
1369        let state = Arc::new(Mutex::new(State::<T>::default()));
1370        let left = shape.inlet(0).expect("merge-sorted left inlet");
1371        let right = shape.inlet(1).expect("merge-sorted right inlet");
1372        let outlet = shape.outlet();
1373        let mut logic = GraphStageLogic::new(shape);
1374        logic
1375            .set_handler(
1376                &left,
1377                Box::new(In {
1378                    inlet_id: left.id(),
1379                    left: left.clone(),
1380                    right: right.clone(),
1381                    outlet: outlet.clone(),
1382                    state: Arc::clone(&state),
1383                }),
1384            )
1385            .unwrap();
1386        logic
1387            .set_handler(
1388                &right,
1389                Box::new(In {
1390                    inlet_id: right.id(),
1391                    left: left.clone(),
1392                    right: right.clone(),
1393                    outlet: outlet.clone(),
1394                    state: Arc::clone(&state),
1395                }),
1396            )
1397            .unwrap();
1398        logic
1399            .set_out_handler(
1400                &outlet.clone(),
1401                Box::new(Out {
1402                    left,
1403                    right,
1404                    outlet: outlet.clone(),
1405                    state,
1406                }),
1407            )
1408            .unwrap();
1409        logic
1410    }
1411}
1412
1413/// Fan in: reassemble elements in gap-free ascending order of the sequence
1414/// number `extract_sequence` returns (N inlets, one outlet). Fails on a missing
1415/// or duplicate sequence number.
1416#[derive(Clone)]
1417pub struct MergeSequence<T: 'static> {
1418    inputs: usize,
1419    extract_sequence: Arc<dyn Fn(&T) -> u64 + Send + Sync>,
1420    _marker: PhantomData<fn() -> T>,
1421}
1422
1423impl<T: 'static> fmt::Debug for MergeSequence<T> {
1424    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1425        f.debug_struct("MergeSequence")
1426            .field("inputs", &self.inputs)
1427            .finish_non_exhaustive()
1428    }
1429}
1430
1431impl<T: 'static> MergeSequence<T> {
1432    #[must_use]
1433    pub fn new<F>(inputs: usize, extract_sequence: F) -> Self
1434    where
1435        F: Fn(&T) -> u64 + Send + Sync + 'static,
1436    {
1437        assert!(
1438            inputs > 1,
1439            "merge sequence input count must be greater than one"
1440        );
1441        Self {
1442            inputs,
1443            extract_sequence: Arc::new(extract_sequence),
1444            _marker: PhantomData,
1445        }
1446    }
1447}
1448
1449impl<T> GraphStage for MergeSequence<T>
1450where
1451    T: Clone + Send + 'static,
1452{
1453    type Shape = FanInShape<T, T>;
1454
1455    fn name(&self) -> &str {
1456        "MergeSequence"
1457    }
1458
1459    fn allocate_shape(&self, allocator: &mut PortAllocator) -> Self::Shape {
1460        let inlets = (0..self.inputs)
1461            .map(|index| allocator.inlet(format!("MergeSequence.in{index}")))
1462            .collect();
1463        FanInShape::new(inlets, allocator.outlet("MergeSequence.out"))
1464    }
1465
1466    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
1467        let extract = Arc::clone(&self.extract_sequence);
1468        let extract_sequence = Arc::new(move |dv: &DatumValue| -> u64 {
1469            let t: &T = dv
1470                .as_any_ref()
1471                .downcast_ref::<T>()
1472                .expect("merge-sequence extract: wrong element type");
1473            extract(t)
1474        });
1475        // Typed extractor: stored as `Arc<dyn Any + Send + Sync>` and
1476        // down-cast at plan time to `Arc<dyn Fn(&T) -> u64 + Send + Sync>`.
1477        let typed_extract_fn = Arc::clone(&self.extract_sequence);
1478        let typed_extract: Arc<dyn Fn(&T) -> u64 + Send + Sync> = typed_extract_fn;
1479        let typed_extract: Arc<StageTypedSequenceFn> = Arc::new(typed_extract);
1480        StageSpec::merge_sequence(
1481            Arc::from(self.name()),
1482            shape.inlets(),
1483            shape.outlets(),
1484            self.inputs,
1485            extract_sequence,
1486            typed_extract,
1487        )
1488    }
1489
1490    fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
1491        #[derive(Clone)]
1492        struct Pending<T> {
1493            sequence: u64,
1494            elem: T,
1495        }
1496
1497        struct State<T> {
1498            next_sequence: u64,
1499            pending: Vec<Pending<T>>,
1500            completed: usize,
1501            pending_output: VecDeque<T>,
1502        }
1503
1504        fn try_emit_pending<T>(state: &mut State<T>) -> StreamResult<()>
1505        where
1506            T: Clone + Send + 'static,
1507        {
1508            while let Some(index) = state
1509                .pending
1510                .iter()
1511                .position(|item| item.sequence == state.next_sequence)
1512            {
1513                let item = state.pending.remove(index);
1514                if state
1515                    .pending
1516                    .iter()
1517                    .any(|other| other.sequence == state.next_sequence)
1518                {
1519                    return Err(StreamError::Failed(format!(
1520                        "duplicate sequence {} on merge sequence",
1521                        state.next_sequence
1522                    )));
1523                }
1524                state.pending_output.push_back(item.elem);
1525                state.next_sequence += 1;
1526            }
1527            Ok(())
1528        }
1529
1530        struct In<T: 'static> {
1531            inlet_id: PortId,
1532            inlet_index: usize,
1533            inlet: Inlet<T>,
1534            all_inlets: Vec<Inlet<T>>,
1535            outlet: Outlet<T>,
1536            extract_sequence: Arc<dyn Fn(&T) -> u64 + Send + Sync>,
1537            state: Arc<Mutex<State<T>>>,
1538        }
1539
1540        impl<T> InHandler for In<T>
1541        where
1542            T: Clone + Send + 'static,
1543        {
1544            fn on_push(
1545                &mut self,
1546                logic: &mut GraphStageLogic,
1547                _inlet: AnyInlet,
1548            ) -> StreamResult<()> {
1549                let elem: T = logic.grab_datum(self.inlet_id).and_then(|value| {
1550                    downcast_datum(value, "grab", || {
1551                        format!("inlet#{}", self.inlet_id.as_usize())
1552                    })
1553                })?;
1554                let sequence = (self.extract_sequence)(&elem);
1555                {
1556                    let mut state = self.state.lock().expect("merge-sequence state poisoned");
1557                    if sequence < state.next_sequence {
1558                        return Err(StreamError::Failed(format!(
1559                            "sequence regression from {} to {} on port {}",
1560                            state.next_sequence, sequence, self.inlet_index
1561                        )));
1562                    }
1563                    state.pending.push(Pending { sequence, elem });
1564                    try_emit_pending(&mut state)?;
1565                }
1566                let next = if logic.is_available(&self.outlet) {
1567                    self.state
1568                        .lock()
1569                        .expect("merge-sequence state poisoned")
1570                        .pending_output
1571                        .pop_front()
1572                } else {
1573                    None
1574                };
1575                if let Some(value) = next {
1576                    logic.push(&self.outlet, value)?;
1577                }
1578                let state = self.state.lock().expect("merge-sequence state poisoned");
1579                if state.completed == self.all_inlets.len()
1580                    && state.pending.is_empty()
1581                    && state.pending_output.is_empty()
1582                {
1583                    logic.complete(&self.outlet)?;
1584                } else if logic.is_available(&self.outlet)
1585                    && state.pending_output.is_empty()
1586                    && state.pending.len() + state.completed == self.all_inlets.len()
1587                {
1588                    return Err(StreamError::Failed(format!(
1589                        "expected sequence {}, but all input ports have pushed or are complete",
1590                        state.next_sequence
1591                    )));
1592                }
1593                if !logic.has_been_pulled(&self.inlet) {
1594                    logic.pull(&self.inlet)?;
1595                }
1596                Ok(())
1597            }
1598
1599            fn on_upstream_finish(
1600                &mut self,
1601                logic: &mut GraphStageLogic,
1602                _inlet: AnyInlet,
1603            ) -> StreamResult<()> {
1604                {
1605                    let mut state = self.state.lock().expect("merge-sequence state poisoned");
1606                    state.completed += 1;
1607                }
1608                let state = self.state.lock().expect("merge-sequence state poisoned");
1609                if state.completed == self.all_inlets.len()
1610                    && state.pending.is_empty()
1611                    && state.pending_output.is_empty()
1612                {
1613                    logic.complete(&self.outlet)?;
1614                } else if logic.is_available(&self.outlet)
1615                    && state.pending_output.is_empty()
1616                    && state.pending.len() + state.completed == self.all_inlets.len()
1617                {
1618                    return Err(StreamError::Failed(format!(
1619                        "expected sequence {}, but all input ports have pushed or are complete",
1620                        state.next_sequence
1621                    )));
1622                }
1623                Ok(())
1624            }
1625        }
1626
1627        struct Out<T: 'static> {
1628            inlets: Vec<Inlet<T>>,
1629            outlet: Outlet<T>,
1630            state: Arc<Mutex<State<T>>>,
1631        }
1632
1633        impl<T> OutHandler for Out<T>
1634        where
1635            T: Clone + Send + 'static,
1636        {
1637            fn on_pull(
1638                &mut self,
1639                logic: &mut GraphStageLogic,
1640                _outlet: AnyOutlet,
1641            ) -> StreamResult<()> {
1642                let next = self
1643                    .state
1644                    .lock()
1645                    .expect("merge-sequence state poisoned")
1646                    .pending_output
1647                    .pop_front();
1648                if let Some(value) = next {
1649                    logic.push(&self.outlet, value)?;
1650                } else {
1651                    let state = self.state.lock().expect("merge-sequence state poisoned");
1652                    if state.completed == self.inlets.len() && state.pending.is_empty() {
1653                        logic.complete(&self.outlet)?;
1654                    } else if state.pending.len() + state.completed == self.inlets.len() {
1655                        return Err(StreamError::Failed(format!(
1656                            "expected sequence {}, but all input ports have pushed or are complete",
1657                            state.next_sequence
1658                        )));
1659                    }
1660                }
1661                for inlet in &self.inlets {
1662                    if !logic.has_been_pulled(inlet) && !logic.is_closed(inlet) {
1663                        logic.pull(inlet)?;
1664                    }
1665                }
1666                Ok(())
1667            }
1668        }
1669
1670        let inlets = shape.inlets_vec();
1671        let outlet = shape.outlet();
1672        let state = Arc::new(Mutex::new(State {
1673            next_sequence: 0,
1674            pending: Vec::new(),
1675            completed: 0,
1676            pending_output: VecDeque::new(),
1677        }));
1678        let mut logic = GraphStageLogic::new(shape);
1679        for (index, inlet) in inlets.iter().cloned().enumerate() {
1680            logic
1681                .set_handler(
1682                    &inlet.clone(),
1683                    Box::new(In {
1684                        inlet_id: inlet.id(),
1685                        inlet_index: index,
1686                        inlet: inlet.clone(),
1687                        all_inlets: inlets.clone(),
1688                        outlet: outlet.clone(),
1689                        extract_sequence: Arc::clone(&self.extract_sequence),
1690                        state: Arc::clone(&state),
1691                    }),
1692                )
1693                .unwrap();
1694        }
1695        logic
1696            .set_out_handler(
1697                &outlet.clone(),
1698                Box::new(Out {
1699                    inlets,
1700                    outlet: outlet.clone(),
1701                    state,
1702                }),
1703            )
1704            .unwrap();
1705        logic
1706    }
1707}
1708
1709/// Fan in: once every inlet has emitted at least once, emit a `Vec<T>` snapshot
1710/// of the latest element per inlet on each push (N inlets, one `Vec<T>` outlet).
1711/// `eager_complete` ends the stream as soon as any inlet completes.
1712#[derive(Clone, Debug)]
1713pub struct MergeLatest<T: 'static> {
1714    inputs: usize,
1715    eager_complete: bool,
1716    _marker: PhantomData<fn() -> T>,
1717}
1718
1719impl<T: 'static> MergeLatest<T> {
1720    #[must_use]
1721    pub fn new(inputs: usize, eager_complete: bool) -> Self {
1722        assert!(
1723            inputs > 0,
1724            "merge-latest input count must be greater than zero"
1725        );
1726        Self {
1727            inputs,
1728            eager_complete,
1729            _marker: PhantomData,
1730        }
1731    }
1732}
1733
1734impl<T> GraphStage for MergeLatest<T>
1735where
1736    T: Clone + Send + 'static,
1737{
1738    type Shape = FanInShape<T, Vec<T>>;
1739
1740    fn name(&self) -> &str {
1741        "MergeLatest"
1742    }
1743
1744    fn allocate_shape(&self, allocator: &mut PortAllocator) -> Self::Shape {
1745        let inlets = (0..self.inputs)
1746            .map(|index| allocator.inlet(format!("MergeLatest.in{index}")))
1747            .collect();
1748        FanInShape::new(inlets, allocator.outlet("MergeLatest.out"))
1749    }
1750
1751    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
1752        let build_snapshot = Arc::new(move |values: &[&DatumValue]| -> DatumValue {
1753            let snapshot: Vec<T> = values
1754                .iter()
1755                .map(|dv| {
1756                    dv.as_any_ref()
1757                        .downcast_ref::<T>()
1758                        .cloned()
1759                        .expect("merge-latest snapshot: wrong element type")
1760                })
1761                .collect();
1762            datum(snapshot)
1763        });
1764        // Typed snapshot: builds Vec<T> directly from &[Option<T>] without boxing.
1765        // The closure type is complex; suppress the lint for this local alias.
1766        #[allow(clippy::type_complexity)]
1767        let typed_snapshot_fn: Arc<dyn Fn(&[Option<T>]) -> Vec<T> + Send + Sync> =
1768            Arc::new(move |slots: &[Option<T>]| {
1769                slots
1770                    .iter()
1771                    .map(|s| {
1772                        s.clone()
1773                            .expect("merge-latest typed snapshot: slot is None")
1774                    })
1775                    .collect()
1776            });
1777        let typed_snapshot: Arc<StageTypedSnapshotFn> = Arc::new(typed_snapshot_fn);
1778        StageSpec::merge_latest(
1779            Arc::from(self.name()),
1780            shape.inlets(),
1781            shape.outlets(),
1782            self.inputs,
1783            self.eager_complete,
1784            build_snapshot,
1785            typed_snapshot,
1786        )
1787    }
1788
1789    fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
1790        struct State<T> {
1791            latest: Vec<Option<T>>,
1792            seen: usize,
1793            completed: usize,
1794            pending: VecDeque<Vec<T>>,
1795            eager_complete: bool,
1796        }
1797
1798        struct In<T: 'static> {
1799            inlet_id: PortId,
1800            inlet_index: usize,
1801            inlet: Inlet<T>,
1802            all_inlets: Vec<Inlet<T>>,
1803            outlet: Outlet<Vec<T>>,
1804            state: Arc<Mutex<State<T>>>,
1805        }
1806
1807        impl<T> InHandler for In<T>
1808        where
1809            T: Clone + Send + 'static,
1810        {
1811            fn on_push(
1812                &mut self,
1813                logic: &mut GraphStageLogic,
1814                _inlet: AnyInlet,
1815            ) -> StreamResult<()> {
1816                let elem: T = logic.grab_datum(self.inlet_id).and_then(|value| {
1817                    downcast_datum(value, "grab", || {
1818                        format!("inlet#{}", self.inlet_id.as_usize())
1819                    })
1820                })?;
1821                {
1822                    let mut state = self.state.lock().expect("merge-latest state poisoned");
1823                    if state.latest[self.inlet_index].is_none() {
1824                        state.seen += 1;
1825                    }
1826                    state.latest[self.inlet_index] = Some(elem);
1827                    if state.seen == state.latest.len() {
1828                        let snapshot = state
1829                            .latest
1830                            .iter()
1831                            .map(|item| item.clone().expect("merge-latest seen"))
1832                            .collect();
1833                        state.pending.push_back(snapshot);
1834                    }
1835                }
1836                let next = if logic.is_available(&self.outlet) {
1837                    self.state
1838                        .lock()
1839                        .expect("merge-latest state poisoned")
1840                        .pending
1841                        .pop_front()
1842                } else {
1843                    None
1844                };
1845                if let Some(value) = next {
1846                    logic.push(&self.outlet, value)?;
1847                }
1848                if !logic.has_been_pulled(&self.inlet) {
1849                    logic.pull(&self.inlet)?;
1850                }
1851                Ok(())
1852            }
1853
1854            fn on_upstream_finish(
1855                &mut self,
1856                logic: &mut GraphStageLogic,
1857                _inlet: AnyInlet,
1858            ) -> StreamResult<()> {
1859                let state = {
1860                    let mut state = self.state.lock().expect("merge-latest state poisoned");
1861                    state.completed += 1;
1862                    (
1863                        state.completed == self.all_inlets.len(),
1864                        state.eager_complete,
1865                        state.pending.is_empty(),
1866                    )
1867                };
1868                if state.0 || (state.1 && state.2) {
1869                    logic.complete(&self.outlet)?;
1870                }
1871                Ok(())
1872            }
1873        }
1874
1875        struct Out<T: 'static> {
1876            inlets: Vec<Inlet<T>>,
1877            outlet: Outlet<Vec<T>>,
1878            state: Arc<Mutex<State<T>>>,
1879        }
1880
1881        impl<T> OutHandler for Out<T>
1882        where
1883            T: Clone + Send + 'static,
1884        {
1885            fn on_pull(
1886                &mut self,
1887                logic: &mut GraphStageLogic,
1888                _outlet: AnyOutlet,
1889            ) -> StreamResult<()> {
1890                let next = self
1891                    .state
1892                    .lock()
1893                    .expect("merge-latest state poisoned")
1894                    .pending
1895                    .pop_front();
1896                if let Some(value) = next {
1897                    logic.push(&self.outlet, value)?;
1898                } else {
1899                    let state = self.state.lock().expect("merge-latest state poisoned");
1900                    if state.completed == self.inlets.len()
1901                        || (state.eager_complete && state.completed > 0)
1902                    {
1903                        logic.complete(&self.outlet)?;
1904                    }
1905                }
1906                for inlet in &self.inlets {
1907                    if !logic.has_been_pulled(inlet) && !logic.is_closed(inlet) {
1908                        logic.pull(inlet)?;
1909                    }
1910                }
1911                Ok(())
1912            }
1913        }
1914
1915        let inlets = shape.inlets_vec();
1916        let outlet = shape.outlet();
1917        let state = Arc::new(Mutex::new(State {
1918            latest: vec![None; inlets.len()],
1919            seen: 0,
1920            completed: 0,
1921            pending: VecDeque::new(),
1922            eager_complete: self.eager_complete,
1923        }));
1924        let mut logic = GraphStageLogic::new(shape);
1925        for (index, inlet) in inlets.iter().cloned().enumerate() {
1926            logic
1927                .set_handler(
1928                    &inlet.clone(),
1929                    Box::new(In {
1930                        inlet_id: inlet.id(),
1931                        inlet_index: index,
1932                        inlet: inlet.clone(),
1933                        all_inlets: inlets.clone(),
1934                        outlet: outlet.clone(),
1935                        state: Arc::clone(&state),
1936                    }),
1937                )
1938                .unwrap();
1939        }
1940        logic
1941            .set_out_handler(
1942                &outlet.clone(),
1943                Box::new(Out {
1944                    inlets,
1945                    outlet: outlet.clone(),
1946                    state,
1947                }),
1948            )
1949            .unwrap();
1950        logic
1951    }
1952}
1953
1954/// Fan out: route each element to the outlet index `partitioner` returns (one
1955/// inlet, N outlets). `eager_cancel` cancels the whole stage when any one outlet
1956/// cancels; otherwise it runs while at least one outlet is live.
1957#[derive(Clone)]
1958pub struct Partition<T: 'static> {
1959    outputs: usize,
1960    partitioner: Arc<dyn Fn(&T) -> usize + Send + Sync>,
1961    eager_cancel: bool,
1962    _marker: PhantomData<fn() -> T>,
1963}
1964
1965impl<T: 'static> fmt::Debug for Partition<T> {
1966    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1967        f.debug_struct("Partition")
1968            .field("outputs", &self.outputs)
1969            .field("eager_cancel", &self.eager_cancel)
1970            .finish_non_exhaustive()
1971    }
1972}
1973
1974impl<T: 'static> Partition<T> {
1975    #[must_use]
1976    pub fn new<F>(outputs: usize, partitioner: F) -> Self
1977    where
1978        F: Fn(&T) -> usize + Send + Sync + 'static,
1979    {
1980        Self::new_with_eager_cancel(outputs, partitioner, false)
1981    }
1982
1983    #[must_use]
1984    pub fn new_with_eager_cancel<F>(outputs: usize, partitioner: F, eager_cancel: bool) -> Self
1985    where
1986        F: Fn(&T) -> usize + Send + Sync + 'static,
1987    {
1988        assert!(
1989            outputs > 0,
1990            "partition output count must be greater than zero"
1991        );
1992        Self {
1993            outputs,
1994            partitioner: Arc::new(partitioner),
1995            eager_cancel,
1996            _marker: PhantomData,
1997        }
1998    }
1999}
2000
2001impl<T> GraphStage for Partition<T>
2002where
2003    T: Clone + Send + 'static,
2004{
2005    type Shape = FanOutShape<T, T>;
2006
2007    fn name(&self) -> &str {
2008        "Partition"
2009    }
2010
2011    fn allocate_shape(&self, allocator: &mut PortAllocator) -> Self::Shape {
2012        let inlet = allocator.inlet("Partition.in");
2013        let outlets = (0..self.outputs)
2014            .map(|index| allocator.outlet(format!("Partition.out{index}")))
2015            .collect();
2016        FanOutShape::new(inlet, outlets)
2017    }
2018
2019    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
2020        let partitioner_clone = Arc::clone(&self.partitioner);
2021        let partitioner = Arc::new(move |dv: &DatumValue| -> usize {
2022            let t: &T = dv
2023                .as_any_ref()
2024                .downcast_ref::<T>()
2025                .expect("partition: wrong element type");
2026            partitioner_clone(t)
2027        });
2028        let typed_partitioner_fn: Arc<dyn Fn(&T) -> usize + Send + Sync> =
2029            Arc::clone(&self.partitioner);
2030        let typed_partitioner: Arc<StageTypedPartitionFn> = Arc::new(typed_partitioner_fn);
2031        StageSpec::partition(
2032            Arc::from(self.name()),
2033            shape.inlets(),
2034            shape.outlets(),
2035            self.outputs,
2036            partitioner,
2037            typed_partitioner,
2038            self.eager_cancel,
2039        )
2040    }
2041
2042    fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
2043        struct State<T> {
2044            pending: Option<(usize, T)>,
2045            upstream_closed: bool,
2046            live_outlets: usize,
2047            cancelled: Vec<bool>,
2048            eager_cancel: bool,
2049        }
2050
2051        fn any_live_demand<T>(
2052            logic: &GraphStageLogic,
2053            outlets: &[Outlet<T>],
2054            cancelled: &[bool],
2055        ) -> bool
2056        where
2057            T: Clone + Send + 'static,
2058        {
2059            outlets
2060                .iter()
2061                .enumerate()
2062                .any(|(index, outlet)| !cancelled[index] && logic.is_available(outlet))
2063        }
2064
2065        struct In<T: 'static> {
2066            inlet_id: PortId,
2067            inlet: Inlet<T>,
2068            outlets: Vec<Outlet<T>>,
2069            partitioner: Arc<dyn Fn(&T) -> usize + Send + Sync>,
2070            state: Arc<Mutex<State<T>>>,
2071        }
2072
2073        impl<T> InHandler for In<T>
2074        where
2075            T: Clone + Send + 'static,
2076        {
2077            fn on_push(
2078                &mut self,
2079                logic: &mut GraphStageLogic,
2080                _inlet: AnyInlet,
2081            ) -> StreamResult<()> {
2082                let item: T = logic.grab_datum(self.inlet_id).and_then(|value| {
2083                    downcast_datum(value, "grab", || {
2084                        format!("inlet#{}", self.inlet_id.as_usize())
2085                    })
2086                })?;
2087                let idx = (self.partitioner)(&item);
2088                if idx >= self.outlets.len() {
2089                    return Err(StreamError::Failed(format!(
2090                        "partitioner returned out-of-bounds index {idx} for {} outputs",
2091                        self.outlets.len()
2092                    )));
2093                }
2094                let mut pull_again = false;
2095                {
2096                    let mut state = self.state.lock().expect("partition state poisoned");
2097                    if state.cancelled[idx] {
2098                        pull_again = !state.upstream_closed
2099                            && any_live_demand(logic, &self.outlets, &state.cancelled);
2100                    } else if logic.is_available(&self.outlets[idx]) {
2101                        logic.push(&self.outlets[idx], item)?;
2102                        pull_again = !state.upstream_closed
2103                            && any_live_demand(logic, &self.outlets, &state.cancelled);
2104                    } else {
2105                        state.pending = Some((idx, item));
2106                    }
2107                }
2108                if pull_again && !logic.has_been_pulled(&self.inlet) {
2109                    logic.pull(&self.inlet)?;
2110                }
2111                Ok(())
2112            }
2113
2114            fn on_upstream_finish(
2115                &mut self,
2116                logic: &mut GraphStageLogic,
2117                _inlet: AnyInlet,
2118            ) -> StreamResult<()> {
2119                let complete_now = {
2120                    let mut state = self.state.lock().expect("partition state poisoned");
2121                    state.upstream_closed = true;
2122                    state.pending.is_none()
2123                };
2124                if complete_now {
2125                    for outlet in &self.outlets {
2126                        if !logic.is_closed(outlet) {
2127                            logic.complete(outlet)?;
2128                        }
2129                    }
2130                }
2131                Ok(())
2132            }
2133        }
2134
2135        struct Out<T: 'static> {
2136            index: usize,
2137            inlet: Inlet<T>,
2138            outlets: Vec<Outlet<T>>,
2139            state: Arc<Mutex<State<T>>>,
2140        }
2141
2142        impl<T> OutHandler for Out<T>
2143        where
2144            T: Clone + Send + 'static,
2145        {
2146            fn on_pull(
2147                &mut self,
2148                logic: &mut GraphStageLogic,
2149                _outlet: AnyOutlet,
2150            ) -> StreamResult<()> {
2151                let mut complete_now = false;
2152                let pending = {
2153                    let mut state = self.state.lock().expect("partition state poisoned");
2154                    if let Some((idx, _)) = &state.pending
2155                        && *idx == self.index
2156                    {
2157                        state.pending.take()
2158                    } else {
2159                        None
2160                    }
2161                };
2162                if let Some((_, item)) = pending {
2163                    logic.push(&self.outlets[self.index], item)?;
2164                    let state = self.state.lock().expect("partition state poisoned");
2165                    if state.upstream_closed {
2166                        complete_now = true;
2167                    } else if any_live_demand(logic, &self.outlets, &state.cancelled)
2168                        && !logic.has_been_pulled(&self.inlet)
2169                    {
2170                        logic.pull(&self.inlet)?;
2171                    }
2172                } else {
2173                    let state = self.state.lock().expect("partition state poisoned");
2174                    if state.upstream_closed {
2175                        complete_now = true;
2176                    } else if any_live_demand(logic, &self.outlets, &state.cancelled)
2177                        && !logic.has_been_pulled(&self.inlet)
2178                    {
2179                        logic.pull(&self.inlet)?;
2180                    }
2181                }
2182                if complete_now {
2183                    for outlet in &self.outlets {
2184                        if !logic.is_closed(outlet) {
2185                            logic.complete(outlet)?;
2186                        }
2187                    }
2188                }
2189                Ok(())
2190            }
2191
2192            fn on_downstream_finish(
2193                &mut self,
2194                logic: &mut GraphStageLogic,
2195                _outlet: AnyOutlet,
2196            ) -> StreamResult<()> {
2197                let (cancel_stage, clear_pending) = {
2198                    let mut state = self.state.lock().expect("partition state poisoned");
2199                    if state.cancelled[self.index] {
2200                        return Ok(());
2201                    }
2202                    state.cancelled[self.index] = true;
2203                    state.live_outlets -= 1;
2204                    let clear_pending = state
2205                        .pending
2206                        .as_ref()
2207                        .is_some_and(|(idx, _)| *idx == self.index);
2208                    let cancel_stage = state.eager_cancel || state.live_outlets == 0;
2209                    if clear_pending {
2210                        state.pending = None;
2211                    }
2212                    (cancel_stage, clear_pending)
2213                };
2214                if cancel_stage {
2215                    logic.complete_stage()?;
2216                } else if clear_pending
2217                    && !logic.has_been_pulled(&self.inlet)
2218                    && !logic.is_closed(&self.inlet)
2219                {
2220                    let state = self.state.lock().expect("partition state poisoned");
2221                    if any_live_demand(logic, &self.outlets, &state.cancelled) {
2222                        logic.pull(&self.inlet)?;
2223                    }
2224                }
2225                Ok(())
2226            }
2227        }
2228
2229        let inlet = shape.inlet();
2230        let outlets = shape.outlets_vec();
2231        let state = Arc::new(Mutex::new(State {
2232            pending: None,
2233            upstream_closed: false,
2234            live_outlets: outlets.len(),
2235            cancelled: vec![false; outlets.len()],
2236            eager_cancel: self.eager_cancel,
2237        }));
2238        let mut logic = GraphStageLogic::new(shape);
2239        logic
2240            .set_handler(
2241                &inlet,
2242                Box::new(In {
2243                    inlet_id: inlet.id(),
2244                    inlet: inlet.clone(),
2245                    outlets: outlets.clone(),
2246                    partitioner: Arc::clone(&self.partitioner),
2247                    state: Arc::clone(&state),
2248                }),
2249            )
2250            .unwrap();
2251        for (index, outlet) in outlets.iter().cloned().enumerate() {
2252            logic
2253                .set_out_handler(
2254                    &outlet,
2255                    Box::new(Out {
2256                        index,
2257                        inlet: inlet.clone(),
2258                        outlets: outlets.clone(),
2259                        state: Arc::clone(&state),
2260                    }),
2261                )
2262                .unwrap();
2263        }
2264        logic
2265    }
2266}
2267
2268/// Fan out: split each `(A, B)` pair, sending `A` to out0 and `B` to out1 (one
2269/// inlet, two outlets). Built on [`UnzipWith`] with the identity split.
2270#[derive(Clone, Debug)]
2271pub struct Unzip<A: 'static, B: 'static> {
2272    _marker: PhantomData<fn() -> (A, B)>,
2273}
2274
2275impl<A: 'static, B: 'static> Unzip<A, B> {
2276    #[must_use]
2277    pub fn new() -> Self {
2278        Self {
2279            _marker: PhantomData,
2280        }
2281    }
2282}
2283
2284impl<A: 'static, B: 'static> Default for Unzip<A, B> {
2285    fn default() -> Self {
2286        Self::new()
2287    }
2288}
2289
2290impl<A, B> GraphStage for Unzip<A, B>
2291where
2292    A: Clone + Send + 'static,
2293    B: Clone + Send + 'static,
2294{
2295    type Shape = FanOutShape2<(A, B), A, B>;
2296
2297    fn name(&self) -> &str {
2298        "Unzip"
2299    }
2300
2301    fn allocate_shape(&self, allocator: &mut PortAllocator) -> Self::Shape {
2302        FanOutShape2::new(
2303            allocator.inlet("Unzip.in"),
2304            allocator.outlet("Unzip.out0"),
2305            allocator.outlet("Unzip.out1"),
2306        )
2307    }
2308
2309    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
2310        let split = Arc::new(|dv: DatumValue| -> (DatumValue, DatumValue) {
2311            let pair: (A, B) =
2312                downcast_datum(dv, "unzip", || "Unzip.in").expect("unzip: wrong element type");
2313            (datum(pair.0), datum(pair.1))
2314        });
2315        // Typed split: `|(a, b): (A, B)| -> (A, B)`.  Stored as an opaque
2316        // `Arc<StageTypedUnzipFn>` and down-cast at plan time.
2317        // The `Arc<dyn Fn((A, B)) -> (A, B)>` wrapper is intentionally verbose;
2318        // suppress the type_complexity lint for this one binding.
2319        #[allow(clippy::type_complexity)]
2320        let typed_split_fn: Arc<dyn Fn((A, B)) -> (A, B) + Send + Sync> =
2321            Arc::new(|pair: (A, B)| pair);
2322        let typed_split: Arc<StageTypedUnzipFn> = Arc::new(typed_split_fn);
2323        StageSpec::unzip(
2324            Arc::from(self.name()),
2325            shape.inlets(),
2326            shape.outlets(),
2327            split,
2328            typed_split,
2329        )
2330    }
2331
2332    fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
2333        UnzipWith::new(|pair: (A, B)| pair).create_logic(shape)
2334    }
2335}
2336
2337/// Fan out: apply `split` to each element, sending the first component to out0
2338/// and the second to out1 (one inlet, two outlets).
2339#[derive(Clone)]
2340pub struct UnzipWith<In: 'static, Out0: 'static, Out1: 'static> {
2341    split: Arc<dyn Fn(In) -> (Out0, Out1) + Send + Sync>,
2342    _marker: PhantomData<fn(In) -> (Out0, Out1)>,
2343}
2344
2345impl<In: 'static, Out0: 'static, Out1: 'static> fmt::Debug for UnzipWith<In, Out0, Out1> {
2346    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2347        f.debug_struct("UnzipWith").finish_non_exhaustive()
2348    }
2349}
2350
2351impl<In: 'static, Out0: 'static, Out1: 'static> UnzipWith<In, Out0, Out1> {
2352    #[must_use]
2353    pub fn new<F>(split: F) -> Self
2354    where
2355        F: Fn(In) -> (Out0, Out1) + Send + Sync + 'static,
2356    {
2357        Self {
2358            split: Arc::new(split),
2359            _marker: PhantomData,
2360        }
2361    }
2362}
2363
2364impl<In, Out0, Out1> GraphStage for UnzipWith<In, Out0, Out1>
2365where
2366    In: Clone + Send + 'static,
2367    Out0: Clone + Send + 'static,
2368    Out1: Clone + Send + 'static,
2369{
2370    type Shape = FanOutShape2<In, Out0, Out1>;
2371
2372    fn name(&self) -> &str {
2373        "UnzipWith"
2374    }
2375
2376    fn allocate_shape(&self, allocator: &mut PortAllocator) -> Self::Shape {
2377        FanOutShape2::new(
2378            allocator.inlet("UnzipWith.in"),
2379            allocator.outlet("UnzipWith.out0"),
2380            allocator.outlet("UnzipWith.out1"),
2381        )
2382    }
2383
2384    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
2385        let split_fn = Arc::clone(&self.split);
2386        let split = Arc::new(move |dv: DatumValue| -> (DatumValue, DatumValue) {
2387            let value: In = downcast_datum(dv, "unzip_with", || "UnzipWith.in")
2388                .expect("unzip-with: wrong element type");
2389            let (out0, out1) = split_fn(value);
2390            (datum(out0), datum(out1))
2391        });
2392        // Typed split: `Arc<dyn Fn(In) -> (Out0, Out1) + Send + Sync>`.
2393        // Stored as opaque `Arc<StageTypedUnzipFn>` and down-cast at plan time.
2394        let typed_split_fn: Arc<dyn Fn(In) -> (Out0, Out1) + Send + Sync> = Arc::clone(&self.split);
2395        let typed_split: Arc<StageTypedUnzipFn> = Arc::new(typed_split_fn);
2396        StageSpec::unzip(
2397            Arc::from(self.name()),
2398            shape.inlets(),
2399            shape.outlets(),
2400            split,
2401            typed_split,
2402        )
2403    }
2404
2405    fn create_logic(&self, shape: &Self::Shape) -> GraphStageLogic {
2406        struct State {
2407            left_open: bool,
2408            right_open: bool,
2409            upstream_closed: bool,
2410        }
2411
2412        struct InHandlerState<In: 'static, Out0: 'static, Out1: 'static> {
2413            inlet_id: PortId,
2414            inlet: Inlet<In>,
2415            out0: Outlet<Out0>,
2416            out1: Outlet<Out1>,
2417            split: Arc<dyn Fn(In) -> (Out0, Out1) + Send + Sync>,
2418            state: Arc<Mutex<State>>,
2419        }
2420
2421        impl<In, Out0, Out1> InHandler for InHandlerState<In, Out0, Out1>
2422        where
2423            In: Clone + Send + 'static,
2424            Out0: Clone + Send + 'static,
2425            Out1: Clone + Send + 'static,
2426        {
2427            fn on_push(
2428                &mut self,
2429                logic: &mut GraphStageLogic,
2430                _inlet: AnyInlet,
2431            ) -> StreamResult<()> {
2432                let value: In = logic.grab_datum(self.inlet_id).and_then(|value| {
2433                    downcast_datum(value, "grab", || {
2434                        format!("inlet#{}", self.inlet_id.as_usize())
2435                    })
2436                })?;
2437                let (left, right) = (self.split)(value);
2438                let state = self.state.lock().expect("unzip-with state poisoned");
2439                if state.left_open {
2440                    logic.push(&self.out0, left)?;
2441                }
2442                if state.right_open {
2443                    logic.push(&self.out1, right)?;
2444                }
2445                drop(state);
2446                let state = self.state.lock().expect("unzip-with state poisoned");
2447                let left_ready = !state.left_open || logic.is_available(&self.out0);
2448                let right_ready = !state.right_open || logic.is_available(&self.out1);
2449                if (state.left_open || state.right_open)
2450                    && left_ready
2451                    && right_ready
2452                    && !logic.has_been_pulled(&self.inlet)
2453                {
2454                    logic.pull(&self.inlet)?;
2455                }
2456                Ok(())
2457            }
2458
2459            fn on_upstream_finish(
2460                &mut self,
2461                logic: &mut GraphStageLogic,
2462                _inlet: AnyInlet,
2463            ) -> StreamResult<()> {
2464                self.state
2465                    .lock()
2466                    .expect("unzip-with state poisoned")
2467                    .upstream_closed = true;
2468                if !logic.is_closed(&self.out0) {
2469                    logic.complete(&self.out0)?;
2470                }
2471                if !logic.is_closed(&self.out1) {
2472                    logic.complete(&self.out1)?;
2473                }
2474                Ok(())
2475            }
2476        }
2477
2478        struct Out<In: 'static, Out0: 'static, Out1: 'static> {
2479            is_left: bool,
2480            inlet: Inlet<In>,
2481            out0: Outlet<Out0>,
2482            out1: Outlet<Out1>,
2483            state: Arc<Mutex<State>>,
2484        }
2485
2486        impl<In, Out0, Out1> OutHandler for Out<In, Out0, Out1>
2487        where
2488            In: Clone + Send + 'static,
2489            Out0: Clone + Send + 'static,
2490            Out1: Clone + Send + 'static,
2491        {
2492            fn on_pull(
2493                &mut self,
2494                logic: &mut GraphStageLogic,
2495                _outlet: AnyOutlet,
2496            ) -> StreamResult<()> {
2497                let state = self.state.lock().expect("unzip-with state poisoned");
2498                let left_ready = !state.left_open || logic.is_available(&self.out0);
2499                let right_ready = !state.right_open || logic.is_available(&self.out1);
2500                if state.upstream_closed {
2501                    drop(state);
2502                    if !logic.is_closed(&self.out0) {
2503                        logic.complete(&self.out0)?;
2504                    }
2505                    if !logic.is_closed(&self.out1) {
2506                        logic.complete(&self.out1)?;
2507                    }
2508                } else if (state.left_open || state.right_open)
2509                    && left_ready
2510                    && right_ready
2511                    && !logic.has_been_pulled(&self.inlet)
2512                {
2513                    drop(state);
2514                    logic.pull(&self.inlet)?;
2515                }
2516                Ok(())
2517            }
2518
2519            fn on_downstream_finish(
2520                &mut self,
2521                logic: &mut GraphStageLogic,
2522                _outlet: AnyOutlet,
2523            ) -> StreamResult<()> {
2524                let mut state = self.state.lock().expect("unzip-with state poisoned");
2525                if self.is_left {
2526                    state.left_open = false;
2527                } else {
2528                    state.right_open = false;
2529                }
2530                if !state.left_open && !state.right_open {
2531                    logic.complete_stage()?;
2532                    return Ok(());
2533                }
2534                let left_ready = !state.left_open || logic.is_available(&self.out0);
2535                let right_ready = !state.right_open || logic.is_available(&self.out1);
2536                if !state.upstream_closed
2537                    && (state.left_open || state.right_open)
2538                    && left_ready
2539                    && right_ready
2540                    && !logic.has_been_pulled(&self.inlet)
2541                {
2542                    logic.pull(&self.inlet)?;
2543                }
2544                Ok(())
2545            }
2546        }
2547
2548        let inlet = shape.inlet();
2549        let out0 = shape.out0();
2550        let out1 = shape.out1();
2551        let state = Arc::new(Mutex::new(State {
2552            left_open: true,
2553            right_open: true,
2554            upstream_closed: false,
2555        }));
2556        let mut logic = GraphStageLogic::new(shape);
2557        logic
2558            .set_handler(
2559                &inlet,
2560                Box::new(InHandlerState {
2561                    inlet_id: inlet.id(),
2562                    inlet: inlet.clone(),
2563                    out0: out0.clone(),
2564                    out1: out1.clone(),
2565                    split: Arc::clone(&self.split),
2566                    state: Arc::clone(&state),
2567                }),
2568            )
2569            .unwrap();
2570        logic
2571            .set_out_handler(
2572                &out0,
2573                Box::new(Out {
2574                    is_left: true,
2575                    inlet: inlet.clone(),
2576                    out0: out0.clone(),
2577                    out1: out1.clone(),
2578                    state: Arc::clone(&state),
2579                }),
2580            )
2581            .unwrap();
2582        logic
2583            .set_out_handler(
2584                &out1.clone(),
2585                Box::new(Out {
2586                    is_left: false,
2587                    inlet: inlet.clone(),
2588                    out0: out0.clone(),
2589                    out1: out1.clone(),
2590                    state,
2591                }),
2592            )
2593            .unwrap();
2594        logic
2595    }
2596}
2597
2598/// A pass-through marker that splits the graph into separate fused segments
2599/// (one inlet, one outlet). Mirrors Akka's `.async` boundary.
2600#[derive(Clone, Debug)]
2601pub struct AsyncBoundary<T: 'static> {
2602    _marker: PhantomData<fn() -> T>,
2603}
2604
2605impl<T: 'static> AsyncBoundary<T> {
2606    #[must_use]
2607    pub fn new() -> Self {
2608        Self {
2609            _marker: PhantomData,
2610        }
2611    }
2612}
2613
2614impl<T: 'static> Default for AsyncBoundary<T> {
2615    fn default() -> Self {
2616        Self::new()
2617    }
2618}
2619
2620impl<T> GraphStage for AsyncBoundary<T>
2621where
2622    T: Clone + Send + 'static,
2623{
2624    type Shape = FlowShape<T, T>;
2625
2626    fn name(&self) -> &str {
2627        "AsyncBoundary"
2628    }
2629
2630    fn allocate_shape(&self, _allocator: &mut PortAllocator) -> Self::Shape {
2631        let first_id = next_port_id_block(2);
2632        FlowShape::new(
2633            Inlet::with_arc_name(first_id, async_boundary_inlet_name()),
2634            Outlet::with_arc_name(first_id.offset(1), async_boundary_outlet_name()),
2635        )
2636    }
2637
2638    fn stage_spec(&self, shape: &Self::Shape) -> StageSpec {
2639        self.stage_spec_with_ports(shape, shape.inlets(), shape.outlets())
2640    }
2641
2642    fn stage_spec_with_ports(
2643        &self,
2644        _shape: &Self::Shape,
2645        inlets: Vec<AnyInlet>,
2646        outlets: Vec<AnyOutlet>,
2647    ) -> StageSpec {
2648        StageSpec::async_boundary(async_boundary_stage_name(), inlets, outlets)
2649    }
2650}