pub(super) mod evaluation;
pub(super) mod execute;
pub(super) mod node_body;
mod session;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::Arc;
use crate::compiler::Program;
use crate::compiler::ast::StmtList;
use crate::error::{DialogueError, Result};
use crate::library::FunctionLibrary;
use crate::runtime::event::DialogueEvent;
use crate::runtime::provider::{LineProvider, PassthroughProvider};
use crate::saliency::{FirstAvailable, SaliencyStrategy};
use crate::value::{Value, VariableStorage};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RunnerPhase {
Idle,
Running,
AwaitingOption,
Done,
}
#[derive(Debug, Clone)]
pub(super) struct Frame {
pub(super) node: Arc<str>,
pub(super) body: StmtList,
pub(super) ip: usize,
}
impl Frame {
pub(super) const fn new(node: Arc<str>, body: StmtList) -> Self {
Self { node, body, ip: 0 }
}
}
type OptionBodies = Vec<(bool, Option<String>, StmtList)>;
pub struct Runner<S: VariableStorage> {
pub(super) program: Program,
pub(super) storage: S,
pub(super) state: RunnerPhase,
pub(super) stack: Vec<Frame>,
pub(super) pending: VecDeque<DialogueEvent>,
pub(super) option_bodies: OptionBodies,
pub(super) library: FunctionLibrary,
pub(super) visits: HashMap<String, u32>,
pub(super) once_seen: HashSet<String>,
pub(super) saliency: Box<dyn SaliencyStrategy>,
pub(super) provider: Box<dyn LineProvider>,
}
impl<S: VariableStorage> Runner<S> {
fn clear_event_queues(&mut self) {
self.pending.clear();
self.option_bodies.clear();
}
#[must_use]
pub const fn program(&self) -> &Program {
&self.program
}
#[must_use]
pub const fn phase(&self) -> RunnerPhase {
self.state
}
#[must_use]
pub fn new(program: Program, storage: S) -> Self {
Self::with_parts(
program,
storage,
Box::new(FirstAvailable),
Box::new(PassthroughProvider),
FunctionLibrary::new(),
)
}
pub(super) fn with_parts(
program: Program,
storage: S,
saliency: Box<dyn SaliencyStrategy>,
provider: Box<dyn LineProvider>,
library: FunctionLibrary,
) -> Self {
Self {
program,
storage,
state: RunnerPhase::Idle,
stack: Vec::new(),
pending: VecDeque::new(),
option_bodies: Vec::new(),
library,
visits: HashMap::new(),
once_seen: HashSet::new(),
saliency,
provider,
}
}
pub fn start(&mut self, node: &str) -> Result<()> {
let body = self.pick_node_body(node)?;
self.clear_event_queues();
self.stack.clear();
self.push_node_frame(node, body);
self.state = RunnerPhase::Running;
self.record_visit(node);
self.pending
.push_back(DialogueEvent::NodeStarted(node.to_owned()));
Ok(())
}
pub fn next_event(&mut self) -> Result<Option<DialogueEvent>> {
if let Some(ev) = self.pending.pop_front() {
return Ok(Some(ev));
}
match self.state {
RunnerPhase::Idle | RunnerPhase::Done => Ok(None),
RunnerPhase::AwaitingOption => Err(DialogueError::ProtocolViolation(
"call select_option() before next_event()".into(),
)),
RunnerPhase::Running => loop {
if let Some(ev) = self.pending.pop_front() {
return Ok(Some(ev));
}
if self.state != RunnerPhase::Running {
return Ok(None);
}
if let Some(ev) = self.step()? {
return Ok(Some(ev));
}
},
}
}
pub fn select_option(&mut self, index: usize) -> Result<()> {
if self.state != RunnerPhase::AwaitingOption {
return Err(DialogueError::ProtocolViolation(
"select_option() called when not awaiting an option".into(),
));
}
let Some(&(available, _, _)) = self.option_bodies.get(index) else {
return Err(DialogueError::ProtocolViolation(format!(
"option index {index} out of range ({})",
self.option_bodies.len()
)));
};
if !available {
return Err(DialogueError::ProtocolViolation(format!(
"option index {index} is unavailable (guard not satisfied)"
)));
}
let (_, once_id, body) = std::mem::take(&mut self.option_bodies).swap_remove(index);
if let Some(id) = once_id {
self.once_seen.insert(id);
}
self.state = RunnerPhase::Running;
self.push_inline_frame(body);
Ok(())
}
pub(super) fn push_inline_frame(&mut self, body: StmtList) {
if body.is_empty() {
return;
}
let title = self
.stack
.last()
.map_or_else(|| Arc::from(""), |f| Arc::clone(&f.node));
self.stack.push(Frame::new(title, body));
}
pub(super) fn push_node_frame(&mut self, node: &str, body: StmtList) {
self.stack.push(Frame::new(Arc::from(node), body));
}
pub(super) fn record_visit(&mut self, node: &str) {
if let Some(count) = self.visits.get_mut(node) {
*count += 1;
} else {
self.visits.insert(node.to_owned(), 1);
}
}
#[must_use]
pub const fn storage(&self) -> &S {
&self.storage
}
pub const fn storage_mut(&mut self) -> &mut S {
&mut self.storage
}
#[must_use]
pub fn all_variables(&self) -> Vec<(String, Value)> {
self.storage.all_variables()
}
#[must_use]
pub fn variable(&self, name: &str) -> Option<Value> {
self.storage.get(name)
}
#[must_use]
pub fn variable_ref(&self, name: &str) -> Option<Cow<'_, Value>> {
self.storage.get_ref(name)
}
pub const fn library_mut(&mut self) -> &mut FunctionLibrary {
&mut self.library
}
pub fn set_saliency(&mut self, strategy: impl SaliencyStrategy) {
self.saliency = Box::new(strategy);
}
pub fn set_provider(&mut self, provider: impl LineProvider) {
self.provider = Box::new(provider);
}
}