bubbles/runtime/runner/
mod.rs1pub(super) mod evaluation;
4pub(super) mod execute;
5pub(super) mod node_body;
6
7use std::collections::{HashMap, HashSet, VecDeque};
8use std::sync::{Arc, Mutex};
9
10use crate::compiler::Program;
11use crate::compiler::ast::Stmt;
12use crate::error::{DialogueError, Result};
13use crate::library::FunctionLibrary;
14use crate::runtime::event::DialogueEvent;
15use crate::runtime::provider::{LineProvider, PassthroughProvider};
16use crate::saliency::{FirstAvailable, SaliencyStrategy};
17use crate::value::{Value, VariableStorage};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub(super) enum State {
22 Idle,
23 Running,
24 AwaitingOption,
25 Done,
26}
27
28#[derive(Debug, Clone)]
30pub(super) struct Frame {
31 pub(super) node: String,
32 pub(super) stmts: VecDeque<Stmt>,
33}
34
35impl Frame {
36 pub(super) fn new(node: String, body: Vec<Stmt>) -> Self {
37 Self {
38 node,
39 stmts: VecDeque::from(body),
40 }
41 }
42}
43
44type OptionBodies = Vec<Vec<Stmt>>;
46
47pub struct Runner<S: VariableStorage> {
54 pub(super) program: Program,
55 pub(super) storage: S,
56 pub(super) state: State,
57 pub(super) stack: Vec<Frame>,
58 pub(super) pending: VecDeque<DialogueEvent>,
59 pub(super) option_bodies: OptionBodies,
60 pub(super) library: FunctionLibrary,
61 pub(super) visits: Arc<Mutex<HashMap<String, usize>>>,
62 pub(super) once_seen: HashSet<String>,
63 pub(super) saliency: Box<dyn SaliencyStrategy>,
64 pub(super) provider: Box<dyn LineProvider>,
65}
66
67impl<S: VariableStorage> Runner<S> {
68 #[must_use]
73 pub fn new(program: Program, storage: S) -> Self {
74 let visits: Arc<Mutex<HashMap<String, usize>>> = Arc::new(Mutex::new(HashMap::new()));
75 let mut library = FunctionLibrary::new();
76
77 let v1 = Arc::clone(&visits);
78 library.register("visited", move |args| {
79 let title = match args.as_slice() {
80 [Value::Text(t)] => t.clone(),
81 _ => {
82 return Err(DialogueError::Function {
83 name: "visited".into(),
84 message: "expected one string argument".into(),
85 });
86 }
87 };
88 Ok(Value::Bool(
89 *v1.lock().unwrap().get(&title).unwrap_or(&0) > 0,
90 ))
91 });
92 let v2 = Arc::clone(&visits);
93 library.register("visited_count", move |args| {
94 let title = match args.as_slice() {
95 [Value::Text(t)] => t.clone(),
96 _ => {
97 return Err(DialogueError::Function {
98 name: "visited_count".into(),
99 message: "expected one string argument".into(),
100 });
101 }
102 };
103 let count = *v2.lock().unwrap().get(&title).unwrap_or(&0);
104 #[allow(clippy::cast_precision_loss)]
105 Ok(Value::Number(count as f64))
106 });
107
108 Self {
109 program,
110 storage,
111 state: State::Idle,
112 stack: Vec::new(),
113 pending: VecDeque::new(),
114 option_bodies: Vec::new(),
115 library,
116 visits,
117 once_seen: HashSet::new(),
118 saliency: Box::new(FirstAvailable),
119 provider: Box::new(PassthroughProvider),
120 }
121 }
122
123 pub fn start(&mut self, node: &str) -> Result<()> {
131 if !self.program.node_exists(node) {
132 return Err(DialogueError::UnknownNode(node.to_owned()));
133 }
134 let body = self.pick_node_body(node)?;
135 self.stack.clear();
136 self.stack.push(Frame::new(node.to_owned(), body));
137 self.state = State::Running;
138 *self
139 .visits
140 .lock()
141 .unwrap()
142 .entry(node.to_owned())
143 .or_insert(0) += 1;
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 State::Idle | State::Done => Ok(None),
159 State::AwaitingOption => Err(DialogueError::Runtime(
160 "call select_option() before next_event()".into(),
161 )),
162 State::Running => loop {
163 if let Some(ev) = self.pending.pop_front() {
164 return Ok(Some(ev));
165 }
166 if self.state != State::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<()> {
182 if self.state != State::AwaitingOption {
183 return Err(DialogueError::Runtime(
184 "select_option() called when not awaiting an option".into(),
185 ));
186 }
187 let body = self.option_bodies.get(index).cloned().ok_or_else(|| {
188 DialogueError::Runtime(format!(
189 "option index {index} out of range ({})",
190 self.option_bodies.len()
191 ))
192 })?;
193 self.option_bodies.clear();
194 self.state = State::Running;
195 self.push_inline_frame(body);
196 Ok(())
197 }
198
199 pub(super) fn push_inline_frame(&mut self, body: Vec<Stmt>) {
204 if body.is_empty() {
205 return;
206 }
207 let title = self
208 .stack
209 .last()
210 .map(|f| f.node.clone())
211 .unwrap_or_default();
212 self.stack.push(Frame::new(title, body));
213 }
214
215 #[must_use]
217 pub const fn storage(&self) -> &S {
218 &self.storage
219 }
220
221 pub const fn storage_mut(&mut self) -> &mut S {
223 &mut self.storage
224 }
225
226 pub const fn library_mut(&mut self) -> &mut FunctionLibrary {
228 &mut self.library
229 }
230
231 pub fn set_saliency(&mut self, strategy: impl SaliencyStrategy) {
233 self.saliency = Box::new(strategy);
234 }
235
236 pub fn set_provider(&mut self, provider: impl LineProvider) {
238 self.provider = Box::new(provider);
239 }
240
241 #[cfg(feature = "serde")]
259 #[must_use]
260 pub fn snapshot(&self) -> crate::runtime::RunnerSnapshot {
261 crate::runtime::RunnerSnapshot {
262 current_node: self.stack.last().map(|f| f.node.clone()),
263 visits: self.visits.lock().unwrap().clone(),
264 once_seen: self.once_seen.clone(),
265 }
266 }
267
268 #[cfg(feature = "serde")]
283 pub fn restore(&mut self, snapshot: crate::runtime::RunnerSnapshot) -> Result<()> {
284 *self.visits.lock().unwrap() = snapshot.visits;
285 self.once_seen = snapshot.once_seen;
286 self.stack.clear();
287 self.pending.clear();
288 self.option_bodies.clear();
289 self.state = State::Idle;
290 if let Some(node) = snapshot.current_node {
291 self.start(&node)?;
292 }
293 Ok(())
294 }
295}