Skip to main content

sim_lib_control/
resume.rs

1/// Limits applied to one resumable frame.
2#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3pub struct FrameLimits {
4    /// Maximum nested frame depth accepted by the driver.
5    pub depth: usize,
6    /// Maximum work units available to each resume operation.
7    pub work: usize,
8}
9
10/// Input delivered when a frame is resumed.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub enum ResumePacket<T, E> {
13    /// Start a frame that has not run before.
14    Start,
15    /// Send a value into a suspended frame.
16    Send(T),
17    /// Throw an error into a suspended frame.
18    Throw(E),
19    /// Ask a suspended frame to close and run its cleanup.
20    Close,
21}
22
23/// Observable result of a resume operation.
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub enum ResumeResult<T, R, E> {
26    /// The frame suspended after yielding a value.
27    Yielded(T),
28    /// The frame completed and returned a value.
29    Returned(R),
30    /// The frame completed with a failure.
31    Failed(E),
32}
33
34/// Failure enforced by the frame boundary rather than by its guest driver.
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum FrameError {
37    /// A non-start packet was sent before the frame started.
38    NotStarted,
39    /// Start was sent more than once.
40    AlreadyStarted,
41    /// A terminal frame was resumed again.
42    AlreadyComplete,
43    /// The driver exceeded its declared nesting depth.
44    DepthExhausted,
45    /// The driver exhausted its declared work allowance.
46    WorkExhausted,
47}
48
49/// Budget passed to a frame driver for one resume operation.
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub struct StepBudget {
52    depth_left: usize,
53    work_left: usize,
54}
55
56impl StepBudget {
57    /// Charges one work unit, failing closed when none remain.
58    pub fn charge_work(&mut self) -> Result<(), FrameError> {
59        self.work_left = self
60            .work_left
61            .checked_sub(1)
62            .ok_or(FrameError::WorkExhausted)?;
63        Ok(())
64    }
65
66    /// Enters one nested frame level, failing closed at the depth limit.
67    pub fn enter(&mut self) -> Result<(), FrameError> {
68        self.depth_left = self
69            .depth_left
70            .checked_sub(1)
71            .ok_or(FrameError::DepthExhausted)?;
72        Ok(())
73    }
74
75    /// Leaves a nested frame level.
76    pub fn leave(&mut self) {
77        self.depth_left = self.depth_left.saturating_add(1);
78    }
79}
80
81/// A surface-neutral, one-shot-completion resumable frame.
82pub struct ResumableFrame<D> {
83    driver: D,
84    limits: FrameLimits,
85    started: bool,
86    complete: bool,
87}
88
89impl<D> ResumableFrame<D> {
90    /// Creates a frame driven by `driver` under explicit limits.
91    pub fn new(limits: FrameLimits, driver: D) -> Self {
92        Self {
93            driver,
94            limits,
95            started: false,
96            complete: false,
97        }
98    }
99
100    /// Returns whether the frame has returned or failed.
101    pub fn is_complete(&self) -> bool {
102        self.complete
103    }
104
105    /// Delivers one packet and returns the next observable transition.
106    pub fn resume<T, R, E>(
107        &mut self,
108        packet: ResumePacket<T, E>,
109    ) -> Result<ResumeResult<T, R, E>, FrameError>
110    where
111        D: FnMut(ResumePacket<T, E>, &mut StepBudget) -> Result<ResumeResult<T, R, E>, FrameError>,
112    {
113        if self.complete {
114            return Err(FrameError::AlreadyComplete);
115        }
116        match (&packet, self.started) {
117            (ResumePacket::Start, true) => return Err(FrameError::AlreadyStarted),
118            (ResumePacket::Start, false) => self.started = true,
119            (_, false) => return Err(FrameError::NotStarted),
120            (_, true) => {}
121        }
122        let mut budget = StepBudget {
123            depth_left: self.limits.depth,
124            work_left: self.limits.work,
125        };
126        let outcome = (self.driver)(packet, &mut budget)?;
127        self.complete = !matches!(outcome, ResumeResult::Yielded(_));
128        Ok(outcome)
129    }
130}