eredu_runtime/execution_control/
choice.rs1use super::SnapshotTokenController;
3use crate::TokenDomain;
4use eredu_core::{TokenFilter, TokenFilterController};
5
6#[derive(Debug, thiserror::Error)]
8pub enum TokenChoiceError<E: std::error::Error + 'static> {
9 #[error("token constraint failed: {0}")]
11 Constraint(#[source] E),
12 #[error("forced token {0} is outside the canonical vocabulary")]
14 InvalidToken(u32),
15 #[error("forced token {0} conflicts with the active token constraints")]
17 Forbidden(u32),
18 #[error("a forced choice is already pending; clear it before replacing it")]
20 AlreadyPending,
21 #[error("forced token {expected} was requested, but backend committed {actual}")]
23 UnexpectedCommit {
24 expected: u32,
26 actual: u32,
28 },
29}
30
31#[derive(Clone, Debug, PartialEq)]
37pub struct TokenChoiceController<C> {
38 inner: C,
39 domain: TokenDomain,
40 pending: Option<u32>,
41 last_forced: bool,
42}
43
44impl<C: TokenFilterController> TokenChoiceController<C> {
45 pub fn new(inner: C, domain: TokenDomain) -> Self {
47 Self {
48 inner,
49 domain,
50 pending: None,
51 last_forced: false,
52 }
53 }
54 pub fn force_next(&mut self, token: u32) -> Result<(), TokenChoiceError<C::Error>> {
57 if self.pending.is_some() {
58 return Err(TokenChoiceError::AlreadyPending);
59 }
60 if token as usize >= self.domain.cardinality() {
61 return Err(TokenChoiceError::InvalidToken(token));
62 }
63 let filter = self
64 .inner
65 .current_filter()
66 .map_err(TokenChoiceError::Constraint)?;
67 Self::check_filter(&filter, token)?;
68 self.pending = Some(token);
69 Ok(())
70 }
71 pub fn clear_forced(&mut self) -> bool {
73 self.pending.take().is_some()
74 }
75 pub fn pending_forced(&self) -> Option<u32> {
77 self.pending
78 }
79 pub fn last_committed_was_forced(&self) -> bool {
81 self.last_forced
82 }
83 pub fn inner(&self) -> &C {
85 &self.inner
86 }
87 fn check_filter(filter: &TokenFilter, token: u32) -> Result<(), TokenChoiceError<C::Error>> {
88 if filter
89 .allowed_mask()
90 .is_some_and(|mask| !mask.get(token as usize).copied().unwrap_or(false))
91 {
92 Err(TokenChoiceError::Forbidden(token))
93 } else {
94 Ok(())
95 }
96 }
97}
98impl<C: TokenFilterController> TokenFilterController for TokenChoiceController<C> {
99 type Error = TokenChoiceError<C::Error>;
100 fn current_filter(&mut self) -> Result<TokenFilter, Self::Error> {
101 let filter = self
102 .inner
103 .current_filter()
104 .map_err(TokenChoiceError::Constraint)?;
105 let Some(token) = self.pending else {
106 return Ok(filter);
107 };
108 Self::check_filter(&filter, token)?;
109 let mut allowed = vec![
110 false;
111 filter
112 .allowed_mask()
113 .map_or(self.domain.cardinality(), <[bool]>::len)
114 ];
115 allowed[token as usize] = true;
116 Ok(TokenFilter::allowed(allowed).expect("one canonical candidate is allowed"))
117 }
118 fn commit_token(&mut self, token: u32) -> Result<(), Self::Error> {
119 if let Some(expected) = self.pending {
120 if expected != token {
121 return Err(TokenChoiceError::UnexpectedCommit {
122 expected,
123 actual: token,
124 });
125 }
126 }
127 self.inner
128 .commit_token(token)
129 .map_err(TokenChoiceError::Constraint)?;
130 self.last_forced = self.pending.take().is_some();
131 Ok(())
132 }
133 fn is_complete(&mut self) -> Result<bool, Self::Error> {
134 self.inner
135 .is_complete()
136 .map_err(TokenChoiceError::Constraint)
137 }
138}
139impl<C: SnapshotTokenController> SnapshotTokenController for TokenChoiceController<C> {
140 fn snapshot_storage_bytes(&self) -> Option<u64> {
141 self.inner
142 .snapshot_storage_bytes()?
143 .checked_add(std::mem::size_of::<Self>() as u64)
144 }
145 fn fork_snapshot(&self) -> Result<Self, String> {
146 Ok(Self {
147 inner: self.inner.fork_snapshot()?,
148 domain: self.domain,
149 pending: self.pending,
150 last_forced: self.last_forced,
151 })
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use std::convert::Infallible;
159
160 #[derive(Clone)]
161 struct Grammar(Vec<u32>);
162 impl TokenFilterController for Grammar {
163 type Error = Infallible;
164 fn current_filter(&mut self) -> Result<TokenFilter, Infallible> {
165 let mut allowed = vec![true; 4];
166 allowed[self.0.len() % 4] = false;
167 Ok(TokenFilter::allowed(allowed).unwrap())
168 }
169 fn commit_token(&mut self, token: u32) -> Result<(), Infallible> {
170 self.0.push(token);
171 Ok(())
172 }
173 fn is_complete(&mut self) -> Result<bool, Infallible> {
174 Ok(false)
175 }
176 }
177 impl SnapshotTokenController for Grammar {
178 fn snapshot_storage_bytes(&self) -> Option<u64> {
179 Some(24 + 4 * self.0.len() as u64)
180 }
181 fn fork_snapshot(&self) -> Result<Self, String> {
182 Ok(self.clone())
183 }
184 }
185
186 #[test]
187 fn choices_validate_constraints_and_commit_once_with_snapshot_isolation() {
188 let mut parent = TokenChoiceController::new(Grammar(vec![]), TokenDomain::new(4));
189 assert!(matches!(
190 parent.force_next(4),
191 Err(TokenChoiceError::InvalidToken(4))
192 ));
193 assert!(matches!(
194 parent.force_next(0),
195 Err(TokenChoiceError::Forbidden(0))
196 ));
197 assert!(parent.pending_forced().is_none());
198 parent.force_next(2).unwrap();
199 assert_eq!(
200 parent.current_filter().unwrap().allowed_mask(),
201 Some(&[false, false, true, false][..])
202 );
203 assert!(matches!(
204 parent.force_next(3),
205 Err(TokenChoiceError::AlreadyPending)
206 ));
207 let mut child = parent.fork_snapshot().unwrap();
208 assert!(matches!(
209 parent.commit_token(3),
210 Err(TokenChoiceError::UnexpectedCommit { .. })
211 ));
212 assert!(parent.inner().0.is_empty());
213 parent.commit_token(2).unwrap();
214 assert!(parent.last_committed_was_forced());
215 assert!(parent.pending_forced().is_none());
216 assert_eq!(parent.inner().0, [2]);
217 assert_eq!(child.pending_forced(), Some(2));
218 assert!(child.clear_forced());
219 child.commit_token(3).unwrap();
220 assert!(!child.last_committed_was_forced());
221 assert_eq!(child.inner().0, [3]);
222 assert_eq!(parent.inner().0, [2]);
223 assert!(matches!(
224 parent.force_next(1),
225 Err(TokenChoiceError::Forbidden(1))
226 ));
227 parent.commit_token(3).unwrap();
228 assert!(!parent.last_committed_was_forced());
229 assert_eq!(parent.inner().0, [2, 3]);
230 }
231}