use crate::context::ExecutorStatus;
use crate::execution::SimulationResult;
use crate::execution::engine::Engine;
use crate::execution::phase::MicroStepResult;
use crate::execution::runner::Runner;
use crate::execution::strategy::{AlwaysContinueStrategy, ContinueStrategy};
use crate::modeling::model::Model;
use crate::primitive::time::TickStatus;
#[derive(Clone)]
pub struct StandardRunner<CS> {
skippable: bool,
continue_strategy: CS,
}
impl<E, M: Model<E>, CS: ContinueStrategy<E, M, ()>> Runner<E, M, CS> for StandardRunner<CS> {
type Err = ();
fn run<F>(
&mut self,
engine: Engine<E, M>,
mut model: M,
mut should_stop: F,
) -> SimulationResult<M, CS::Err>
where
F: FnMut(&M, ExecutorStatus, TickStatus) -> bool,
{
let mut runner_error: Option<CS::Err> = None;
let mut executor = engine.begin_simulation(&model);
loop {
let (executor_status, tick_status) = executor.peek_next_tick();
if should_stop(&model, executor_status, tick_status) {
break;
}
let mut active_executor = executor.begin_tick(&model);
loop {
let micro_step_handler = active_executor.begin_micro_step(&model);
let mut source_phase = micro_step_handler.start_source_phase(&model);
while let Some(source_ready) = source_phase.take_one() {
source_phase.fire_and_schedule(&model, source_ready);
}
let micro_step_handler = source_phase.complete_source_phase(&model);
let mut event_phase = micro_step_handler.to_event_phase(&model);
while let Some(event_ready) = event_phase.take_one() {
event_phase.handle_event(&mut model, event_ready);
}
let micro_step_handler = event_phase.complete_event_phase(&model);
match micro_step_handler.end_micro_step(&model) {
MicroStepResult::Continue(unchecked) => {
match self
.continue_strategy
.handle_micro_step_continue(&model, unchecked)
{
Ok(new_active_executor) => {
active_executor = new_active_executor;
continue;
}
Err((new_active_executor, error)) => {
active_executor = new_active_executor;
runner_error = Some(error);
break;
}
}
}
MicroStepResult::Complete(new_active_executor, _) => {
active_executor = new_active_executor;
break;
}
}
}
executor = if self.skippable {
active_executor.end_tick_with_jump_to_next_tick(&model)
} else {
active_executor.end_tick_with_increment_tick(&model)
};
if runner_error.is_some() {
break;
}
}
if let Some(error) = runner_error.take() {
executor.end_simulation_as_error(model, error)
} else {
executor.end_simulation_as_ok(model)
}
}
}
impl StandardRunner<AlwaysContinueStrategy> {
pub fn new(skippable: bool) -> Self {
StandardRunner {
skippable,
continue_strategy: AlwaysContinueStrategy::new(),
}
}
}
impl<CS> StandardRunner<CS> {
pub fn new_with_continue_strategy(skippable: bool, continue_strategy: CS) -> Self {
StandardRunner {
skippable,
continue_strategy,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::{EventContext, SourceContext, UserContext};
use crate::execution::strategy::LimitAbortStrategy;
use crate::modeling::event::{Event, EventPriority};
use crate::modeling::hook::Hook;
use crate::modeling::hook::instance::SharedHook;
use crate::modeling::source::Source;
use crate::primitive::time::{Duration, MicroStep, SimTime, TickStatus};
use crate::source_handler::{SourceReadyEntry, SourceView};
use std::sync::{Arc, Mutex};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum TestEvent {
A,
}
#[derive(Debug)]
struct TestModel {
event_count: usize,
}
impl Model<TestEvent> for TestModel {
fn handle_event(
&mut self,
_context: &mut EventContext<TestEvent, Self>,
_event: &Event<TestEvent>,
) {
self.event_count += 1;
}
}
#[test]
fn test_standard_runner_new_and_skippable() {
let runner_always = StandardRunner::new(true);
assert!(runner_always.skippable);
let strategy = AlwaysContinueStrategy::new();
let runner_custom = StandardRunner::new_with_continue_strategy(false, strategy);
assert!(!runner_custom.skippable);
}
#[test]
fn test_standard_runner_run_success() {
let model = TestModel { event_count: 0 };
let mut engine = Engine::new();
engine.schedule_event_at(
SimTime::from_ticks(5),
EventPriority::minimum(),
TestEvent::A,
);
let mut runner = StandardRunner::new(true);
let should_stop = |_m: &TestModel, _status: ExecutorStatus, tick: TickStatus| {
tick.is_done_ticks(false, 10)
};
let result = runner.run(engine, model, should_stop);
assert!(result.is_ok());
let output = result.unwrap();
assert_eq!(output.model().event_count, 1);
}
#[test]
fn test_standard_runner_run_with_strategy_error() {
let model = TestModel { event_count: 0 };
let mut engine = Engine::new();
struct TestSource;
impl Source<TestEvent, TestModel> for TestSource {
fn on_registered(
&mut self,
_context: &mut dyn UserContext<TestEvent, TestModel>,
_model: &TestModel,
) -> Option<Duration> {
Some(Duration::zero())
}
fn fire(
&mut self,
context: &mut SourceContext<TestEvent, TestModel>,
_model: &TestModel,
) -> Option<Duration> {
context.schedule_event(Duration::zero(), EventPriority::minimum(), TestEvent::A);
Some(Duration::one())
}
}
engine.add_source("test source", TestSource);
let strategy = LimitAbortStrategy::new(0, 0);
let mut runner = StandardRunner::new_with_continue_strategy(true, strategy);
let mut loop_count = 0;
let should_stop = |_m: &TestModel, _status: ExecutorStatus, _tick: TickStatus| {
loop_count += 1;
loop_count > 10
};
let result = runner.run(engine, model, should_stop);
assert!(result.is_err());
}
#[test]
fn test_standard_runner_run_without_strategy_error() {
let model = TestModel { event_count: 0 };
let mut engine = Engine::new();
engine.schedule_event_at(SimTime::zero(), EventPriority::minimum(), TestEvent::A);
let strategy = LimitAbortStrategy::new(0, 0);
let mut runner = StandardRunner::new_with_continue_strategy(true, strategy);
let mut loop_count = 0;
let should_stop = |_m: &TestModel, _status: ExecutorStatus, _tick: TickStatus| {
loop_count += 1;
loop_count > 10
};
let result = runner.run(engine, model, should_stop);
assert!(result.is_ok());
}
#[derive(Debug, PartialEq, Eq, Clone)]
enum LifecycleEvent {
BeforeSimulation,
BeforeTick(SimTime),
BeforeFireSource(SimTime),
BeforeScheduleEvent,
AfterScheduleEvent,
AfterFireSource(SimTime),
AfterTick(SimTime),
AfterSimulation,
}
struct TraceSource {
trace: Arc<Mutex<Vec<LifecycleEvent>>>,
initial_delay: Duration,
interval_delay: Option<Duration>,
}
impl Source<TestEvent, TestModel> for TraceSource {
fn on_registered(
&mut self,
_context: &mut dyn UserContext<TestEvent, TestModel>,
_model: &TestModel,
) -> Option<Duration> {
self.trace
.lock()
.unwrap()
.push(LifecycleEvent::BeforeSimulation);
Some(self.initial_delay)
}
fn fire(
&mut self,
context: &mut SourceContext<TestEvent, TestModel>,
_model: &TestModel,
) -> Option<Duration> {
let mut t = self.trace.lock().unwrap();
t.push(LifecycleEvent::BeforeFireSource(context.current_tick()));
t.push(LifecycleEvent::BeforeScheduleEvent);
context.schedule_event(Duration::zero(), EventPriority::minimum(), TestEvent::A);
t.push(LifecycleEvent::AfterScheduleEvent);
t.push(LifecycleEvent::AfterFireSource(context.current_tick()));
self.interval_delay
}
}
#[test]
fn test_runner_lifecycle_execution_order_scenario() {
let trace = Arc::new(Mutex::new(Vec::new()));
let model = TestModel { event_count: 0 };
let mut engine = Engine::new();
engine.add_source(
"trace_source",
TraceSource {
trace: Arc::clone(&trace),
initial_delay: Duration::ticks(1),
interval_delay: None, },
);
let mut runner = StandardRunner::new(true);
let should_stop = move |_m: &TestModel, _status: ExecutorStatus, tick: TickStatus| {
tick.is_done_ticks(false, 2)
};
let result = runner.run(engine, model, should_stop);
assert!(result.is_ok());
trace.lock().unwrap().push(LifecycleEvent::AfterSimulation);
let final_trace = trace.lock().unwrap();
let expected = vec![
LifecycleEvent::BeforeSimulation,
LifecycleEvent::BeforeFireSource(SimTime::from_ticks(1)),
LifecycleEvent::BeforeScheduleEvent,
LifecycleEvent::AfterScheduleEvent,
LifecycleEvent::AfterFireSource(SimTime::from_ticks(1)),
LifecycleEvent::AfterSimulation,
];
assert_eq!(*final_trace, expected);
}
#[test]
fn test_lifecycle_interruption_on_strategy_error() {
let trace = Arc::new(Mutex::new(Vec::new()));
let model = TestModel { event_count: 0 };
let mut engine = Engine::new();
engine.add_source(
"loop_source",
TraceSource {
trace: Arc::clone(&trace),
initial_delay: Duration::zero(),
interval_delay: Some(Duration::zero()),
},
);
let strategy = LimitAbortStrategy::new(0, 0);
let mut runner = StandardRunner::new_with_continue_strategy(true, strategy);
let trace_for_stop = Arc::clone(&trace);
let should_stop = move |_m: &TestModel, _status: ExecutorStatus, tick: TickStatus| {
trace_for_stop
.lock()
.unwrap()
.push(LifecycleEvent::BeforeTick(tick.current()));
false
};
let result = runner.run(engine, model, should_stop);
assert!(result.is_err());
let final_trace = trace.lock().unwrap();
assert!(final_trace.contains(&LifecycleEvent::BeforeSimulation));
assert!(final_trace.contains(&LifecycleEvent::BeforeTick(SimTime::zero())));
assert!(!final_trace.contains(&LifecycleEvent::BeforeTick(SimTime::from_ticks(1))));
let last_event = final_trace.last().unwrap();
assert_ne!(last_event, &LifecycleEvent::AfterTick(SimTime::zero()));
}
#[derive(Debug, PartialEq, Eq, Clone)]
enum HookCall {
BeforeSimulation,
AfterSimulation(SimTime),
BeforeTick {
current: SimTime,
skipped: Duration,
},
AfterTick {
current: SimTime,
last_micro: MicroStep,
},
BeforeMicroStep {
current: SimTime,
micro: MicroStep,
},
AfterMicroStep {
current: SimTime,
micro: MicroStep,
},
BeforeSourcePhase {
current: SimTime,
micro: MicroStep,
},
AfterSourcePhase {
current: SimTime,
micro: MicroStep,
},
BeforeEventPhase {
current: SimTime,
micro: MicroStep,
},
AfterEventPhase {
current: SimTime,
micro: MicroStep,
},
}
struct MockHook {
calls: Arc<Mutex<Vec<HookCall>>>,
}
impl<E, M: Model<E>> Hook<E, M> for MockHook {
fn before_simulation(&self, _model: &M) {
self.calls.lock().unwrap().push(HookCall::BeforeSimulation);
}
fn after_simulation(&self, _model: &M, end_tick: SimTime) {
self.calls
.lock()
.unwrap()
.push(HookCall::AfterSimulation(end_tick));
}
fn before_tick(&self, _model: &M, current_tick: SimTime, skipped_duration: Duration) {
self.calls.lock().unwrap().push(HookCall::BeforeTick {
current: current_tick,
skipped: skipped_duration,
});
}
fn after_tick(&self, _model: &M, current_tick: SimTime, last_micro_step: MicroStep) {
self.calls.lock().unwrap().push(HookCall::AfterTick {
current: current_tick,
last_micro: last_micro_step,
});
}
fn before_micro_step(
&self,
_model: &M,
current_tick: SimTime,
current_micro_step: MicroStep,
) {
self.calls.lock().unwrap().push(HookCall::BeforeMicroStep {
current: current_tick,
micro: current_micro_step,
});
}
fn after_micro_step(
&self,
_model: &M,
current_tick: SimTime,
current_micro_step: MicroStep,
) {
self.calls.lock().unwrap().push(HookCall::AfterMicroStep {
current: current_tick,
micro: current_micro_step,
});
}
fn on_discard_remain_micro_step(
&self,
_model: &M,
_current_tick: SimTime,
_first_discarded_micro_step: MicroStep,
_discarded_sources: &[SourceReadyEntry],
_discarded_events: &[Event<E>],
) {
}
fn before_register_source(&self, _model: &M, _name: &str) {}
fn after_register_source(&self, _model: &M, _name: &str) {}
fn before_source_phase(
&self,
_model: &M,
current_tick: SimTime,
current_micro_step: MicroStep,
) {
self.calls
.lock()
.unwrap()
.push(HookCall::BeforeSourcePhase {
current: current_tick,
micro: current_micro_step,
});
}
fn before_source(
&self,
_model: &M,
_current_tick: SimTime,
_current_micro_step: MicroStep,
_source_view: &SourceView,
) {
}
fn after_source(
&self,
_model: &M,
_current_tick: SimTime,
_current_micro_step: MicroStep,
_source_view: &SourceView,
_computed_next_fire: Option<SimTime>,
) {
}
fn cancel_source(
&self,
_model: &M,
_current_tick: SimTime,
_current_micro_step: MicroStep,
_scheduled_at: SimTime,
_source_view: &SourceView,
) {
}
fn discard_source(
&self,
_model: &M,
_current_tick: SimTime,
_current_micro_step: MicroStep,
_source_view: &SourceView,
) {
}
fn after_source_phase(
&self,
_model: &M,
current_tick: SimTime,
current_micro_step: MicroStep,
) {
self.calls.lock().unwrap().push(HookCall::AfterSourcePhase {
current: current_tick,
micro: current_micro_step,
});
}
fn before_event_phase(
&self,
_model: &M,
current_tick: SimTime,
current_micro_step: MicroStep,
) {
self.calls.lock().unwrap().push(HookCall::BeforeEventPhase {
current: current_tick,
micro: current_micro_step,
});
}
fn before_event(
&self,
_model: &M,
_current_tick: SimTime,
_current_micro_step: MicroStep,
_event: &Event<E>,
) {
}
fn after_event(
&self,
_model: &M,
_current_tick: SimTime,
_current_micro_step: MicroStep,
_event: &Event<E>,
) {
}
fn cancel_event(
&self,
_model: &M,
_current_tick: SimTime,
_current_micro_step: MicroStep,
_scheduled_at: SimTime,
_event: &Event<E>,
) {
}
fn discard_event(
&self,
_model: &M,
_current_tick: SimTime,
_current_micro_step: MicroStep,
_event: &Event<E>,
) {
}
fn after_event_phase(
&self,
_model: &M,
current_tick: SimTime,
current_micro_step: MicroStep,
) {
self.calls.lock().unwrap().push(HookCall::AfterEventPhase {
current: current_tick,
micro: current_micro_step,
});
}
}
#[test]
fn test_standard_runner_hook_lifecycle_flow_with_include_zero_tick() {
let hook = MockHook {
calls: Arc::new(Mutex::new(Vec::new())),
};
let shared_hook = SharedHook::new(hook);
let model = TestModel { event_count: 0 };
let mut engine = Engine::new();
engine.add_shared_hook(shared_hook.clone());
engine.schedule_event_at(
SimTime::from_ticks(1),
EventPriority::minimum(),
TestEvent::A,
);
let mut runner = StandardRunner::new(true);
let should_stop = |_m: &TestModel, _status: ExecutorStatus, tick: TickStatus| {
tick.is_done_ticks(true, 2)
};
let _result = runner.run(engine, model, should_stop);
let final_calls = shared_hook.get_ref().calls.lock().unwrap();
let expected = vec![
HookCall::BeforeSimulation,
HookCall::BeforeTick {
current: SimTime::from_ticks(0),
skipped: Duration::zero(),
},
HookCall::BeforeMicroStep {
current: SimTime::from_ticks(0),
micro: MicroStep::zero(),
},
HookCall::BeforeSourcePhase {
current: SimTime::from_ticks(0),
micro: MicroStep::zero(),
},
HookCall::AfterSourcePhase {
current: SimTime::from_ticks(0),
micro: MicroStep::zero(),
},
HookCall::BeforeEventPhase {
current: SimTime::from_ticks(0),
micro: MicroStep::zero(),
},
HookCall::AfterEventPhase {
current: SimTime::from_ticks(0),
micro: MicroStep::zero(),
},
HookCall::AfterMicroStep {
current: SimTime::from_ticks(0),
micro: MicroStep::zero(),
},
HookCall::AfterTick {
current: SimTime::from_ticks(0),
last_micro: MicroStep::zero(),
},
HookCall::BeforeTick {
current: SimTime::from_ticks(1),
skipped: Duration::ticks(0),
},
HookCall::BeforeMicroStep {
current: SimTime::from_ticks(1),
micro: MicroStep::zero(),
},
HookCall::BeforeSourcePhase {
current: SimTime::from_ticks(1),
micro: MicroStep::zero(),
},
HookCall::AfterSourcePhase {
current: SimTime::from_ticks(1),
micro: MicroStep::zero(),
},
HookCall::BeforeEventPhase {
current: SimTime::from_ticks(1),
micro: MicroStep::zero(),
},
HookCall::AfterEventPhase {
current: SimTime::from_ticks(1),
micro: MicroStep::zero(),
},
HookCall::AfterMicroStep {
current: SimTime::from_ticks(1),
micro: MicroStep::zero(),
},
HookCall::AfterTick {
current: SimTime::from_ticks(1),
last_micro: MicroStep::zero(),
},
HookCall::AfterSimulation(SimTime::from_ticks(1)),
];
assert_eq!(*final_calls, expected);
}
#[test]
fn test_standard_runner_hook_lifecycle_flow_without_include_zero_tick() {
let hook = MockHook {
calls: Arc::new(Mutex::new(Vec::new())),
};
let shared_hook = SharedHook::new(hook);
let model = TestModel { event_count: 0 };
let mut engine = Engine::new();
engine.add_shared_hook(shared_hook.clone());
engine.schedule_event_at(
SimTime::from_ticks(1),
EventPriority::minimum(),
TestEvent::A,
);
let mut runner = StandardRunner::new(true);
let should_stop = |_m: &TestModel, _status: ExecutorStatus, tick: TickStatus| {
tick.is_done_ticks(false, 2)
};
let _result = runner.run(engine, model, should_stop);
let final_calls = shared_hook.get_ref().calls.lock().unwrap();
let expected = vec![
HookCall::BeforeSimulation,
HookCall::BeforeTick {
current: SimTime::from_ticks(0),
skipped: Duration::zero(),
},
HookCall::BeforeMicroStep {
current: SimTime::from_ticks(0),
micro: MicroStep::zero(),
},
HookCall::BeforeSourcePhase {
current: SimTime::from_ticks(0),
micro: MicroStep::zero(),
},
HookCall::AfterSourcePhase {
current: SimTime::from_ticks(0),
micro: MicroStep::zero(),
},
HookCall::BeforeEventPhase {
current: SimTime::from_ticks(0),
micro: MicroStep::zero(),
},
HookCall::AfterEventPhase {
current: SimTime::from_ticks(0),
micro: MicroStep::zero(),
},
HookCall::AfterMicroStep {
current: SimTime::from_ticks(0),
micro: MicroStep::zero(),
},
HookCall::AfterTick {
current: SimTime::from_ticks(0),
last_micro: MicroStep::zero(),
},
HookCall::BeforeTick {
current: SimTime::from_ticks(1),
skipped: Duration::ticks(0),
},
HookCall::BeforeMicroStep {
current: SimTime::from_ticks(1),
micro: MicroStep::zero(),
},
HookCall::BeforeSourcePhase {
current: SimTime::from_ticks(1),
micro: MicroStep::zero(),
},
HookCall::AfterSourcePhase {
current: SimTime::from_ticks(1),
micro: MicroStep::zero(),
},
HookCall::BeforeEventPhase {
current: SimTime::from_ticks(1),
micro: MicroStep::zero(),
},
HookCall::AfterEventPhase {
current: SimTime::from_ticks(1),
micro: MicroStep::zero(),
},
HookCall::AfterMicroStep {
current: SimTime::from_ticks(1),
micro: MicroStep::zero(),
},
HookCall::AfterTick {
current: SimTime::from_ticks(1),
last_micro: MicroStep::zero(),
},
HookCall::BeforeTick {
current: SimTime::from_ticks(2),
skipped: Duration::zero(),
},
HookCall::BeforeMicroStep {
current: SimTime::from_ticks(2),
micro: MicroStep::zero(),
},
HookCall::BeforeSourcePhase {
current: SimTime::from_ticks(2),
micro: MicroStep::zero(),
},
HookCall::AfterSourcePhase {
current: SimTime::from_ticks(2),
micro: MicroStep::zero(),
},
HookCall::BeforeEventPhase {
current: SimTime::from_ticks(2),
micro: MicroStep::zero(),
},
HookCall::AfterEventPhase {
current: SimTime::from_ticks(2),
micro: MicroStep::zero(),
},
HookCall::AfterMicroStep {
current: SimTime::from_ticks(2),
micro: MicroStep::zero(),
},
HookCall::AfterTick {
current: SimTime::from_ticks(2),
last_micro: MicroStep::zero(),
},
HookCall::AfterSimulation(SimTime::from_ticks(2)),
];
assert_eq!(*final_calls, expected);
}
}