simengine 0.2.7

A plugin-based simulation engine runtime and plugin API
Documentation
use crate::core::{
    EngineState, Manifest, SimulationState, StateTransitionCondition, StateTransitionConfig,
};
use std::collections::HashMap;

pub struct StateMachine {
    engine_state: EngineState,
    simulation_states: HashMap<String, SimulationState>,
    transitions: Vec<StateTransitionConfig>,
}

impl StateMachine {
    pub fn new(manifest: &Manifest) -> Self {
        let simulation_states = manifest
            .simulations
            .iter()
            .map(|sim| (sim.name.clone(), SimulationState::_START))
            .collect();

        Self {
            engine_state: EngineState::START,
            simulation_states,
            transitions: manifest.state_transitions.clone(),
        }
    }

    pub fn engine_state(&self) -> EngineState {
        self.engine_state
    }

    pub fn should_step(&self) -> bool {
        self.engine_state == EngineState::RUNNING
    }

    pub fn is_terminal(&self) -> bool {
        matches!(
            self.engine_state,
            EngineState::ERROR | EngineState::FINISHED
        )
    }

    pub fn set_simulation_state(&mut self, sim_name: impl Into<String>, state: SimulationState) {
        self.simulation_states.insert(sim_name.into(), state);
    }

    pub fn apply_transitions(&mut self) {
        for transition in &self.transitions {
            if self.transition_matches(transition) && self.engine_state != transition.engine {
                println!(
                    "[runner] engine state transition {:?} -> {:?}",
                    self.engine_state, transition.engine
                );
                self.engine_state = transition.engine;
            }
        }
    }

    fn transition_matches(&self, transition: &StateTransitionConfig) -> bool {
        self.condition_matches(&transition.when)
    }

    fn condition_matches(&self, condition: &StateTransitionCondition) -> bool {
        match (!condition.all.is_empty(), !condition.any.is_empty()) {
            (true, _) => condition
                .all
                .iter()
                .all(|item| self.simulation_state(&item.sim) == item.state),
            (false, true) => condition
                .any
                .iter()
                .any(|item| self.simulation_state(&item.sim) == item.state),
            (false, false) => false,
        }
    }

    fn simulation_state(&self, sim_name: &str) -> SimulationState {
        self.simulation_states
            .get(sim_name)
            .copied()
            .unwrap_or(SimulationState::_START)
    }
}