Skip to main content

sim_lib_control/
coroutine.rs

1use sim_kernel::Ref;
2
3/// The next observable transition from a resumable coroutine frame.
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub enum CoroutineFrameStep<T = Ref> {
6    /// The frame produced a value from its producer side.
7    Produced(T),
8    /// The frame consumed a value on its consumer side.
9    Consumed(T),
10    /// Both sides are exhausted.
11    Complete,
12}
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15enum CoroutineFrameTurn {
16    Produce,
17    Consume,
18}
19
20/// A resumable producer/consumer frame for control libraries.
21///
22/// The frame alternates between produced and consumed values while either side
23/// has work remaining. It carries no language-specific status names, so codec
24/// and language layers can map the generic steps into their own surfaces.
25#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct CoroutineFrame<T = Ref> {
27    produced: Vec<T>,
28    consumed: Vec<T>,
29    produced_index: usize,
30    consumed_index: usize,
31    turn: CoroutineFrameTurn,
32}
33
34impl<T> CoroutineFrame<T> {
35    /// Builds a frame from producer-side and consumer-side values.
36    pub fn new(produced: Vec<T>, consumed: Vec<T>) -> Self {
37        Self {
38            produced,
39            consumed,
40            produced_index: 0,
41            consumed_index: 0,
42            turn: CoroutineFrameTurn::Produce,
43        }
44    }
45
46    /// Resumes the frame, returning the next producer or consumer transition.
47    pub fn resume(&mut self) -> CoroutineFrameStep<T>
48    where
49        T: Clone,
50    {
51        loop {
52            match self.turn {
53                CoroutineFrameTurn::Produce => {
54                    self.turn = CoroutineFrameTurn::Consume;
55                    if let Some(value) = self.produced.get(self.produced_index).cloned() {
56                        self.produced_index += 1;
57                        return CoroutineFrameStep::Produced(value);
58                    }
59                }
60                CoroutineFrameTurn::Consume => {
61                    self.turn = CoroutineFrameTurn::Produce;
62                    if let Some(value) = self.consumed.get(self.consumed_index).cloned() {
63                        self.consumed_index += 1;
64                        return CoroutineFrameStep::Consumed(value);
65                    }
66                }
67            }
68
69            if self.is_complete() {
70                return CoroutineFrameStep::Complete;
71            }
72        }
73    }
74
75    /// Returns whether both producer and consumer sides are exhausted.
76    pub fn is_complete(&self) -> bool {
77        self.produced_index >= self.produced.len() && self.consumed_index >= self.consumed.len()
78    }
79}
80
81/// Identifies which of a coroutine's two cooperating lanes yielded a value.
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub enum CoroutineLane {
84    /// The first lane.
85    First,
86    /// The second lane.
87    Second,
88}
89
90/// Outcome of resuming a [`Coroutine`]: a yielded value, or exhaustion.
91#[derive(Clone, Debug, PartialEq, Eq)]
92pub enum CoroutineStep {
93    /// A lane yielded a value and control returned to the driver.
94    Yielded {
95        /// The lane that produced this value.
96        lane: CoroutineLane,
97        /// The yielded value.
98        value: Ref,
99    },
100    /// Both lanes are drained; the coroutine has nothing left to yield.
101    Exhausted,
102}
103
104/// Two cooperating value streams that yield by alternating between lanes.
105///
106/// Models symmetric coroutine control: each [`Coroutine::resume`] hands control
107/// to the next lane, falling through to the other when one is drained.
108///
109/// # Examples
110///
111/// ```
112/// use sim_kernel::{Ref, Symbol};
113/// use sim_lib_control::{Coroutine, CoroutineLane, CoroutineStep};
114///
115/// let a = Ref::Symbol(Symbol::new("a"));
116/// let b = Ref::Symbol(Symbol::new("b"));
117/// let mut co = Coroutine::alternating(vec![a.clone()], vec![b.clone()]);
118/// assert_eq!(
119///     co.resume(),
120///     CoroutineStep::Yielded { lane: CoroutineLane::First, value: a }
121/// );
122/// assert_eq!(
123///     co.resume(),
124///     CoroutineStep::Yielded { lane: CoroutineLane::Second, value: b }
125/// );
126/// assert_eq!(co.resume(), CoroutineStep::Exhausted);
127/// ```
128#[derive(Clone, Debug, PartialEq, Eq)]
129pub struct Coroutine {
130    first: Vec<Ref>,
131    second: Vec<Ref>,
132    first_index: usize,
133    second_index: usize,
134    next_lane: CoroutineLane,
135}
136
137impl Coroutine {
138    /// Builds a coroutine that alternates yields between the `first` and
139    /// `second` lanes, starting with the first.
140    pub fn alternating(first: Vec<Ref>, second: Vec<Ref>) -> Self {
141        Self {
142            first,
143            second,
144            first_index: 0,
145            second_index: 0,
146            next_lane: CoroutineLane::First,
147        }
148    }
149
150    /// Resumes the coroutine, yielding the next value from the active lane (or
151    /// the other lane if the active one is drained), or
152    /// [`CoroutineStep::Exhausted`] when both are empty.
153    pub fn resume(&mut self) -> CoroutineStep {
154        let step = match self.next_lane {
155            CoroutineLane::First => self.resume_first().or_else(|| self.resume_second()),
156            CoroutineLane::Second => self.resume_second().or_else(|| self.resume_first()),
157        };
158        step.unwrap_or(CoroutineStep::Exhausted)
159    }
160
161    fn resume_first(&mut self) -> Option<CoroutineStep> {
162        let value = self.first.get(self.first_index).cloned()?;
163        self.first_index += 1;
164        self.next_lane = CoroutineLane::Second;
165        Some(CoroutineStep::Yielded {
166            lane: CoroutineLane::First,
167            value,
168        })
169    }
170
171    fn resume_second(&mut self) -> Option<CoroutineStep> {
172        let value = self.second.get(self.second_index).cloned()?;
173        self.second_index += 1;
174        self.next_lane = CoroutineLane::First;
175        Some(CoroutineStep::Yielded {
176            lane: CoroutineLane::Second,
177            value,
178        })
179    }
180}