use alloc::vec::Vec;
use crate::Machine;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TransitionRecord<M, E, C> {
pub step: usize,
pub from: M,
pub to: M,
pub event: E,
pub commands: Vec<C>,
pub timestamp: std::time::SystemTime,
}
impl<M, E, C> TransitionRecord<M, E, C> {
#[must_use]
pub fn new(step: usize, from: M, to: M, event: E, commands: Vec<C>) -> Self {
Self {
step,
from,
to,
event,
commands,
timestamp: std::time::SystemTime::now(),
}
}
}
#[derive(Debug, Default)]
pub struct InMemoryJournal<M, E, C> {
records: Vec<TransitionRecord<M, E, C>>,
}
impl<M, E, C> InMemoryJournal<M, E, C> {
#[must_use]
pub const fn new() -> Self {
Self {
records: Vec::new(),
}
}
#[must_use]
pub fn len(&self) -> usize {
self.records.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.records.is_empty()
}
#[must_use]
pub fn records(&self) -> &[TransitionRecord<M, E, C>] {
&self.records
}
pub fn append(&mut self, record: TransitionRecord<M, E, C>) {
self.records.push(record);
}
#[must_use]
pub fn last(&self) -> Option<&TransitionRecord<M, E, C>> {
self.records.last()
}
pub fn record_step(&mut self, from: &M, to: &M, event: &E, commands: &[C])
where
M: Clone,
E: Clone,
C: Clone,
{
let step = self.records.len();
self.append(TransitionRecord::new(
step,
from.clone(),
to.clone(),
event.clone(),
commands.to_vec(),
));
}
pub fn transitions_from<'a>(
&'a self,
mode: &'a M,
) -> impl Iterator<Item = &'a TransitionRecord<M, E, C>> + 'a
where
M: PartialEq,
{
self.records.iter().filter(move |r| &r.from == mode)
}
pub fn transitions_to<'a>(
&'a self,
mode: &'a M,
) -> impl Iterator<Item = &'a TransitionRecord<M, E, C>> + 'a
where
M: PartialEq,
{
self.records.iter().filter(move |r| &r.to == mode)
}
pub fn replay<Mac>(&self, machine: &Mac, initial_mode: M) -> Result<M, ReplayError>
where
Mac: Machine<Mode = M, Event = E, Command = C>,
M: Clone + PartialEq,
C: PartialEq,
{
self.replay_with_error::<core::convert::Infallible, _>(machine, initial_mode)
}
pub fn replay_with_error<Err, Mac>(
&self,
machine: &Mac,
initial_mode: M,
) -> Result<M, ReplayError>
where
Mac: Machine<Err, Mode = M, Event = E, Command = C>,
M: Clone + PartialEq,
C: PartialEq,
{
let mut mode = initial_mode;
for record in &self.records {
if mode != record.from {
return Err(ReplayError::new(record.step, ReplayMismatch::FromMode));
}
let decision = machine.decide(&mode, &record.event);
let (next_mode, commands) = decision.apply(mode.clone());
if next_mode != record.to {
return Err(ReplayError::new(record.step, ReplayMismatch::ToMode));
}
if commands.as_slice() != record.commands.as_slice() {
return Err(ReplayError::new(record.step, ReplayMismatch::Commands));
}
mode = next_mode;
}
Ok(mode)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ReplayMismatch {
FromMode,
ToMode,
Commands,
}
impl core::fmt::Display for ReplayMismatch {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::FromMode => {
write!(f, "current mode differs from recorded starting mode")
}
Self::ToMode => {
write!(f, "computed next mode differs from recorded mode")
}
Self::Commands => {
write!(f, "computed commands differ from recorded commands")
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReplayError {
step: usize,
mismatch: ReplayMismatch,
}
impl core::fmt::Display for ReplayError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"replay diverged at step {}: {}",
self.step, self.mismatch,
)
}
}
impl std::error::Error for ReplayError {}
impl ReplayError {
#[must_use]
pub const fn new(step: usize, mismatch: ReplayMismatch) -> Self {
Self { step, mismatch }
}
#[must_use]
pub const fn step(&self) -> usize {
self.step
}
#[must_use]
pub const fn mismatch(&self) -> ReplayMismatch {
self.mismatch
}
}