use super::action::Action;
use super::precondition::{Precondition, PreconditionResult};
use super::situation::Situation;
use super::trace::{Trace, TraceEntry};
#[derive(Debug)]
pub enum EngineError<A: Action> {
Violated {
engine: Engine<A>,
violations: Vec<PreconditionResult>,
},
LogicalError { engine: Engine<A>, reason: String },
}
#[allow(clippy::type_complexity)]
pub struct Engine<A: Action> {
situation: A::Sit,
past: Vec<A::Sit>,
future: Vec<A::Sit>,
preconditions: Vec<Box<dyn Precondition<A>>>,
apply_fn: Box<dyn Fn(&A::Sit, &A) -> Result<A::Sit, String>>,
trace: Trace,
}
impl<A: Action> std::fmt::Debug for Engine<A> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Engine")
.field("situation", &self.situation)
.field("step", &self.step())
.field("back_depth", &self.back_depth())
.field("forward_depth", &self.forward_depth())
.field("trace_entries", &self.trace.entries().len())
.finish()
}
}
impl<A: Action> Engine<A> {
pub fn new(
situation: A::Sit,
preconditions: Vec<Box<dyn Precondition<A>>>,
apply_fn: impl Fn(&A::Sit, &A) -> Result<A::Sit, String> + 'static,
) -> Self {
Self {
situation,
past: Vec::new(),
future: Vec::new(),
preconditions,
apply_fn: Box::new(apply_fn),
trace: Trace::new(),
}
}
pub fn step(&self) -> usize {
self.past.len()
}
pub fn situation(&self) -> &A::Sit {
&self.situation
}
pub fn trace(&self) -> &Trace {
&self.trace
}
pub fn is_terminal(&self) -> bool {
self.situation.is_terminal()
}
#[allow(clippy::result_large_err)]
pub fn next(mut self, action: A) -> Result<Self, EngineError<A>> {
let situation_before = self.situation.describe();
let action_desc = action.describe();
let step = self.step();
let results: Vec<PreconditionResult> = self
.preconditions
.iter()
.map(|p| p.check(&self.situation, &action))
.collect();
let violations: Vec<PreconditionResult> = results
.iter()
.filter(|r| !r.is_satisfied())
.cloned()
.collect();
if !violations.is_empty() {
self.trace.record(TraceEntry {
step,
situation_before,
action: action_desc,
precondition_results: results,
situation_after: None,
success: false,
});
return Err(EngineError::Violated {
engine: self,
violations,
});
}
match (self.apply_fn)(&self.situation, &action) {
Ok(new_situation) => {
let situation_after = new_situation.describe();
self.trace.record(TraceEntry {
step,
situation_before,
action: action_desc,
precondition_results: results,
situation_after: Some(situation_after),
success: true,
});
self.past.push(self.situation.clone());
self.future.clear();
self.situation = new_situation;
Ok(self)
}
Err(reason) => {
self.trace.record(TraceEntry {
step,
situation_before,
action: action_desc,
precondition_results: results,
situation_after: None,
success: false,
});
Err(EngineError::LogicalError {
engine: self,
reason,
})
}
}
}
pub fn back(mut self) -> Result<Self, Self> {
match self.past.pop() {
Some(previous) => {
self.future.push(self.situation.clone());
self.situation = previous;
Ok(self)
}
None => Err(self),
}
}
pub fn forward(mut self) -> Result<Self, Self> {
match self.future.pop() {
Some(next) => {
self.past.push(self.situation.clone());
self.situation = next;
Ok(self)
}
None => Err(self),
}
}
pub fn back_depth(&self) -> usize {
self.past.len()
}
pub fn forward_depth(&self) -> usize {
self.future.len()
}
pub fn try_next(self, action: A) -> Result<Self, Vec<String>> {
self.next(action).map_err(|e| match e {
EngineError::Violated { violations, .. } => violations
.into_iter()
.map(|v| match v {
PreconditionResult::Violated { rule, reason, .. } => {
format!("{}: {}", rule, reason)
}
PreconditionResult::Satisfied { .. } => unreachable!(),
})
.collect(),
EngineError::LogicalError { reason, .. } => {
vec![format!("logical error: {}", reason)]
}
})
}
}