Skip to main content

des_sim/context/
executor.rs

1//! The `executor` module provides `ExecutorContext` and `ActiveExecutorContext`, which are internal
2//! contexts used by the simulation runner to manage the simulation's state and progression.
3//!
4//! These contexts offer low-level control over ticks and micro-steps, and interact with hooks
5//! and schedulers to advance the simulation.
6
7use crate::event_scheduler::EventScheduler;
8use crate::execution::phase::MicroStepHandler;
9use crate::execution::{SimulationError, SimulationOutput, SimulationResult};
10use crate::modeling::event::Event;
11use crate::modeling::hook::Hook;
12use crate::modeling::hook::instance::HookDelegate;
13use crate::modeling::model::Model;
14use crate::primitive::time::{Duration, SimTime};
15use crate::primitive::time::{MicroStepStatus, TickStatus};
16use crate::source_handler::{SourceHandler, SourceReadyEntry};
17use std::cmp::min;
18use std::collections::VecDeque;
19
20/// Represents the current status of the execution engine.
21#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
22pub enum ExecutorStatus {
23    /// No further events are scheduled.
24    NoMoreEvent,
25    /// More events are scheduled for execution.
26    ExistsMoreEvent,
27}
28
29/// Manages the simulation state at the execution engine level.
30///
31/// This structure is used by the runner to control the simulation's progression.
32/// Unlike the user-facing context, this provides a low-level interface for
33/// controlling internal simulation steps and hook processing.
34pub struct ExecutorContext<E, M: Model<E>> {
35    pub(crate) next_tick_status: TickStatus,
36    pub(crate) current_tick: SimTime,
37    pub(crate) hook_delegate: HookDelegate<E, M>,
38    pub(crate) source_handler: SourceHandler<E, M>,
39    pub(crate) event_scheduler: EventScheduler<E>,
40}
41
42impl<E, M: Model<E>> ExecutorContext<E, M> {
43    /// Retrieves the hooks associated with the current process.
44    pub(crate) fn hook(&self) -> &impl Hook<E, M> {
45        &self.hook_delegate
46    }
47
48    /// Previews the status of the next tick.
49    ///
50    /// # Returns
51    /// An `ExecutorStatus` indicating if events exist, and the next tick information.
52    pub fn peek_next_tick(&self) -> (ExecutorStatus, TickStatus) {
53        let next_event_fired_at = self.event_scheduler.peek_next_time();
54        let executor_status = if next_event_fired_at.is_some() {
55            ExecutorStatus::ExistsMoreEvent
56        } else {
57            ExecutorStatus::NoMoreEvent
58        };
59
60        (executor_status, self.next_tick_status)
61    }
62
63    /// Initiates processing for the current tick and transitions to an active context.
64    ///
65    /// Invokes the `before_tick` hook and updates the simulation state.
66    pub fn begin_tick(self, model: &M) -> ActiveExecutorContext<E, M> {
67        self.hook_delegate.before_tick(
68            model,
69            self.next_tick_status.current(),
70            self.next_tick_status.skipped(),
71        );
72
73        ActiveExecutorContext {
74            // From here, the current TickStatus
75            current_tick_status: self.next_tick_status,
76            next_micro_step_status: MicroStepStatus::initialize(),
77            hook_delegate: self.hook_delegate,
78            source_handler: self.source_handler,
79            event_scheduler: self.event_scheduler,
80        }
81    }
82
83    /// Terminates the simulation successfully and returns the result.
84    pub fn end_simulation_as_ok<Err>(self, model: M) -> SimulationResult<M, Err> {
85        self.hook().after_simulation(&model, self.current_tick);
86        Ok(SimulationOutput::new(self.current_tick, model))
87    }
88
89    /// Terminates the simulation with an error and returns the result.
90    pub fn end_simulation_as_error<Err>(self, model: M, error: Err) -> SimulationResult<M, Err> {
91        self.hook().after_simulation(&model, self.current_tick);
92        Err(SimulationError::new(self.current_tick, model, error))
93    }
94}
95
96/// Context for tick processing where micro-steps can be executed.
97pub struct ActiveExecutorContext<E, M: Model<E>> {
98    pub(crate) current_tick_status: TickStatus,
99    /// Holds the state of the future micro-step, as none have started yet.
100    pub(crate) next_micro_step_status: MicroStepStatus,
101    pub(crate) hook_delegate: HookDelegate<E, M>,
102    pub(crate) source_handler: SourceHandler<E, M>,
103    pub(crate) event_scheduler: EventScheduler<E>,
104}
105
106impl<E, M: Model<E>> ActiveExecutorContext<E, M> {
107    pub(crate) fn hook(&self) -> &impl Hook<E, M> {
108        &self.hook_delegate
109    }
110
111    /// Initiates micro-step processing.
112    pub fn begin_micro_step(self, model: &M) -> MicroStepHandler<ActiveExecutorContext<E, M>> {
113        self.hook().before_micro_step(
114            model,
115            self.current_tick_status.current(),
116            self.next_micro_step_status.current(),
117        );
118
119        MicroStepHandler::new(self)
120    }
121
122    /// Ends the current tick and advances to the next tick by incrementing by 1.
123    pub fn end_tick_with_increment_tick(self, model: &M) -> ExecutorContext<E, M> {
124        let current_tick = self.current_tick_status.current();
125        self.hook()
126            .after_tick(model, current_tick, self.next_micro_step_status.current());
127
128        let next_tick = current_tick + Duration::one();
129        let next_tick_status = TickStatus::new(next_tick, Duration::zero());
130
131        ExecutorContext {
132            next_tick_status,
133            current_tick,
134            hook_delegate: self.hook_delegate,
135            source_handler: self.source_handler,
136            event_scheduler: self.event_scheduler,
137        }
138    }
139
140    /// Ends the current tick and jumps to the next tick based on scheduled events or sources.
141    /// If no further scheduled times are found, defaults to incrementing by 1.
142    pub fn end_tick_with_jump_to_next_tick(self, model: &M) -> ExecutorContext<E, M> {
143        let current_tick = self.current_tick_status.current();
144        self.hook()
145            .after_tick(model, current_tick, self.next_micro_step_status.current());
146
147        // Calculate the next event time and the duration of the skipped period.
148        let (skipped, next_tick) = match (
149            self.source_handler.peek_next_time(),
150            self.event_scheduler.peek_next_time(),
151        ) {
152            (Some(next_scheduled_at), None) | (None, Some(next_scheduled_at)) => (
153                next_scheduled_at - current_tick - Duration::one(),
154                next_scheduled_at,
155            ),
156            (Some(source_next_scheduled_at), Some(event_next_scheduled_at)) => {
157                let next_scheduled_at = min(source_next_scheduled_at, event_next_scheduled_at);
158                (
159                    next_scheduled_at - current_tick - Duration::one(),
160                    next_scheduled_at,
161                )
162            }
163            (_, _) => {
164                // Nothing else is scheduled; proceed to the immediate next tick.
165                (Duration::zero(), current_tick + Duration::one())
166            }
167        };
168        let next_tick_status = TickStatus::new(next_tick, skipped);
169
170        ExecutorContext {
171            next_tick_status,
172            current_tick,
173            hook_delegate: self.hook_delegate,
174            source_handler: self.source_handler,
175            event_scheduler: self.event_scheduler,
176        }
177    }
178
179    /// Discards any remaining micro-steps in the current tick.
180    ///
181    /// # Returns
182    /// A pair containing the discarded sources and events.
183    pub fn discard_remain_micro_step(
184        &mut self,
185        model: &M,
186    ) -> (VecDeque<SourceReadyEntry>, VecDeque<Event<E>>) {
187        let current_tick = self.current_tick_status.current();
188        let mut ready_sources = self.source_handler.drain_ready(current_tick);
189        let mut ready_events = self.event_scheduler.drain_ready(current_tick);
190
191        self.hook().on_discard_remain_micro_step(
192            model,
193            current_tick,
194            self.next_micro_step_status.current(),
195            ready_sources.make_contiguous(),
196            ready_events.make_contiguous(),
197        );
198
199        (ready_sources, ready_events)
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206    use crate::context::{EventContext, SourceContext, UserContext};
207    use crate::modeling::event::{Event, EventPriority};
208    use crate::modeling::hook::instance::SharedHook;
209    use crate::modeling::source::Source;
210    use crate::primitive::time::{Duration, MicroStep, SimTime};
211    use crate::source_handler::SourceView;
212    use std::cell::RefCell;
213    use std::rc::Rc;
214
215    /// Represents a simple event payload for testing.
216    #[derive(Debug, PartialEq, Eq, Copy, Clone)]
217    struct TestEvent;
218
219    /// Represents a source that triggers at a fixed tick duration.
220    #[derive(Debug, PartialEq, Eq, Copy, Clone)]
221    struct TestSource {
222        tick: Duration,
223    }
224
225    impl Source<TestEvent, TestModel> for TestSource {
226        fn on_registered(
227            &mut self,
228            _context: &mut dyn UserContext<TestEvent, TestModel>,
229            _model: &TestModel,
230        ) -> Option<Duration> {
231            Some(self.tick)
232        }
233
234        fn fire(
235            &mut self,
236            _context: &mut SourceContext<TestEvent, TestModel>,
237            _model: &TestModel,
238        ) -> Option<Duration> {
239            Some(self.tick)
240        }
241    }
242
243    /// A simple model implementation for testing context interactions.
244    #[derive(Debug)]
245    struct TestModel;
246
247    impl Model<TestEvent> for TestModel {
248        fn handle_event(
249            &mut self,
250            _context: &mut EventContext<TestEvent, Self>,
251            _event: &Event<TestEvent>,
252        ) {
253            // No-op
254        }
255    }
256
257    /// A mock hook that tracks method invocations for verification.
258    #[derive(Default)]
259    struct MockHook {
260        before_tick_called: Rc<RefCell<Vec<(SimTime, Duration)>>>,
261        after_tick_called: Rc<RefCell<Vec<(SimTime, MicroStep)>>>,
262        before_micro_step_called: Rc<RefCell<Vec<(SimTime, MicroStep)>>>,
263        #[allow(clippy::type_complexity)]
264        on_discard_remain_micro_step_called: Rc<RefCell<Vec<(SimTime, MicroStep, usize, usize)>>>,
265        after_simulation_called: Rc<RefCell<Vec<SimTime>>>,
266    }
267
268    impl MockHook {
269        fn new() -> Self {
270            Default::default()
271        }
272    }
273
274    impl<E, M: Model<E>> Hook<E, M> for MockHook {
275        fn before_simulation(&self, _model: &M) {
276            unreachable!();
277        }
278
279        fn after_simulation(&self, _model: &M, last_tick: SimTime) {
280            self.after_simulation_called.borrow_mut().push(last_tick);
281        }
282
283        fn before_tick(&self, _model: &M, now: SimTime, skipped: Duration) {
284            self.before_tick_called.borrow_mut().push((now, skipped));
285        }
286
287        fn after_tick(&self, _model: &M, now: SimTime, micro_step: MicroStep) {
288            self.after_tick_called.borrow_mut().push((now, micro_step));
289        }
290
291        fn before_micro_step(&self, _model: &M, now: SimTime, micro_step: MicroStep) {
292            self.before_micro_step_called
293                .borrow_mut()
294                .push((now, micro_step));
295        }
296
297        fn after_micro_step(
298            &self,
299            _model: &M,
300            _current_tick: SimTime,
301            _current_micro_step: MicroStep,
302        ) {
303            unreachable!();
304        }
305
306        fn on_discard_remain_micro_step(
307            &self,
308            _model: &M,
309            now: SimTime,
310            micro_step: MicroStep,
311            ready_sources: &[SourceReadyEntry],
312            ready_events: &[Event<E>],
313        ) {
314            self.on_discard_remain_micro_step_called.borrow_mut().push((
315                now,
316                micro_step,
317                ready_sources.len(),
318                ready_events.len(),
319            ));
320        }
321
322        fn before_register_source(&self, _model: &M, _name: &str) {
323            unreachable!();
324        }
325
326        fn after_register_source(&self, _model: &M, _name: &str) {
327            unreachable!();
328        }
329
330        fn before_source_phase(
331            &self,
332            _model: &M,
333            _current_tick: SimTime,
334            _current_micro_step: MicroStep,
335        ) {
336            unreachable!();
337        }
338
339        fn before_source(
340            &self,
341            _model: &M,
342            _current_tick: SimTime,
343            _current_micro_step: MicroStep,
344            _source_view: &SourceView,
345        ) {
346            unreachable!();
347        }
348
349        fn after_source(
350            &self,
351            _model: &M,
352            _current_tick: SimTime,
353            _current_micro_step: MicroStep,
354            _source_view: &SourceView,
355            _computed_next_fire: Option<SimTime>,
356        ) {
357            unreachable!();
358        }
359
360        fn cancel_source(
361            &self,
362            _model: &M,
363            _current_tick: SimTime,
364            _current_micro_step: MicroStep,
365            _scheduled_at: SimTime,
366            _source_view: &SourceView,
367        ) {
368            unreachable!();
369        }
370
371        fn discard_source(
372            &self,
373            _model: &M,
374            _current_tick: SimTime,
375            _current_micro_step: MicroStep,
376            _source_view: &SourceView,
377        ) {
378            unreachable!();
379        }
380
381        fn after_source_phase(
382            &self,
383            _model: &M,
384            _current_tick: SimTime,
385            _current_micro_step: MicroStep,
386        ) {
387            unreachable!();
388        }
389
390        fn before_event_phase(
391            &self,
392            _model: &M,
393            _current_tick: SimTime,
394            _current_micro_step: MicroStep,
395        ) {
396            unreachable!();
397        }
398
399        fn before_event(
400            &self,
401            _model: &M,
402            _current_tick: SimTime,
403            _current_micro_step: MicroStep,
404            _event: &Event<E>,
405        ) {
406            // 呼ばれないことを確認する
407            unreachable!();
408        }
409
410        fn after_event(
411            &self,
412            _model: &M,
413            _current_tick: SimTime,
414            _current_micro_step: MicroStep,
415            _event: &Event<E>,
416        ) {
417            // 呼ばれないことを確認する
418            unreachable!();
419        }
420
421        fn cancel_event(
422            &self,
423            _model: &M,
424            _current_tick: SimTime,
425            _current_micro_step: MicroStep,
426            _scheduled_at: SimTime,
427            _event: &Event<E>,
428        ) {
429            // 呼ばれないことを確認する
430            unreachable!();
431        }
432
433        fn discard_event(
434            &self,
435            _model: &M,
436            _current_tick: SimTime,
437            _current_micro_step: MicroStep,
438            _event: &Event<E>,
439        ) {
440            // 呼ばれないことを確認する
441            unreachable!();
442        }
443
444        fn after_event_phase(
445            &self,
446            _model: &M,
447            _current_tick: SimTime,
448            _current_micro_step: MicroStep,
449        ) {
450            // 呼ばれないことを確認する
451            unreachable!();
452        }
453    }
454
455    fn create_mock_executor_context(
456        current_tick: SimTime,
457        next_tick_status: TickStatus,
458    ) -> (
459        ExecutorContext<TestEvent, TestModel>,
460        SharedHook<TestEvent, TestModel, MockHook>,
461    ) {
462        let test_hook = MockHook::new();
463        let shared_hook = SharedHook::new(test_hook);
464        let mut hook_delegate = HookDelegate::new();
465        hook_delegate.add_shared_hook(shared_hook.clone());
466        let context = ExecutorContext {
467            next_tick_status,
468            current_tick,
469            hook_delegate,
470            source_handler: SourceHandler::new(),
471            event_scheduler: EventScheduler::new(),
472        };
473        (context, shared_hook)
474    }
475
476    #[test]
477    fn test_executor_context_peek_next_tick_no_event() {
478        let current_tick = SimTime::from_ticks(0);
479        let next_tick_status = TickStatus::new(SimTime::from_ticks(1), Duration::zero());
480
481        let (context, _) = create_mock_executor_context(current_tick, next_tick_status);
482
483        let (status, tick_status) = context.peek_next_tick();
484        assert_eq!(status, ExecutorStatus::NoMoreEvent);
485        assert_eq!(tick_status, next_tick_status);
486    }
487
488    #[test]
489    fn test_executor_context_peek_next_tick_with_event() {
490        let current_tick = SimTime::from_ticks(0);
491        let next_tick_status = TickStatus::new(SimTime::from_ticks(1), Duration::zero());
492        let (mut context, _) = create_mock_executor_context(current_tick, next_tick_status);
493
494        context.event_scheduler.schedule(
495            SimTime::from_ticks(5),
496            Duration::ticks(0),
497            EventPriority::minimum(),
498            TestEvent,
499        );
500        context.event_scheduler.flush_pending();
501
502        assert_eq!(
503            context.event_scheduler.peek_next_time(),
504            Some(SimTime::from_ticks(5))
505        );
506
507        let (status, tick_status) = context.peek_next_tick();
508        assert_eq!(status, ExecutorStatus::ExistsMoreEvent);
509        assert_eq!(tick_status, next_tick_status);
510    }
511
512    #[test]
513    fn test_executor_context_begin_tick() {
514        let current_tick = SimTime::from_ticks(0);
515        let next_tick_status = TickStatus::new(SimTime::from_ticks(1), Duration::zero());
516        let (context, shared_hook) = create_mock_executor_context(current_tick, next_tick_status);
517
518        let model = TestModel;
519        let active_context = context.begin_tick(&model);
520
521        assert_eq!(active_context.current_tick_status, next_tick_status);
522        assert_eq!(
523            active_context.next_micro_step_status,
524            MicroStepStatus::initialize()
525        );
526        assert_eq!(shared_hook.get_ref().before_tick_called.borrow().len(), 1);
527        assert_eq!(
528            shared_hook.get_ref().before_tick_called.borrow()[0],
529            (SimTime::from_ticks(1), Duration::zero())
530        );
531    }
532
533    #[test]
534    fn test_executor_context_end_simulation_as_ok() {
535        let current_tick = SimTime::from_ticks(10);
536        let next_tick_status = TickStatus::new(SimTime::from_ticks(11), Duration::zero());
537        let (context, shared_hook) = create_mock_executor_context(current_tick, next_tick_status);
538
539        let model = TestModel;
540        let result: SimulationResult<TestModel, &'static str> = context.end_simulation_as_ok(model);
541
542        assert!(result.is_ok());
543        let output = result.unwrap();
544        assert_eq!(output.last_tick(), current_tick);
545        assert_eq!(
546            shared_hook.get_ref().after_simulation_called.borrow().len(),
547            1
548        );
549        assert_eq!(
550            shared_hook.get_ref().after_simulation_called.borrow()[0],
551            current_tick
552        );
553    }
554
555    #[test]
556    fn test_executor_context_end_simulation_as_error() {
557        let current_tick = SimTime::from_ticks(10);
558        let next_tick_status = TickStatus::new(SimTime::from_ticks(11), Duration::zero());
559        let (context, shared_hook) = create_mock_executor_context(current_tick, next_tick_status);
560
561        let model = TestModel;
562        let error_msg = "Simulation failed";
563        let result = context.end_simulation_as_error(model, error_msg);
564
565        assert!(result.is_err());
566        let error = result.unwrap_err();
567        assert_eq!(error.last_tick(), current_tick);
568        assert_eq!(error.error(), &error_msg);
569        assert_eq!(
570            shared_hook.get_ref().after_simulation_called.borrow().len(),
571            1
572        );
573        assert_eq!(
574            shared_hook.get_ref().after_simulation_called.borrow()[0],
575            current_tick
576        );
577    }
578
579    // ActiveExecutorContext Tests
580    fn create_mock_active_executor_context(
581        current_tick_status: TickStatus,
582        next_micro_step_status: MicroStepStatus,
583    ) -> (
584        ActiveExecutorContext<TestEvent, TestModel>,
585        SharedHook<TestEvent, TestModel, MockHook>,
586    ) {
587        let test_hook = MockHook::new();
588        let shared_hook = SharedHook::new(test_hook);
589        let mut hook_delegate = HookDelegate::new();
590        hook_delegate.add_shared_hook(shared_hook.clone());
591        let context = ActiveExecutorContext {
592            current_tick_status,
593            next_micro_step_status,
594            hook_delegate,
595            source_handler: SourceHandler::new(),
596            event_scheduler: EventScheduler::new(),
597        };
598        (context, shared_hook)
599    }
600
601    #[test]
602    fn test_active_executor_context_begin_micro_step() {
603        let current_tick_status = TickStatus::new(SimTime::from_ticks(5), Duration::zero());
604        let next_micro_step_status = MicroStepStatus::new(MicroStep::zero());
605        let (context, shared_hook) =
606            create_mock_active_executor_context(current_tick_status, next_micro_step_status);
607
608        let model = TestModel;
609        let micro_step_handler = context.begin_micro_step(&model);
610
611        assert_eq!(
612            micro_step_handler.ref_context().current_tick_status,
613            current_tick_status
614        );
615        assert_eq!(
616            shared_hook
617                .get_ref()
618                .before_micro_step_called
619                .borrow()
620                .len(),
621            1
622        );
623        assert_eq!(
624            shared_hook.get_ref().before_micro_step_called.borrow()[0],
625            (SimTime::from_ticks(5), MicroStep::zero())
626        );
627    }
628
629    #[test]
630    fn test_active_executor_context_end_tick_with_increment_tick() {
631        let current_tick_status = TickStatus::new(SimTime::from_ticks(5), Duration::zero());
632        let mut micro_step_ten = MicroStep::zero();
633        for _ in 0..10 {
634            micro_step_ten = micro_step_ten.next();
635        }
636        let next_micro_step_status = MicroStepStatus::new(micro_step_ten);
637        let (context, shared_hook) =
638            create_mock_active_executor_context(current_tick_status, next_micro_step_status);
639
640        let model = TestModel;
641        let next_context = context.end_tick_with_increment_tick(&model);
642
643        assert_eq!(next_context.current_tick, SimTime::from_ticks(5));
644        assert_eq!(
645            next_context.next_tick_status.current(),
646            SimTime::from_ticks(6)
647        );
648        assert_eq!(next_context.next_tick_status.skipped(), Duration::zero());
649        assert_eq!(shared_hook.get_ref().after_tick_called.borrow().len(), 1);
650        assert_eq!(
651            shared_hook.get_ref().after_tick_called.borrow()[0],
652            (SimTime::from_ticks(5), micro_step_ten)
653        );
654    }
655
656    #[test]
657    fn test_active_executor_context_end_tick_with_jump_to_next_tick_only_source() {
658        let current_tick_status = TickStatus::new(SimTime::from_ticks(5), Duration::zero());
659        let mut micro_step_ten = MicroStep::zero();
660        for _ in 0..10 {
661            micro_step_ten = micro_step_ten.next();
662        }
663        let next_micro_step_status = MicroStepStatus::new(micro_step_ten);
664        let (mut context, shared_hook) =
665            create_mock_active_executor_context(current_tick_status, next_micro_step_status);
666
667        context.source_handler.add_source_after_registered_action(
668            "test source",
669            current_tick_status.current(),
670            Some(Duration::ticks(7)),
671            TestSource {
672                tick: Duration::ticks(3),
673            },
674        );
675        context.source_handler.flush_pending();
676
677        let model = TestModel;
678        let next_context = context.end_tick_with_jump_to_next_tick(&model);
679
680        assert_eq!(next_context.current_tick, SimTime::from_ticks(5));
681        assert_eq!(
682            next_context.next_tick_status.current(),
683            SimTime::from_ticks(12)
684        );
685        assert_eq!(next_context.next_tick_status.skipped(), Duration::ticks(6));
686        assert_eq!(shared_hook.get_ref().after_tick_called.borrow().len(), 1);
687        assert_eq!(
688            shared_hook.get_ref().after_tick_called.borrow()[0],
689            (SimTime::from_ticks(5), micro_step_ten)
690        );
691    }
692
693    #[test]
694    fn test_active_executor_context_end_tick_with_jump_to_next_tick_only_event() {
695        let current_tick_status = TickStatus::new(SimTime::from_ticks(5), Duration::zero());
696        let mut micro_step_ten = MicroStep::zero();
697        for _ in 0..10 {
698            micro_step_ten = micro_step_ten.next();
699        }
700        let next_micro_step_status = MicroStepStatus::new(micro_step_ten);
701        let (mut context, shared_hook) =
702            create_mock_active_executor_context(current_tick_status, next_micro_step_status);
703        context.event_scheduler.schedule(
704            SimTime::from_ticks(5),
705            Duration::ticks(5),
706            EventPriority::minimum(),
707            TestEvent,
708        );
709        context.event_scheduler.flush_pending();
710
711        let model = TestModel;
712        let next_context = context.end_tick_with_jump_to_next_tick(&model);
713
714        assert_eq!(next_context.current_tick, SimTime::from_ticks(5));
715        assert_eq!(
716            next_context.next_tick_status.current(),
717            SimTime::from_ticks(10)
718        );
719        assert_eq!(next_context.next_tick_status.skipped(), Duration::ticks(4));
720        assert_eq!(shared_hook.get_ref().after_tick_called.borrow().len(), 1);
721        assert_eq!(
722            shared_hook.get_ref().after_tick_called.borrow()[0],
723            (SimTime::from_ticks(5), micro_step_ten)
724        );
725    }
726
727    #[test]
728    fn test_active_executor_context_end_tick_with_jump_to_next_tick_both_event_and_source() {
729        let current_tick_status = TickStatus::new(SimTime::from_ticks(5), Duration::zero());
730        let mut micro_step_ten = MicroStep::zero();
731        for _ in 0..10 {
732            micro_step_ten = micro_step_ten.next();
733        }
734        let next_micro_step_status = MicroStepStatus::new(micro_step_ten);
735        let (mut context, shared_hook) =
736            create_mock_active_executor_context(current_tick_status, next_micro_step_status);
737        context.source_handler.add_source_after_registered_action(
738            "test source",
739            current_tick_status.current(),
740            Some(Duration::ticks(7)),
741            TestSource {
742                tick: Duration::ticks(3),
743            },
744        );
745        context.event_scheduler.schedule(
746            SimTime::from_ticks(5),
747            Duration::ticks(5),
748            EventPriority::minimum(),
749            TestEvent,
750        );
751        context.source_handler.flush_pending();
752        context.event_scheduler.flush_pending();
753
754        let model = TestModel;
755        let next_context = context.end_tick_with_jump_to_next_tick(&model);
756
757        assert_eq!(next_context.current_tick, SimTime::from_ticks(5));
758        assert_eq!(
759            next_context.next_tick_status.current(),
760            SimTime::from_ticks(10)
761        ); // min(10, 12) = 10
762        assert_eq!(next_context.next_tick_status.skipped(), Duration::ticks(4));
763        assert_eq!(shared_hook.get_ref().after_tick_called.borrow().len(), 1);
764        assert_eq!(
765            shared_hook.get_ref().after_tick_called.borrow()[0],
766            (SimTime::from_ticks(5), micro_step_ten)
767        );
768    }
769
770    #[test]
771    fn test_active_executor_context_end_tick_with_jump_to_next_tick_no_next() {
772        let current_tick_status = TickStatus::new(SimTime::from_ticks(5), Duration::zero());
773        let mut micro_step_ten = MicroStep::zero();
774        for _ in 0..10 {
775            micro_step_ten = micro_step_ten.next();
776        }
777        let next_micro_step_status = MicroStepStatus::new(micro_step_ten);
778        let (context, shared_hook) =
779            create_mock_active_executor_context(current_tick_status, next_micro_step_status);
780
781        let model = TestModel;
782        let next_context = context.end_tick_with_jump_to_next_tick(&model);
783
784        assert_eq!(next_context.current_tick, SimTime::from_ticks(5));
785        assert_eq!(
786            next_context.next_tick_status.current(),
787            SimTime::from_ticks(6)
788        );
789        assert_eq!(next_context.next_tick_status.skipped(), Duration::zero());
790        assert_eq!(shared_hook.get_ref().after_tick_called.borrow().len(), 1);
791        assert_eq!(
792            shared_hook.get_ref().after_tick_called.borrow()[0],
793            (SimTime::from_ticks(5), micro_step_ten)
794        );
795    }
796
797    #[test]
798    fn test_active_executor_context_discard_remain_micro_step() {
799        let current_tick_status = TickStatus::new(SimTime::from_ticks(5), Duration::zero());
800        let mut micro_step_ten = MicroStep::zero();
801        for _ in 0..10 {
802            micro_step_ten = micro_step_ten.next();
803        }
804        let next_micro_step_status = MicroStepStatus::new(micro_step_ten);
805        let (mut context, shared_hook) =
806            create_mock_active_executor_context(current_tick_status, next_micro_step_status);
807
808        context.source_handler.add_source_after_registered_action(
809            "test source",
810            current_tick_status.current(),
811            Some(Duration::ticks(0)),
812            TestSource {
813                tick: Duration::ticks(3),
814            },
815        );
816        context.event_scheduler.schedule(
817            SimTime::from_ticks(5),
818            Duration::ticks(0),
819            EventPriority::minimum(),
820            TestEvent,
821        );
822        context.event_scheduler.schedule(
823            SimTime::from_ticks(5),
824            Duration::ticks(0),
825            EventPriority::minimum(),
826            TestEvent,
827        );
828        context.source_handler.flush_pending();
829        context.event_scheduler.flush_pending();
830
831        let model = TestModel;
832        let (discarded_sources, discarded_events) = context.discard_remain_micro_step(&model);
833
834        assert_eq!(discarded_sources.len(), 1);
835        assert_eq!(discarded_sources[0].name(), "test source");
836
837        assert_eq!(discarded_events.len(), 2);
838        assert_eq!(discarded_events[0].payload, TestEvent);
839        assert_eq!(discarded_events[1].payload, TestEvent);
840
841        assert_eq!(
842            shared_hook
843                .get_ref()
844                .on_discard_remain_micro_step_called
845                .borrow()
846                .len(),
847            1
848        );
849        assert_eq!(
850            shared_hook
851                .get_ref()
852                .on_discard_remain_micro_step_called
853                .borrow()[0],
854            (SimTime::from_ticks(5), micro_step_ten, 1, 2)
855        );
856    }
857}