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