#![deny(missing_docs)]
use std::{collections::HashMap, convert::Infallible, io::Error as IoError, path::Path};
use event_simulation::{Simulation, SimulationInfo, SimulationState};
use header_parsing::parse_header;
use multilinear::{MultilinearInfo, MultilinearSimulation};
use multilinear_parser::{NamedMultilinearInfo, parse_multilinear, parse_multilinear_extended};
use thiserror::Error;
pub use multilinear::{Aspect, Event};
pub struct Presentation {
pub owner: Box<str>,
pub dialog: Box<str>,
}
#[derive(Debug, Error)]
pub enum LoadError {
#[error("failed to parse story net: {0}")]
Net(String),
#[error("failed to read wiring file: {0}")]
Wiring(#[from] IoError),
#[error("wiring references unknown event: {0}")]
UnknownEvent(Box<str>),
}
pub struct Story {
simulation: MultilinearSimulation,
events: HashMap<Box<str>, Event>,
aspects: HashMap<Box<str>, (Aspect, Vec<Box<str>>)>,
presentation: HashMap<Event, Presentation>,
idle: HashMap<Box<str>, Box<str>>,
}
impl Story {
pub fn load(net: &Path, wiring: &Path) -> Result<Self, LoadError> {
let named = parse_multilinear_extended(net, None)
.map_err(|error| LoadError::Net(error.to_string()))?;
let wiring = parse_wiring(wiring)?;
Self::from_parts(named, wiring)
}
pub fn load_with_state(
net: &Path,
wiring: &Path,
state: Vec<usize>,
) -> Result<Self, LoadError> {
let named = parse_multilinear_extended(net, None)
.map_err(|error| LoadError::Net(error.to_string()))?;
let wiring = parse_wiring(wiring)?;
Self::from_parts_with_state(named, wiring, Some(state))
}
pub fn state(&self) -> Vec<usize> {
self.simulation.data().to_vec()
}
pub fn load_from_str(net: &str, wiring: &str) -> Result<Self, LoadError> {
let named =
parse_multilinear(net.as_bytes()).map_err(|error| LoadError::Net(error.to_string()))?;
Self::from_parts(named, parse_wiring_str(wiring))
}
fn from_parts(named: NamedMultilinearInfo, wiring: Wiring) -> Result<Self, LoadError> {
Self::from_parts_with_state(named, wiring, None)
}
fn from_parts_with_state(
named: NamedMultilinearInfo,
wiring: Wiring,
state: Option<Vec<usize>>,
) -> Result<Self, LoadError> {
let events: HashMap<Box<str>, Event> = (&named.events)
.into_iter()
.map(|(event, namespace)| (join_namespace(namespace), event))
.collect();
let aspects: HashMap<Box<str>, (Aspect, Vec<Box<str>>)> = (&named.aspects)
.into_iter()
.map(|(aspect, (name, values))| (name.clone(), (aspect, values.clone())))
.collect();
let Wiring {
events: wired,
idle,
} = wiring;
let mut presentation = HashMap::new();
for (name, entry) in wired {
let Some(&event) = events.get(&name) else {
return Err(LoadError::UnknownEvent(name));
};
presentation.insert(event, entry);
}
for (name, event) in &events {
if !presentation.contains_key(event) {
eprintln!("warning: net event has no wiring (never surfaced): {name}");
}
}
let simulation = match state {
Some(data) => MultilinearSimulation::from_data(named.info, data)
.unwrap_or_else(|error: Infallible| match error {}),
None => MultilinearSimulation::new(named.info),
};
Ok(Self {
simulation,
events,
aspects,
presentation,
idle,
})
}
pub fn empty() -> Self {
Self {
simulation: MultilinearSimulation::new(MultilinearInfo::default()),
events: HashMap::new(),
aspects: HashMap::new(),
presentation: HashMap::new(),
idle: HashMap::new(),
}
}
pub fn event(&self, name: &str) -> Option<Event> {
self.events.get(name).copied()
}
pub fn aspects(&self) -> impl Iterator<Item = (&str, Aspect)> {
self.aspects
.iter()
.map(|(name, (aspect, _))| (name.as_ref(), *aspect))
}
pub fn aspect(&self, name: &str) -> Option<Aspect> {
self.aspects.get(name).map(|(aspect, _)| *aspect)
}
pub fn value(&self, aspect: Aspect) -> usize {
self.simulation.data()[aspect.0]
}
pub fn value_name(&self, name: &str) -> Option<&str> {
let (aspect, values) = self.aspects.get(name)?;
values
.get(self.simulation.data()[aspect.0])
.map(AsRef::as_ref)
}
pub fn values(&self, name: &str) -> Option<impl Iterator<Item = &str>> {
let (_, values) = self.aspects.get(name)?;
Some(values.iter().map(AsRef::as_ref))
}
pub fn set_value(&mut self, name: &str, value: &str) -> bool {
let Some((aspect, values)) = self.aspects.get(name) else {
return false;
};
let Some(index) = values
.iter()
.position(|candidate| candidate.as_ref() == value)
else {
return false;
};
let mut data = self.simulation.data().to_vec();
data[aspect.0] = index;
let state = self
.simulation
.load_state(data)
.unwrap_or_else(|error: Infallible| match error {});
self.simulation.state = state;
true
}
pub fn callable(&self, event: Event) -> bool {
self.simulation.callable(event)
}
pub fn callable_events(&self) -> Vec<Event> {
let mut events: Vec<Event> = self.simulation.callables().collect();
events.sort();
events
}
pub fn options(&self, owner: &str) -> Vec<Event> {
let mut events: Vec<Event> = self
.simulation
.callables()
.filter(|event| {
self.presentation
.get(event)
.is_some_and(|presentation| presentation.owner.as_ref() == owner)
})
.collect();
events.sort();
events
}
pub fn presentation(&self, event: Event) -> Option<&Presentation> {
self.presentation.get(&event)
}
pub fn dialog(&self, event: Event) -> Option<&str> {
self.presentation.get(&event).map(|p| p.dialog.as_ref())
}
pub fn owner(&self, event: Event) -> Option<&str> {
self.presentation.get(&event).map(|p| p.owner.as_ref())
}
pub fn idle(&self, owner: &str) -> Option<&str> {
self.idle.get(owner).map(AsRef::as_ref)
}
pub fn play(&mut self, event: Event) -> bool {
self.simulation.try_call(event)
}
pub fn revert(&mut self, event: Event) -> bool {
self.simulation.try_revert(event)
}
}
fn join_namespace(namespace: &[Box<str>]) -> Box<str> {
namespace
.iter()
.map(AsRef::as_ref)
.collect::<Vec<&str>>()
.join(" ")
.into_boxed_str()
}
struct Wiring {
events: HashMap<Box<str>, Presentation>,
idle: HashMap<Box<str>, Box<str>>,
}
fn parse_wiring(path: &Path) -> Result<Wiring, IoError> {
let content = std::fs::read_to_string(path)?;
Ok(parse_wiring_str(&content))
}
fn parse_wiring_str(content: &str) -> Wiring {
let mut events: HashMap<Box<str>, Presentation> = HashMap::new();
let mut idle: HashMap<Box<str>, Box<str>> = HashMap::new();
let mut header_path: Vec<Box<str>> = Vec::new();
for line in content.lines() {
if let Some(result) = parse_header(&mut header_path, line) {
match result {
Ok(changes) => {
changes.apply();
}
Err(_) => eprintln!("invalid header in wiring: {line}"),
}
continue;
}
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
let mut tokens = trimmed.splitn(2, char::is_whitespace);
let Some(key) = tokens.next() else { continue };
let value = tokens.next().unwrap_or("").trim();
match header_path.as_slice() {
[section, event] if section.as_ref() == "events" => {
let entry = events.entry(event.clone()).or_insert_with(|| Presentation {
owner: Box::from(""),
dialog: Box::from(""),
});
match key {
"owner" => entry.owner = Box::from(value),
"dialog" => entry.dialog = Box::from(value),
_ => {}
}
}
[section, owner] if section.as_ref() == "idle" && key == "dialog" => {
idle.insert(owner.clone(), Box::from(value));
}
_ => {}
}
}
Wiring { events, idle }
}