event-simulation 0.2.1

A library for event based simulation of application state
Documentation
//! A minimal `SimulationInfo` implementation modeling a text adventure.
//!
//! The world is a set of rooms connected by passages. Each passage is an event:
//! calling it walks forward into the target room, reverting it walks back to the
//! source room. The access data is the current room index, which the application
//! looks up in the info to render — info-side rendering of state-side data, the
//! same split `petri-net-simulation` and `multilinear` use.
//!
//! This mirrors the pattern used by real consumers like `petri-net-simulation`
//! and `multilinear`: `callables`/`revertables` read a cache stored in the state,
//! while `call`/`revert` recompute that cache from the info after mutating.
//!
//! Run with `cargo run --example text_adventure`.

use std::convert::Infallible;

use event_simulation::{OwnedSimulation, Simulation, SimulationInfo, SimulationState};

/// An event: an index into [`Adventure::passages`].
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct Choice(usize);

/// A directed connection between two rooms, shown to the player as a choice.
struct Passage {
    text: String,
    from: usize,
    to: usize,
}

/// The immutable world definition. Shared by every state derived from it.
struct Adventure {
    rooms: Vec<String>,
    passages: Vec<Passage>,
}

impl Adventure {
    fn choices_from(&self, room: usize) -> Vec<Choice> {
        self.matching(|passage| passage.from == room)
    }

    fn choices_into(&self, room: usize) -> Vec<Choice> {
        self.matching(|passage| passage.to == room)
    }

    fn matching(&self, predicate: impl Fn(&Passage) -> bool) -> Vec<Choice> {
        self.passages
            .iter()
            .enumerate()
            .filter(|(_, passage)| predicate(passage))
            .map(|(index, _)| Choice(index))
            .collect()
    }
}

/// The mutable state: the current room plus the cached set of available events.
/// The caches exist because the trait derives `callables`/`revertables` from the
/// state alone, with no access to the info.
#[derive(Clone)]
struct Position {
    room: usize,
    callables: Vec<Choice>,
    revertables: Vec<Choice>,
}

impl Position {
    fn at(adventure: &Adventure, room: usize) -> Self {
        Self {
            room,
            callables: adventure.choices_from(room),
            revertables: adventure.choices_into(room),
        }
    }
}

impl SimulationInfo for Adventure {
    type State = Position;
    type StateLoadingError = Infallible;
    type AccessData = usize;
    type LoadData = usize;
    type Event = Choice;
    type EventContainer<'a> = std::vec::IntoIter<Choice>;

    fn default_state(&self) -> Position {
        Position::at(self, 0)
    }

    fn load_state(&self, room: usize) -> Result<Position, Infallible> {
        Ok(Position::at(self, room.min(self.rooms.len() - 1)))
    }

    unsafe fn clone_state(&self, state: &Position) -> Position {
        state.clone()
    }

    unsafe fn data<'a>(&self, state: &'a Position) -> &'a usize {
        &state.room
    }

    fn callables(state: &Position) -> std::vec::IntoIter<Choice> {
        state.callables.clone().into_iter()
    }

    fn revertables(state: &Position) -> std::vec::IntoIter<Choice> {
        state.revertables.clone().into_iter()
    }

    fn callable(state: &Position, event: Choice) -> bool {
        state.callables.contains(&event)
    }

    fn revertable(state: &Position, event: Choice) -> bool {
        state.revertables.contains(&event)
    }

    unsafe fn call(&self, state: &mut Position, Choice(index): Choice) {
        *state = Position::at(self, self.passages[index].to);
    }

    unsafe fn revert(&self, state: &mut Position, Choice(index): Choice) {
        *state = Position::at(self, self.passages[index].from);
    }
}

fn passage(text: &str, from: usize, to: usize) -> Passage {
    Passage {
        text: text.into(),
        from,
        to,
    }
}

fn main() {
    let adventure = Adventure {
        rooms: vec![
            "You stand at a crossroads.".into(),
            "A dark cave yawns before you.".into(),
            "Sunlight breaks over a quiet meadow.".into(),
        ],
        passages: vec![
            passage("Enter the cave", 0, 1),
            passage("Walk to the meadow", 0, 2),
            passage("Retreat from the cave", 1, 0),
        ],
    };

    let mut simulation = OwnedSimulation::<Adventure>::new(adventure);

    let describe =
        |simulation: &OwnedSimulation<Adventure>| simulation.rooms[*simulation.data()].clone();

    println!("{}", describe(&simulation));
    for choice in simulation.callables() {
        println!("{}", simulation.passages[choice.0].text);
    }

    let Some(first) = simulation.callables().next() else {
        panic!("crossroads has choices");
    };
    assert!(simulation.try_call(first));
    assert_eq!(simulation.state.room, 1);
    println!("\n{}", describe(&simulation));

    let Some(back) = simulation.revertables().next() else {
        panic!("cave can be left");
    };
    assert!(simulation.try_revert(back));
    assert_eq!(simulation.state.room, 0);
    println!("\nBack: {}", describe(&simulation));
}