1use crate::context::{EventContext, ExecutorStatus};
9use crate::execution::SimulationResult;
10use crate::execution::engine::Engine;
11use crate::execution::phase::MicroStepResult;
12use crate::execution::runner::Runner;
13use crate::execution::strategy::{AlwaysContinueStrategy, ContinueStrategy};
14use crate::modeling::event::{Event, EventPriority};
15use crate::modeling::model::Model;
16use crate::primitive::time::TickStatus;
17use std::marker::PhantomData;
18use std::sync::mpsc::Sender;
19
20pub trait ParallelModel<E, Command>: Model<E> {
25 fn handle_event_parallel(&self, event: Event<E>, sender: Sender<Command>);
28
29 fn apply_command(&mut self, context: &mut EventContext<E, Self>, command: Command)
33 where
34 Self: Sized;
35}
36
37#[derive(Clone)]
42pub struct ParallelRunner<Command, CS> {
43 skippable: bool,
44 continue_strategy: CS,
45 sync_priority_threshold: EventPriority,
47 _command: PhantomData<Command>,
48}
49
50impl<E, Command, M: ParallelModel<E, Command>, CS: ContinueStrategy<E, M, ()>> Runner<E, M, CS>
51 for ParallelRunner<Command, CS>
52where
53 E: Send + 'static,
54 M: Send + Sync + 'static,
55 Command: Send,
56{
57 type Err = ();
58
59 fn run<F>(
60 &mut self,
61 engine: Engine<E, M>,
62 mut model: M,
63 mut should_stop: F,
64 ) -> SimulationResult<M, CS::Err>
65 where
66 F: FnMut(&M, ExecutorStatus, TickStatus) -> bool,
67 {
68 let mut runner_error: Option<CS::Err> = None;
69 let mut executor = engine.begin_simulation(&model);
70
71 loop {
72 let (executor_status, tick_status) = executor.peek_next_tick();
73 if should_stop(&model, executor_status, tick_status) {
74 break;
75 }
76
77 let mut active_executor = executor.begin_tick(&model);
78
79 loop {
80 let micro_step_handler = active_executor.begin_micro_step(&model);
82
83 let mut source_phase = micro_step_handler.start_source_phase(&model);
85 while let Some(source_ready) = source_phase.take_one() {
86 source_phase.fire_and_schedule(&model, source_ready);
87 }
88 let micro_step_handler = source_phase.complete_source_phase(&model);
89
90 let mut event_phase = micro_step_handler.to_event_phase(&model);
92
93 loop {
94 if let Some(event_ready) =
96 event_phase.take_front_if(|e| e.priority >= self.sync_priority_threshold)
97 {
98 event_phase.handle_event(&mut model, event_ready);
100 } else {
101 let parallel_events = event_phase.take_all();
103 let (sender, receiver) = std::sync::mpsc::channel();
104
105 std::thread::scope(|_scope| {
107 use rayon::prelude::*;
108
109 let sender = sender;
110 parallel_events.into_par_iter().for_each_with(
111 sender,
112 |sender_worker, event_ready| {
113 let model_ref = &model;
114 model_ref
115 .handle_event_parallel(event_ready, sender_worker.clone());
116 },
117 );
118 });
119
120 while let Ok(command) = receiver.try_recv() {
122 model.apply_command(event_phase.get_context(), command);
123 }
124
125 break;
126 }
127 }
128
129 let micro_step_handler = event_phase.complete_event_phase(&model);
130
131 match micro_step_handler.end_micro_step(&model) {
133 MicroStepResult::Continue(unchecked) => {
134 match self
135 .continue_strategy
136 .handle_micro_step_continue(&model, unchecked)
137 {
138 Ok(new_active_executor) => {
139 active_executor = new_active_executor;
140 continue;
141 }
142 Err((new_active_executor, error)) => {
143 active_executor = new_active_executor;
144 runner_error = Some(error);
145 break;
146 }
147 }
148 }
149 MicroStepResult::Complete(new_active_executor, _) => {
150 active_executor = new_active_executor;
151 break;
152 }
153 }
154 }
155
156 executor = if self.skippable {
157 active_executor.end_tick_with_jump_to_next_tick(&model)
158 } else {
159 active_executor.end_tick_with_increment_tick(&model)
160 };
161
162 if runner_error.is_some() {
163 break;
164 }
165 }
166
167 if let Some(error) = runner_error.take() {
168 executor.end_simulation_as_error(model, error)
169 } else {
170 executor.end_simulation_as_ok(model)
171 }
172 }
173}
174
175impl<Command> ParallelRunner<Command, AlwaysContinueStrategy> {
176 pub fn new(skippable: bool, sync_priority_threshold: EventPriority) -> Self {
183 ParallelRunner {
184 skippable,
185 continue_strategy: AlwaysContinueStrategy::new(),
186 sync_priority_threshold,
187 _command: PhantomData,
188 }
189 }
190}
191
192impl<Command, CS> ParallelRunner<Command, CS> {
193 pub fn new_with_continue_strategy(
202 skippable: bool,
203 sync_priority_threshold: EventPriority,
204 continue_strategy: CS,
205 ) -> Self {
206 ParallelRunner {
207 skippable,
208 continue_strategy,
209 sync_priority_threshold,
210 _command: PhantomData,
211 }
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::context::{EventContext, ExecutorStatus, SourceContext, UserContext};
219 use crate::execution::engine::Engine;
220 use crate::execution::strategy::{AlwaysContinueStrategy, LimitAbortStrategy};
221 use crate::modeling::event::{Event, EventPriority};
222 use crate::modeling::hook::Hook;
223 use crate::modeling::model::Model;
224 use crate::modeling::source::Source;
225 use crate::primitive::time::{Duration, MicroStep, SimTime, TickStatus};
226 use crate::source_handler::{SourceReadyEntry, SourceView};
227 use std::sync::atomic::{AtomicUsize, Ordering};
228 use std::sync::mpsc::Sender;
229 use std::sync::{Arc, Mutex};
230
231 #[allow(unused)]
233 #[derive(Debug)]
234 enum TestCommand {
235 IncrementSync,
236 IncrementParallel,
237 }
238
239 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
240 enum TestEvent {
241 SyncTarget,
242 ParallelTarget,
243 }
244
245 struct TestParallelModel {
247 sync_event_count: Arc<AtomicUsize>,
248 parallel_command_count: Arc<AtomicUsize>,
249 }
250
251 impl Model<TestEvent> for TestParallelModel {
252 fn handle_event(
253 &mut self,
254 _context: &mut EventContext<TestEvent, Self>,
255 event: &Event<TestEvent>,
256 ) {
257 if let TestEvent::SyncTarget = event.payload {
259 self.sync_event_count.fetch_add(1, Ordering::SeqCst);
260 }
261 }
262 }
263
264 impl ParallelModel<TestEvent, TestCommand> for TestParallelModel {
265 fn handle_event_parallel(&self, event: Event<TestEvent>, sender: Sender<TestCommand>) {
266 if let TestEvent::ParallelTarget = event.payload {
268 let _ = sender.send(TestCommand::IncrementParallel);
269 }
270 }
271
272 fn apply_command(
273 &mut self,
274 _context: &mut EventContext<TestEvent, Self>,
275 command: TestCommand,
276 ) {
277 if let TestCommand::IncrementParallel = command {
279 self.parallel_command_count.fetch_add(1, Ordering::SeqCst);
280 }
281 }
282 }
283
284 #[test]
285 fn test_parallel_runner_new_and_args() {
286 let runner = ParallelRunner::<TestCommand, AlwaysContinueStrategy>::new(
287 true,
288 EventPriority::new(100),
289 );
290 assert!(runner.skippable);
291 assert_eq!(runner.sync_priority_threshold, EventPriority::new(100));
292 }
293
294 #[test]
295 fn test_parallel_runner_sync_and_parallel_execution() {
296 let sync_counter = Arc::new(AtomicUsize::new(0));
297 let parallel_counter = Arc::new(AtomicUsize::new(0));
298
299 let model = TestParallelModel {
300 sync_event_count: sync_counter.clone(),
301 parallel_command_count: parallel_counter.clone(),
302 };
303
304 let mut engine = Engine::new();
305
306 engine.schedule_event_at(
308 SimTime::zero(),
309 EventPriority::new(10),
310 TestEvent::SyncTarget,
311 );
312 engine.schedule_event_at(
314 SimTime::zero(),
315 EventPriority::new(0),
316 TestEvent::ParallelTarget,
317 );
318
319 let mut runner = ParallelRunner::new(true, EventPriority::new(5));
321
322 let mut tick_counter = 0;
323 let should_stop = |_m: &TestParallelModel, _status: ExecutorStatus, _tick: TickStatus| {
324 if tick_counter >= 1 {
325 true
326 } else {
327 tick_counter += 1;
328 false
329 }
330 };
331
332 let result = runner.run(engine, model, should_stop);
333 assert!(result.is_ok());
334
335 assert_eq!(sync_counter.load(Ordering::SeqCst), 1);
337 assert_eq!(parallel_counter.load(Ordering::SeqCst), 1);
338 }
339
340 #[test]
341 fn test_parallel_runner_boundary_priority() {
342 let sync_counter = Arc::new(AtomicUsize::new(0));
343 let parallel_counter = Arc::new(AtomicUsize::new(0));
344
345 let model = TestParallelModel {
346 sync_event_count: sync_counter.clone(),
347 parallel_command_count: parallel_counter.clone(),
348 };
349
350 let mut engine = Engine::new();
351
352 engine.schedule_event_at(
354 SimTime::zero(),
355 EventPriority::new(5),
356 TestEvent::SyncTarget,
357 );
358
359 let mut runner = ParallelRunner::new(true, EventPriority::new(5));
361 let mut tick_counter = 0;
362 let should_stop = |_m: &TestParallelModel, _status: ExecutorStatus, _tick: TickStatus| {
363 if tick_counter >= 1 {
364 true
365 } else {
366 tick_counter += 1;
367 false
368 }
369 };
370
371 let result = runner.run(engine, model, should_stop);
372 assert!(result.is_ok());
373
374 assert_eq!(sync_counter.load(Ordering::SeqCst), 1);
376 assert_eq!(parallel_counter.load(Ordering::SeqCst), 0);
377 }
378
379 #[test]
380 fn test_parallel_runner_massive_parallel_events() {
381 let sync_counter = Arc::new(AtomicUsize::new(0));
382 let parallel_counter = Arc::new(AtomicUsize::new(0));
383 let model = TestParallelModel {
384 sync_event_count: sync_counter.clone(),
385 parallel_command_count: parallel_counter.clone(),
386 };
387
388 let mut engine = Engine::new();
389
390 let event_count = 100;
392 for _ in 0..event_count {
393 engine.schedule_event_at(
394 SimTime::zero(),
395 EventPriority::new(0), TestEvent::ParallelTarget,
397 );
398 }
399
400 let mut runner = ParallelRunner::new(true, EventPriority::new(10));
401 let mut tick_counter = 0;
402 let should_stop = |_m: &TestParallelModel, _status: ExecutorStatus, _tick: TickStatus| {
403 if tick_counter >= 1 {
404 true
405 } else {
406 tick_counter += 1;
407 false
408 }
409 };
410
411 let result = runner.run(engine, model, should_stop);
412 assert!(result.is_ok());
413 assert_eq!(parallel_counter.load(Ordering::SeqCst), event_count);
414 }
415
416 #[test]
417 fn test_parallel_runner_aborts_on_strategy_error() {
418 let mut engine = Engine::new();
419
420 engine.schedule_event_at(
422 SimTime::zero(),
423 EventPriority::new(0),
425 TestEvent::ParallelTarget,
426 );
427
428 struct TestAbortModel;
429 impl Model<TestEvent> for TestAbortModel {
430 fn handle_event(
431 &mut self,
432 _context: &mut EventContext<TestEvent, Self>,
433 _event: &Event<TestEvent>,
434 ) {
435 }
437 }
438 impl ParallelModel<TestEvent, TestCommand> for TestAbortModel {
439 fn handle_event_parallel(&self, event: Event<TestEvent>, sender: Sender<TestCommand>) {
440 if let TestEvent::ParallelTarget = event.payload {
441 let _ = sender.send(TestCommand::IncrementParallel);
442 }
443 }
444 fn apply_command(
445 &mut self,
446 context: &mut EventContext<TestEvent, Self>,
447 _command: TestCommand,
448 ) {
449 context.schedule_event(
451 Duration::zero(),
452 EventPriority::new(0),
453 TestEvent::ParallelTarget,
454 );
455 }
456 }
457
458 let abort_model = TestAbortModel;
459
460 let strategy = LimitAbortStrategy::new(0, 0);
462 let mut runner =
463 ParallelRunner::new_with_continue_strategy(true, EventPriority::new(5), strategy);
464
465 let mut loop_count = 0;
466 let should_stop = |_m: &TestAbortModel, _status: ExecutorStatus, _tick: TickStatus| {
467 loop_count += 1;
468 loop_count > 5
469 };
470
471 let result = runner.run(engine, abort_model, should_stop);
475
476 assert!(result.is_err());
478 }
479
480 #[test]
481 fn test_parallel_runner_without_aborts_on_strategy_error() {
482 let model = TestParallelModel {
483 sync_event_count: Arc::new(AtomicUsize::new(0)),
484 parallel_command_count: Arc::new(AtomicUsize::new(0)),
485 };
486
487 let mut engine = Engine::new();
488
489 engine.schedule_event_at(
492 SimTime::zero(),
493 EventPriority::new(0),
494 TestEvent::ParallelTarget,
495 );
496
497 let strategy = LimitAbortStrategy::new(0, 0);
499 let mut runner =
500 ParallelRunner::new_with_continue_strategy(true, EventPriority::new(5), strategy);
501
502 let mut loop_count = 0;
503 let should_stop = |_m: &TestParallelModel, _status: ExecutorStatus, _tick: TickStatus| {
504 loop_count += 1;
505 loop_count > 5
506 };
507
508 let result = runner.run(engine, model, should_stop);
509
510 assert!(result.is_ok());
512 }
513
514 #[derive(Debug, PartialEq, Eq, Clone)]
516 enum LifecycleEvent {
517 BeforeSimulation,
518 BeforeTick(SimTime),
519 BeforeFireSource(SimTime),
520 BeforeScheduleEvent,
521 AfterScheduleEvent,
522 AfterFireSource(SimTime),
523 AfterTick(SimTime),
524 AfterSimulation,
525 }
526
527 struct TraceSource {
529 trace: Arc<Mutex<Vec<LifecycleEvent>>>,
530 initial_delay: Duration,
531 interval_delay: Option<Duration>,
532 }
533
534 impl Source<TestEvent, TestParallelModel> for TraceSource {
535 fn on_registered(
536 &mut self,
537 _context: &mut dyn UserContext<TestEvent, TestParallelModel>,
538 _model: &TestParallelModel,
539 ) -> Option<Duration> {
540 self.trace
541 .lock()
542 .unwrap()
543 .push(LifecycleEvent::BeforeSimulation);
544 Some(self.initial_delay)
545 }
546
547 fn fire(
548 &mut self,
549 context: &mut SourceContext<TestEvent, TestParallelModel>,
550 _model: &TestParallelModel,
551 ) -> Option<Duration> {
552 let mut t = self.trace.lock().unwrap();
553 t.push(LifecycleEvent::BeforeFireSource(context.current_tick()));
554 t.push(LifecycleEvent::BeforeScheduleEvent);
555
556 context.schedule_event(
558 Duration::zero(),
559 EventPriority::new(0),
560 TestEvent::ParallelTarget,
561 );
562
563 t.push(LifecycleEvent::AfterScheduleEvent);
564 t.push(LifecycleEvent::AfterFireSource(context.current_tick()));
565 self.interval_delay
566 }
567 }
568
569 #[test]
570 fn test_parallel_runner_lifecycle_execution_order_scenario() {
571 let trace = Arc::new(Mutex::new(Vec::new()));
572 let model = TestParallelModel {
573 sync_event_count: Arc::new(AtomicUsize::new(0)),
574 parallel_command_count: Arc::new(AtomicUsize::new(0)),
575 };
576 let mut engine = Engine::new();
577
578 engine.add_source(
580 "trace_source",
581 TraceSource {
582 trace: Arc::clone(&trace),
583 initial_delay: Duration::ticks(1),
584 interval_delay: None,
585 },
586 );
587
588 let mut runner = ParallelRunner::new(true, EventPriority::new(5));
589
590 let should_stop =
592 move |_m: &TestParallelModel, _status: ExecutorStatus, tick: TickStatus| {
593 tick.is_done_ticks(false, 2)
594 };
595
596 let result = runner.run(engine, model, should_stop);
597 assert!(result.is_ok());
598
599 trace.lock().unwrap().push(LifecycleEvent::AfterSimulation);
602
603 let final_trace = trace.lock().unwrap();
604
605 let expected = vec![
606 LifecycleEvent::BeforeSimulation,
607 LifecycleEvent::BeforeFireSource(SimTime::from_ticks(1)),
609 LifecycleEvent::BeforeScheduleEvent,
610 LifecycleEvent::AfterScheduleEvent,
611 LifecycleEvent::AfterFireSource(SimTime::from_ticks(1)),
612 LifecycleEvent::AfterSimulation,
614 ];
615
616 assert_eq!(*final_trace, expected);
617 }
618
619 #[test]
620 fn test_parallel_runner_lifecycle_interruption_on_strategy_error() {
621 let trace = Arc::new(Mutex::new(Vec::new()));
622 let model = TestParallelModel {
623 sync_event_count: Arc::new(AtomicUsize::new(0)),
624 parallel_command_count: Arc::new(AtomicUsize::new(0)),
625 };
626 let mut engine = Engine::new();
627
628 engine.add_source(
630 "loop_source",
631 TraceSource {
632 trace: Arc::clone(&trace),
633 initial_delay: Duration::zero(),
634 interval_delay: Some(Duration::zero()),
635 },
636 );
637
638 let strategy = LimitAbortStrategy::new(0, 0);
640 let mut runner =
641 ParallelRunner::new_with_continue_strategy(true, EventPriority::new(5), strategy);
642
643 let trace_for_stop = Arc::clone(&trace);
644 let should_stop =
645 move |_m: &TestParallelModel, _status: ExecutorStatus, tick: TickStatus| {
646 trace_for_stop
647 .lock()
648 .unwrap()
649 .push(LifecycleEvent::BeforeTick(tick.current()));
650 false
651 };
652
653 let result = runner.run(engine, model, should_stop);
654
655 assert!(result.is_err());
657
658 let final_trace = trace.lock().unwrap();
659
660 assert!(final_trace.contains(&LifecycleEvent::BeforeSimulation));
662 assert!(final_trace.contains(&LifecycleEvent::BeforeTick(SimTime::zero())));
663 assert!(!final_trace.contains(&LifecycleEvent::BeforeTick(SimTime::from_ticks(1))));
664
665 let last_event = final_trace.last().unwrap();
668 assert_ne!(last_event, &LifecycleEvent::AfterTick(SimTime::zero()));
669 }
670
671 #[derive(Debug, PartialEq, Eq, Clone)]
673 enum HookCall {
674 BeforeSimulation,
675 AfterSimulation(SimTime),
676 BeforeTick {
677 current: SimTime,
678 skipped: Duration,
679 },
680 AfterTick {
681 current: SimTime,
682 last_micro: MicroStep,
683 },
684 BeforeMicroStep {
685 current: SimTime,
686 micro: MicroStep,
687 },
688 AfterMicroStep {
689 current: SimTime,
690 micro: MicroStep,
691 },
692 BeforeSourcePhase {
693 current: SimTime,
694 micro: MicroStep,
695 },
696 AfterSourcePhase {
697 current: SimTime,
698 micro: MicroStep,
699 },
700 BeforeEventPhase {
701 current: SimTime,
702 micro: MicroStep,
703 },
704 AfterEventPhase {
705 current: SimTime,
706 micro: MicroStep,
707 },
708 }
709
710 struct MockHook {
712 calls: Arc<Mutex<Vec<HookCall>>>,
713 }
714
715 impl<E, M: Model<E>> Hook<E, M> for MockHook {
716 fn before_simulation(&self, _model: &M) {
717 self.calls.lock().unwrap().push(HookCall::BeforeSimulation);
718 }
719 fn after_simulation(&self, _model: &M, end_tick: SimTime) {
720 self.calls
721 .lock()
722 .unwrap()
723 .push(HookCall::AfterSimulation(end_tick));
724 }
725 fn before_tick(&self, _model: &M, current_tick: SimTime, skipped_duration: Duration) {
726 self.calls.lock().unwrap().push(HookCall::BeforeTick {
727 current: current_tick,
728 skipped: skipped_duration,
729 });
730 }
731 fn after_tick(&self, _model: &M, current_tick: SimTime, last_micro_step: MicroStep) {
732 self.calls.lock().unwrap().push(HookCall::AfterTick {
733 current: current_tick,
734 last_micro: last_micro_step,
735 });
736 }
737 fn before_micro_step(
738 &self,
739 _model: &M,
740 current_tick: SimTime,
741 current_micro_step: MicroStep,
742 ) {
743 self.calls.lock().unwrap().push(HookCall::BeforeMicroStep {
744 current: current_tick,
745 micro: current_micro_step,
746 });
747 }
748 fn after_micro_step(
749 &self,
750 _model: &M,
751 current_tick: SimTime,
752 current_micro_step: MicroStep,
753 ) {
754 self.calls.lock().unwrap().push(HookCall::AfterMicroStep {
755 current: current_tick,
756 micro: current_micro_step,
757 });
758 }
759 fn on_discard_remain_micro_step(
760 &self,
761 _model: &M,
762 _current_tick: SimTime,
763 _first_discarded_micro_step: MicroStep,
764 _discarded_sources: &[SourceReadyEntry],
765 _discarded_events: &[Event<E>],
766 ) {
767 }
768 fn before_register_source(&self, _model: &M, _name: &str) {}
769 fn after_register_source(&self, _model: &M, _name: &str) {}
770 fn before_source_phase(
771 &self,
772 _model: &M,
773 current_tick: SimTime,
774 current_micro_step: MicroStep,
775 ) {
776 self.calls
777 .lock()
778 .unwrap()
779 .push(HookCall::BeforeSourcePhase {
780 current: current_tick,
781 micro: current_micro_step,
782 });
783 }
784 fn before_source(
785 &self,
786 _model: &M,
787 _current_tick: SimTime,
788 _current_micro_step: MicroStep,
789 _source_view: &SourceView,
790 ) {
791 }
792 fn after_source(
793 &self,
794 _model: &M,
795 _current_tick: SimTime,
796 _current_micro_step: MicroStep,
797 _source_view: &SourceView,
798 _computed_next_fire: Option<SimTime>,
799 ) {
800 }
801 fn cancel_source(
802 &self,
803 _model: &M,
804 _current_tick: SimTime,
805 _current_micro_step: MicroStep,
806 _scheduled_at: SimTime,
807 _source_view: &SourceView,
808 ) {
809 }
810 fn discard_source(
811 &self,
812 _model: &M,
813 _current_tick: SimTime,
814 _current_micro_step: MicroStep,
815 _source_view: &SourceView,
816 ) {
817 }
818 fn after_source_phase(
819 &self,
820 _model: &M,
821 current_tick: SimTime,
822 current_micro_step: MicroStep,
823 ) {
824 self.calls.lock().unwrap().push(HookCall::AfterSourcePhase {
825 current: current_tick,
826 micro: current_micro_step,
827 });
828 }
829 fn before_event_phase(
830 &self,
831 _model: &M,
832 current_tick: SimTime,
833 current_micro_step: MicroStep,
834 ) {
835 self.calls.lock().unwrap().push(HookCall::BeforeEventPhase {
836 current: current_tick,
837 micro: current_micro_step,
838 });
839 }
840 fn before_event(
841 &self,
842 _model: &M,
843 _current_tick: SimTime,
844 _current_micro_step: MicroStep,
845 _event: &Event<E>,
846 ) {
847 }
848 fn after_event(
849 &self,
850 _model: &M,
851 _current_tick: SimTime,
852 _current_micro_step: MicroStep,
853 _event: &Event<E>,
854 ) {
855 }
856 fn cancel_event(
857 &self,
858 _model: &M,
859 _current_tick: SimTime,
860 _current_micro_step: MicroStep,
861 _scheduled_at: SimTime,
862 _event: &Event<E>,
863 ) {
864 }
865 fn discard_event(
866 &self,
867 _model: &M,
868 _current_tick: SimTime,
869 _current_micro_step: MicroStep,
870 _event: &Event<E>,
871 ) {
872 }
873 fn after_event_phase(
874 &self,
875 _model: &M,
876 current_tick: SimTime,
877 current_micro_step: MicroStep,
878 ) {
879 self.calls.lock().unwrap().push(HookCall::AfterEventPhase {
880 current: current_tick,
881 micro: current_micro_step,
882 });
883 }
884 }
885
886 #[test]
887 fn test_parallel_runner_hook_lifecycle_flow_with_include_zero_tick() {
888 use crate::modeling::hook::instance::SharedHook;
889
890 let hook = MockHook {
891 calls: Arc::new(Mutex::new(Vec::new())),
892 };
893 let shared_hook = SharedHook::new(hook);
894
895 let model = TestParallelModel {
896 sync_event_count: Arc::new(AtomicUsize::new(0)),
897 parallel_command_count: Arc::new(AtomicUsize::new(0)),
898 };
899 let mut engine = Engine::new();
900 engine.add_shared_hook(shared_hook.clone());
901
902 engine.schedule_event_at(
904 SimTime::from_ticks(1),
905 EventPriority::new(0),
906 TestEvent::ParallelTarget,
907 );
908
909 let mut runner = ParallelRunner::new(true, EventPriority::new(5));
910
911 let should_stop = |_m: &TestParallelModel, _status: ExecutorStatus, tick: TickStatus| {
913 tick.is_done_ticks(true, 2)
914 };
915
916 let _result = runner.run(engine, model, should_stop);
917
918 let final_calls = shared_hook.get_ref().calls.lock().unwrap();
919
920 let expected = vec![
921 HookCall::BeforeSimulation,
922 HookCall::BeforeTick {
924 current: SimTime::from_ticks(0),
925 skipped: Duration::zero(),
926 },
927 HookCall::BeforeMicroStep {
928 current: SimTime::from_ticks(0),
929 micro: MicroStep::zero(),
930 },
931 HookCall::BeforeSourcePhase {
932 current: SimTime::from_ticks(0),
933 micro: MicroStep::zero(),
934 },
935 HookCall::AfterSourcePhase {
936 current: SimTime::from_ticks(0),
937 micro: MicroStep::zero(),
938 },
939 HookCall::BeforeEventPhase {
940 current: SimTime::from_ticks(0),
941 micro: MicroStep::zero(),
942 },
943 HookCall::AfterEventPhase {
944 current: SimTime::from_ticks(0),
945 micro: MicroStep::zero(),
946 },
947 HookCall::AfterMicroStep {
948 current: SimTime::from_ticks(0),
949 micro: MicroStep::zero(),
950 },
951 HookCall::AfterTick {
952 current: SimTime::from_ticks(0),
953 last_micro: MicroStep::zero(),
954 },
955 HookCall::BeforeTick {
957 current: SimTime::from_ticks(1),
958 skipped: Duration::ticks(0),
959 },
960 HookCall::BeforeMicroStep {
961 current: SimTime::from_ticks(1),
962 micro: MicroStep::zero(),
963 },
964 HookCall::BeforeSourcePhase {
965 current: SimTime::from_ticks(1),
966 micro: MicroStep::zero(),
967 },
968 HookCall::AfterSourcePhase {
969 current: SimTime::from_ticks(1),
970 micro: MicroStep::zero(),
971 },
972 HookCall::BeforeEventPhase {
973 current: SimTime::from_ticks(1),
974 micro: MicroStep::zero(),
975 },
976 HookCall::AfterEventPhase {
977 current: SimTime::from_ticks(1),
978 micro: MicroStep::zero(),
979 },
980 HookCall::AfterMicroStep {
981 current: SimTime::from_ticks(1),
982 micro: MicroStep::zero(),
983 },
984 HookCall::AfterTick {
985 current: SimTime::from_ticks(1),
986 last_micro: MicroStep::zero(),
987 },
988 HookCall::AfterSimulation(SimTime::from_ticks(1)),
989 ];
990
991 assert_eq!(*final_calls, expected);
992 }
993
994 #[test]
995 fn test_parallel_runner_hook_lifecycle_flow_without_include_zero_tick() {
996 use crate::modeling::hook::instance::SharedHook;
997
998 let hook = MockHook {
999 calls: Arc::new(Mutex::new(Vec::new())),
1000 };
1001 let shared_hook = SharedHook::new(hook);
1002
1003 let model = TestParallelModel {
1004 sync_event_count: Arc::new(AtomicUsize::new(0)),
1005 parallel_command_count: Arc::new(AtomicUsize::new(0)),
1006 };
1007 let mut engine = Engine::new();
1008 engine.add_shared_hook(shared_hook.clone());
1009
1010 engine.schedule_event_at(
1012 SimTime::from_ticks(1),
1013 EventPriority::new(0),
1014 TestEvent::ParallelTarget,
1015 );
1016
1017 let mut runner = ParallelRunner::new(true, EventPriority::new(5));
1018
1019 let should_stop = |_m: &TestParallelModel, _status: ExecutorStatus, tick: TickStatus| {
1021 tick.is_done_ticks(false, 2)
1022 };
1023
1024 let _result = runner.run(engine, model, should_stop);
1025
1026 let final_calls = shared_hook.get_ref().calls.lock().unwrap();
1027
1028 let expected = vec![
1029 HookCall::BeforeSimulation,
1030 HookCall::BeforeTick {
1032 current: SimTime::from_ticks(0),
1033 skipped: Duration::zero(),
1034 },
1035 HookCall::BeforeMicroStep {
1036 current: SimTime::from_ticks(0),
1037 micro: MicroStep::zero(),
1038 },
1039 HookCall::BeforeSourcePhase {
1040 current: SimTime::from_ticks(0),
1041 micro: MicroStep::zero(),
1042 },
1043 HookCall::AfterSourcePhase {
1044 current: SimTime::from_ticks(0),
1045 micro: MicroStep::zero(),
1046 },
1047 HookCall::BeforeEventPhase {
1048 current: SimTime::from_ticks(0),
1049 micro: MicroStep::zero(),
1050 },
1051 HookCall::AfterEventPhase {
1052 current: SimTime::from_ticks(0),
1053 micro: MicroStep::zero(),
1054 },
1055 HookCall::AfterMicroStep {
1056 current: SimTime::from_ticks(0),
1057 micro: MicroStep::zero(),
1058 },
1059 HookCall::AfterTick {
1060 current: SimTime::from_ticks(0),
1061 last_micro: MicroStep::zero(),
1062 },
1063 HookCall::BeforeTick {
1065 current: SimTime::from_ticks(1),
1066 skipped: Duration::ticks(0),
1067 },
1068 HookCall::BeforeMicroStep {
1069 current: SimTime::from_ticks(1),
1070 micro: MicroStep::zero(),
1071 },
1072 HookCall::BeforeSourcePhase {
1073 current: SimTime::from_ticks(1),
1074 micro: MicroStep::zero(),
1075 },
1076 HookCall::AfterSourcePhase {
1077 current: SimTime::from_ticks(1),
1078 micro: MicroStep::zero(),
1079 },
1080 HookCall::BeforeEventPhase {
1081 current: SimTime::from_ticks(1),
1082 micro: MicroStep::zero(),
1083 },
1084 HookCall::AfterEventPhase {
1085 current: SimTime::from_ticks(1),
1086 micro: MicroStep::zero(),
1087 },
1088 HookCall::AfterMicroStep {
1089 current: SimTime::from_ticks(1),
1090 micro: MicroStep::zero(),
1091 },
1092 HookCall::AfterTick {
1093 current: SimTime::from_ticks(1),
1094 last_micro: MicroStep::zero(),
1095 },
1096 HookCall::BeforeTick {
1098 current: SimTime::from_ticks(2),
1099 skipped: Duration::zero(),
1100 },
1101 HookCall::BeforeMicroStep {
1102 current: SimTime::from_ticks(2),
1103 micro: MicroStep::zero(),
1104 },
1105 HookCall::BeforeSourcePhase {
1106 current: SimTime::from_ticks(2),
1107 micro: MicroStep::zero(),
1108 },
1109 HookCall::AfterSourcePhase {
1110 current: SimTime::from_ticks(2),
1111 micro: MicroStep::zero(),
1112 },
1113 HookCall::BeforeEventPhase {
1114 current: SimTime::from_ticks(2),
1115 micro: MicroStep::zero(),
1116 },
1117 HookCall::AfterEventPhase {
1118 current: SimTime::from_ticks(2),
1119 micro: MicroStep::zero(),
1120 },
1121 HookCall::AfterMicroStep {
1122 current: SimTime::from_ticks(2),
1123 micro: MicroStep::zero(),
1124 },
1125 HookCall::AfterTick {
1126 current: SimTime::from_ticks(2),
1127 last_micro: MicroStep::zero(),
1128 },
1129 HookCall::AfterSimulation(SimTime::from_ticks(2)),
1130 ];
1131
1132 assert_eq!(*final_calls, expected);
1133 }
1134}