pub(super) mod evaluation;
pub(super) mod execute;
pub(super) mod node_body;
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, Copy, PartialEq, Eq)]
pub(super) enum State {
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<StmtList>;
pub struct Runner<S: VariableStorage> {
pub(super) program: Program,
pub(super) storage: S,
pub(super) state: State,
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 {
match self.state {
State::Idle => RunnerPhase::Idle,
State::Running => RunnerPhase::Running,
State::AwaitingOption => RunnerPhase::AwaitingOption,
State::Done => RunnerPhase::Done,
}
}
#[must_use]
pub fn new(program: Program, storage: S) -> Self {
Self {
program,
storage,
state: State::Idle,
stack: Vec::new(),
pending: VecDeque::new(),
option_bodies: Vec::new(),
library: FunctionLibrary::new(),
visits: HashMap::new(),
once_seen: HashSet::new(),
saliency: Box::new(FirstAvailable),
provider: Box::new(PassthroughProvider),
}
}
pub fn start(&mut self, node: &str) -> Result<()> {
if !self.program.node_exists(node) {
return Err(DialogueError::UnknownNode(node.to_owned()));
}
let body = self.pick_node_body(node)?;
self.clear_event_queues();
let title: Arc<str> = Arc::from(node);
self.stack.clear();
self.stack.push(Frame::new(title, body));
self.state = State::Running;
*self.visits.entry(node.to_owned()).or_insert(0) += 1;
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 {
State::Idle | State::Done => Ok(None),
State::AwaitingOption => Err(DialogueError::ProtocolViolation(
"call select_option() before next_event()".into(),
)),
State::Running => loop {
if let Some(ev) = self.pending.pop_front() {
return Ok(Some(ev));
}
if self.state != State::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 != State::AwaitingOption {
return Err(DialogueError::ProtocolViolation(
"select_option() called when not awaiting an option".into(),
));
}
let body = self.option_bodies.get(index).cloned().ok_or_else(|| {
DialogueError::ProtocolViolation(format!(
"option index {index} out of range ({})",
self.option_bodies.len()
))
})?;
self.option_bodies.clear();
self.state = State::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));
}
#[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);
}
pub(crate) fn set_saliency_box(&mut self, strategy: Box<dyn SaliencyStrategy>) {
self.saliency = strategy;
}
pub(crate) fn set_provider_box(&mut self, provider: Box<dyn LineProvider>) {
self.provider = provider;
}
#[must_use]
pub fn snapshot(&self) -> crate::runtime::RunnerSnapshot {
crate::runtime::RunnerSnapshot {
current_node: self.stack.last().map(|f| f.node.as_ref().to_owned()),
visits: self.visits.clone(),
once_seen: self.once_seen.clone(),
}
}
pub fn restore(&mut self, snapshot: crate::runtime::RunnerSnapshot) -> Result<()> {
self.visits = snapshot.visits;
self.once_seen = snapshot.once_seen;
self.stack.clear();
self.clear_event_queues();
self.state = State::Idle;
if let Some(node) = snapshot.current_node {
self.start(&node)?;
}
Ok(())
}
}