use super::{
ActionKind, AnyAction, AnyModel, Dispatcher, Effectful, EffectfulModel, ModelState,
PrivateModel, Pure, PureModel, State,
};
use std::collections::BTreeMap;
use std::{env, io::Write};
use type_uuid::TypeUuid;
pub struct Runner<Substate: ModelState> {
models: BTreeMap<type_uuid::Bytes, AnyModel<Substate>>,
state: State<Substate>,
dispatchers: Vec<Dispatcher>,
}
pub trait RegisterModel {
fn register<Substate: ModelState>(builder: RunnerBuilder<Substate>) -> RunnerBuilder<Substate>;
}
pub struct RunnerBuilder<Substate: ModelState> {
models: BTreeMap<type_uuid::Bytes, AnyModel<Substate>>,
state: State<Substate>,
dispatchers: Vec<Dispatcher>,
}
impl<Substate: ModelState> RunnerBuilder<Substate> {
pub fn new() -> Self {
Self {
models: BTreeMap::default(),
state: State::<Substate>::new(),
dispatchers: Vec::new(),
}
}
pub fn instance(mut self, substate: Substate, tick: fn() -> AnyAction) -> Self {
self.state.substates.push(substate);
self.dispatchers.push(Dispatcher::new(tick));
self
}
pub fn register<T: RegisterModel>(self) -> Self {
T::register(self)
}
pub fn model_pure<M: PureModel>(mut self) -> Self {
self.models
.insert(M::Action::UUID, Pure::<M>::into_vtable2());
self
}
pub fn model_effectful<M: EffectfulModel>(mut self, model: Effectful<M>) -> Self {
self.models
.insert(M::Action::UUID, Box::new(model).into_vtable());
self
}
pub fn build(self) -> Runner<Substate> {
Runner::new(self.state, self.models, self.dispatchers)
}
}
impl<Substate: ModelState> Runner<Substate> {
pub fn new(
state: State<Substate>,
models: BTreeMap<type_uuid::Bytes, AnyModel<Substate>>,
dispatchers: Vec<Dispatcher>,
) -> Self {
Self {
models,
state,
dispatchers,
}
}
pub fn run(&mut self) {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
.format(|buf, record| writeln!(buf, "[{}] {}", record.level(), record.args()))
.init();
loop {
for instance in 0..self.dispatchers.len() {
self.state.set_current_instance(instance);
let dispatcher = &mut self.dispatchers[instance];
if dispatcher.is_halted() {
return;
}
let action = dispatcher.next_action();
self.process_action(action, instance)
}
}
}
fn process_action(&mut self, action: AnyAction, instance: usize) {
let dispatcher = &mut self.dispatchers[instance];
let model = self
.models
.get_mut(&action.uuid)
.expect(&format!("action not found {}", action.type_name));
if let Some(_reader) = &mut dispatcher.replay_file {
todo!()
}
if let Some(writer) = &mut dispatcher.record_file {
model.serialize_into(writer, &action)
}
match action.kind {
ActionKind::Pure => model.process_pure(&mut self.state, action, dispatcher),
ActionKind::Effectful => model.process_effectful(action, dispatcher),
}
}
pub fn record(&mut self, session_name: &str) {
let path = env::current_dir().expect("Failed to retrieve current directory");
for (instance, dispatcher) in self.dispatchers.iter_mut().enumerate() {
dispatcher.record(&format!(
"{}/{}_{}.rec",
path.to_str().unwrap(),
session_name,
instance
))
}
self.run()
}
pub fn replay(&mut self, session_name: &str) {
let path = env::current_dir().expect("Failed to retrieve current directory");
for (instance, dispatcher) in self.dispatchers.iter_mut().enumerate() {
dispatcher.open_recording(&format!(
"{}/{}_{}.rec",
path.to_str().unwrap(),
session_name,
instance
))
}
self.run()
}
}