bubbles/runtime/runner/
mod.rs1pub(super) mod evaluation;
4pub(super) mod execute;
5pub(super) mod node_body;
6mod session;
7
8use std::borrow::Cow;
9use std::collections::{HashMap, HashSet, VecDeque};
10use std::sync::Arc;
11
12use crate::compiler::Program;
13use crate::compiler::ast::StmtList;
14use crate::error::{DialogueError, Result};
15use crate::library::FunctionLibrary;
16use crate::runtime::event::DialogueEvent;
17use crate::runtime::provider::{LineProvider, PassthroughProvider};
18use crate::saliency::{FirstAvailable, SaliencyStrategy};
19use crate::value::{Value, VariableStorage};
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum RunnerPhase {
24 Idle,
26 Running,
28 AwaitingOption,
31 Done,
33}
34
35#[derive(Debug, Clone)]
42pub(super) struct Frame {
43 pub(super) node: Arc<str>,
44 pub(super) body: StmtList,
45 pub(super) ip: usize,
46}
47
48impl Frame {
49 pub(super) const fn new(node: Arc<str>, body: StmtList) -> Self {
50 Self { node, body, ip: 0 }
51 }
52}
53
54type OptionBodies = Vec<(bool, Option<String>, StmtList)>;
56
57pub struct Runner<S: VariableStorage> {
64 pub(super) program: Program,
65 pub(super) storage: S,
66 pub(super) state: RunnerPhase,
67 pub(super) stack: Vec<Frame>,
68 pub(super) pending: VecDeque<DialogueEvent>,
69 pub(super) option_bodies: OptionBodies,
70 pub(super) library: FunctionLibrary,
71 pub(super) visits: HashMap<String, u32>,
72 pub(super) once_seen: HashSet<String>,
73 pub(super) saliency: Box<dyn SaliencyStrategy>,
74 pub(super) provider: Box<dyn LineProvider>,
75}
76
77impl<S: VariableStorage> Runner<S> {
78 fn clear_event_queues(&mut self) {
79 self.pending.clear();
80 self.option_bodies.clear();
81 }
82
83 #[must_use]
85 pub const fn program(&self) -> &Program {
86 &self.program
87 }
88
89 #[must_use]
91 pub const fn phase(&self) -> RunnerPhase {
92 self.state
93 }
94
95 #[must_use]
97 pub fn new(program: Program, storage: S) -> Self {
98 Self::with_parts(
99 program,
100 storage,
101 Box::new(FirstAvailable),
102 Box::new(PassthroughProvider),
103 FunctionLibrary::new(),
104 )
105 }
106
107 pub(super) fn with_parts(
108 program: Program,
109 storage: S,
110 saliency: Box<dyn SaliencyStrategy>,
111 provider: Box<dyn LineProvider>,
112 library: FunctionLibrary,
113 ) -> Self {
114 Self {
115 program,
116 storage,
117 state: RunnerPhase::Idle,
118 stack: Vec::new(),
119 pending: VecDeque::new(),
120 option_bodies: Vec::new(),
121 library,
122 visits: HashMap::new(),
123 once_seen: HashSet::new(),
124 saliency,
125 provider,
126 }
127 }
128
129 pub fn start(&mut self, node: &str) -> Result<()> {
138 let body = self.pick_node_body(node)?;
139 self.clear_event_queues();
140 self.stack.clear();
141 self.push_node_frame(node, body);
142 self.state = RunnerPhase::Running;
143 self.record_visit(node);
144 self.pending
145 .push_back(DialogueEvent::NodeStarted(node.to_owned()));
146 Ok(())
147 }
148
149 pub fn next_event(&mut self) -> Result<Option<DialogueEvent>> {
154 if let Some(ev) = self.pending.pop_front() {
155 return Ok(Some(ev));
156 }
157 match self.state {
158 RunnerPhase::Idle | RunnerPhase::Done => Ok(None),
159 RunnerPhase::AwaitingOption => Err(DialogueError::ProtocolViolation(
160 "call select_option() before next_event()".into(),
161 )),
162 RunnerPhase::Running => loop {
163 if let Some(ev) = self.pending.pop_front() {
164 return Ok(Some(ev));
165 }
166 if self.state != RunnerPhase::Running {
167 return Ok(None);
168 }
169 if let Some(ev) = self.step()? {
170 return Ok(Some(ev));
171 }
172 },
173 }
174 }
175
176 pub fn select_option(&mut self, index: usize) -> Result<()> {
183 if self.state != RunnerPhase::AwaitingOption {
184 return Err(DialogueError::ProtocolViolation(
185 "select_option() called when not awaiting an option".into(),
186 ));
187 }
188 let Some(&(available, _, _)) = self.option_bodies.get(index) else {
189 return Err(DialogueError::ProtocolViolation(format!(
190 "option index {index} out of range ({})",
191 self.option_bodies.len()
192 )));
193 };
194 if !available {
195 return Err(DialogueError::ProtocolViolation(format!(
196 "option index {index} is unavailable (guard not satisfied)"
197 )));
198 }
199 let (_, once_id, body) = std::mem::take(&mut self.option_bodies).swap_remove(index);
200 if let Some(id) = once_id {
201 self.once_seen.insert(id);
202 }
203 self.state = RunnerPhase::Running;
204 self.push_inline_frame(body);
205 Ok(())
206 }
207
208 pub(super) fn push_inline_frame(&mut self, body: StmtList) {
213 if body.is_empty() {
214 return;
215 }
216 let title = self
217 .stack
218 .last()
219 .map_or_else(|| Arc::from(""), |f| Arc::clone(&f.node));
220 self.stack.push(Frame::new(title, body));
221 }
222
223 pub(super) fn push_node_frame(&mut self, node: &str, body: StmtList) {
229 self.stack.push(Frame::new(Arc::from(node), body));
230 }
231
232 pub(super) fn record_visit(&mut self, node: &str) {
235 if let Some(count) = self.visits.get_mut(node) {
236 *count += 1;
237 } else {
238 self.visits.insert(node.to_owned(), 1);
239 }
240 }
241
242 #[must_use]
244 pub const fn storage(&self) -> &S {
245 &self.storage
246 }
247
248 pub const fn storage_mut(&mut self) -> &mut S {
250 &mut self.storage
251 }
252
253 #[must_use]
259 pub fn all_variables(&self) -> Vec<(String, Value)> {
260 self.storage.all_variables()
261 }
262
263 #[must_use]
265 pub fn variable(&self, name: &str) -> Option<Value> {
266 self.storage.get(name)
267 }
268
269 #[must_use]
271 pub fn variable_ref(&self, name: &str) -> Option<Cow<'_, Value>> {
272 self.storage.get_ref(name)
273 }
274
275 pub const fn library_mut(&mut self) -> &mut FunctionLibrary {
277 &mut self.library
278 }
279
280 pub fn set_saliency(&mut self, strategy: impl SaliencyStrategy) {
282 self.saliency = Box::new(strategy);
283 }
284
285 pub fn set_provider(&mut self, provider: impl LineProvider) {
287 self.provider = Box::new(provider);
288 }
289}