Skip to main content

pipecrab_core/
processor.rs

1use crate::frame::{DataFrame, Direction, SystemFrame};
2
3/// What happens to the frame that [`Processor::decide_data`] or
4/// [`Processor::decide_system`] just received.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Disposition {
7    /// Pass the input onward — data downstream, system in its travel direction.
8    Forward,
9    /// Consume the input; it does not propagate.
10    Drop,
11}
12
13/// Outcome of [`Processor::decide_data`] / [`Processor::decide_system`]:
14/// a disposition for the input frame plus zero or more effects to emit.
15///
16/// # Four forms
17///
18/// ```
19/// use pipecrab_core::{Decision, Disposition};
20///
21/// // 1. Pass-through: forward the input, emit nothing.
22/// let d: Decision<u32> = Decision::forward();
23/// assert_eq!(d.disposition, Disposition::Forward);
24/// assert!(d.effects.is_empty());
25///
26/// // 2. Transform (drop-on-emit): consume the input, emit a replacement.
27/// let d: Decision<u32> = Decision::drop().emit(42);
28/// assert_eq!(d.disposition, Disposition::Drop);
29/// assert_eq!(d.effects, [42]);
30///
31/// // 3. Observe-and-pass: forward the input *and* emit a derived value.
32/// let d: Decision<u32> = Decision::forward().emit(42);
33/// assert_eq!(d.disposition, Disposition::Forward);
34/// assert_eq!(d.effects, [42]);
35///
36/// // 4. Silent drop: consume the input, emit nothing.
37/// let d: Decision<u32> = Decision::drop();
38/// assert_eq!(d.disposition, Disposition::Drop);
39/// assert!(d.effects.is_empty());
40/// ```
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct Decision<E> {
43    /// What happens to the input frame.
44    pub disposition: Disposition,
45    /// Effects to perform, in order, after the disposition is acted on.
46    pub effects: Vec<E>,
47}
48
49impl<E> Decision<E> {
50    /// Forward the input, emit nothing. Allocates nothing.
51    pub fn forward() -> Self {
52        Self { disposition: Disposition::Forward, effects: Vec::new() }
53    }
54
55    /// Drop the input, emit nothing. Allocates nothing.
56    pub fn drop() -> Self {
57        Self { disposition: Disposition::Drop, effects: Vec::new() }
58    }
59
60    /// Append an effect, leaving the disposition unchanged. Chainable.
61    pub fn emit(mut self, effect: E) -> Self {
62        self.effects.push(effect);
63        self
64    }
65}
66
67impl<E> Default for Decision<E> {
68    fn default() -> Self {
69        Self::forward()
70    }
71}
72
73/// The pure core of a stage.
74///
75/// Both methods are synchronous and total — no `.await`, no I/O — so they
76/// cannot be cancelled mid-step. All state mutation happens here; `perform`
77/// (in the runtime) is `&self` so a dropped future can't tear state.
78///
79/// `Effect` is the stage's own command vocabulary: plain data defined in core.
80/// The methods emit commands describing *what should happen*; the runtime's
81/// `perform` interprets them and does the I/O.
82///
83/// # Defaults
84///
85/// Both methods default to [`Decision::forward()`]: an un-overridden stage is a
86/// transparent pass-through. Override only the variants you care about.
87pub trait Processor {
88    /// The command vocabulary this stage can emit; plain data, no I/O.
89    type Effect;
90
91    /// Maps an incoming data frame to a disposition and zero or more effects.
92    ///
93    /// Called for every [`DataFrame`] arriving on the data lane. The default
94    /// forwards the frame unchanged.
95    fn decide_data(&mut self, _frame: &DataFrame) -> Decision<Self::Effect> {
96        Decision::forward()
97    }
98
99    /// Maps an incoming system frame to a disposition and zero or more effects.
100    ///
101    /// Called for every [`SystemFrame`] arriving on the system lane, along with
102    /// the [`Direction`] it is travelling. The default forwards the frame.
103    fn decide_system(&mut self, _dir: Direction, _frame: &SystemFrame) -> Decision<Self::Effect> {
104        Decision::forward()
105    }
106}