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}