Skip to main content

des_sim/execution/phase/
handler.rs

1//! The `handler` module defines `MicroStepHandler`, a central component for managing
2//! the execution flow within a simulation tick.
3//!
4//! It orchestrates the transitions between different micro-step phases (source and event processing)
5//! and ensures type-safe progression of the simulation state.
6
7use crate::context::{ActiveExecutorContext, EventContext, SourceContext};
8use crate::execution::phase::{EventPhase, MicroStepResult, SourcePhase, UncheckedActiveExecutor};
9use crate::modeling::hook::Hook;
10use crate::modeling::model::Model;
11use crate::primitive::time::MicroStepStatus;
12
13/// Controls the micro-step execution phases of the simulation.
14///
15/// This structure owns the simulation state (context) and guarantees phase transitions
16/// in a one-way (disposable) manner. By not implementing `Clone`, it ensures the safety
17/// of the execution flow at the type-system level.
18pub struct MicroStepHandler<CTX> {
19    context: CTX,
20}
21
22impl<CTX> MicroStepHandler<CTX> {
23    /// Creates a new handler.
24    pub(crate) fn new(context: CTX) -> MicroStepHandler<CTX> {
25        MicroStepHandler { context }
26    }
27
28    /// Returns an immutable reference to the context.
29    pub fn ref_context(&self) -> &CTX {
30        &self.context
31    }
32
33    /// Returns a mutable reference to the context.
34    pub fn ref_mut_context(&mut self) -> &mut CTX {
35        &mut self.context
36    }
37}
38
39impl<E, M: Model<E>> MicroStepHandler<ActiveExecutorContext<E, M>> {
40    /// Starts the source execution phase.
41    ///
42    /// Generates a `SourcePhase` from the current context and invokes the `before_source_phase` hook.
43    pub fn start_source_phase(self, model: &M) -> SourcePhase<E, M> {
44        let mut context = self.context;
45        context.hook().before_source_phase(
46            model,
47            context.current_tick_status.current(),
48            context.next_micro_step_status.current(),
49        );
50
51        let ready_sources = context
52            .source_handler
53            .drain_ready(context.current_tick_status.current());
54
55        SourcePhase::new(
56            SourceContext {
57                current_tick_status: context.current_tick_status,
58                current_micro_step_status: context.next_micro_step_status,
59                hook_delegate: context.hook_delegate,
60                source_handler: None,
61                event_scheduler: context.event_scheduler,
62            },
63            context.source_handler,
64            ready_sources,
65        )
66    }
67}
68
69impl<E, M: Model<E>> MicroStepHandler<SourceContext<E, M>> {
70    /// Transitions to the event execution phase.
71    ///
72    /// Invokes the `before_event_phase` hook, restores the pending `source_handler`,
73    /// and generates an `EventPhase`.
74    pub fn to_event_phase(self, model: &M) -> EventPhase<E, M> {
75        let mut context = self.context;
76        context.hook().before_event_phase(
77            model,
78            context.current_tick_status.current(),
79            context.current_micro_step_status.current(),
80        );
81
82        let ready_events = context
83            .event_scheduler
84            .drain_ready(context.current_tick_status.current());
85
86        EventPhase::new(
87            EventContext {
88                current_tick_status: context.current_tick_status,
89                current_micro_step_status: context.current_micro_step_status,
90                hook_delegate: context.hook_delegate,
91                source_handler: context
92                    .source_handler
93                    .expect("Failed to retrieve SourceHandler from SourcePhase."),
94                event_scheduler: context.event_scheduler,
95            },
96            ready_events,
97        )
98    }
99}
100
101impl<E, M: Model<E>> MicroStepHandler<EventContext<E, M>> {
102    /// Ends the current micro-step and returns the result (continue or complete).
103    ///
104    /// Checks for remaining executable events or sources within the current tick and
105    /// constructs the context for the next micro-step if necessary.
106    pub fn end_micro_step(mut self, model: &M) -> MicroStepResult<E, M> {
107        // Flush pending handlers to accurately peek at the next scheduled events/sources.
108        self.ref_mut_context().source_handler.flush_pending();
109        self.ref_mut_context().event_scheduler.flush_pending();
110        let current_tick = self.ref_context().current_tick_status.current();
111
112        // Check the next scheduled time for both events and sources.
113        let next_event_at = self.ref_context().event_scheduler.peek_next_time();
114        let next_source_at = self.ref_context().source_handler.peek_next_time();
115
116        let has_next_in_current_tick = matches!(next_event_at, Some(t) if t == current_tick)
117            || matches!(next_source_at, Some(t) if t == current_tick);
118
119        if has_next_in_current_tick {
120            // Still have work to do in the current tick; proceed to the next micro-step.
121            let current_micro_step = self.ref_context().current_micro_step_status.current();
122            let next_micro_step = current_micro_step.next();
123
124            self.context.hook().after_micro_step(
125                model,
126                current_tick,
127                self.ref_context().current_micro_step_status.current(),
128            );
129
130            let active_context = ActiveExecutorContext {
131                current_tick_status: self.ref_context().current_tick_status,
132                next_micro_step_status: MicroStepStatus::new(next_micro_step),
133                hook_delegate: self.context.hook_delegate,
134                source_handler: self.context.source_handler,
135                event_scheduler: self.context.event_scheduler,
136            };
137
138            MicroStepResult::Continue(UncheckedActiveExecutor::new(
139                active_context,
140                current_micro_step,
141            ))
142        } else {
143            // No more work in the current tick; terminate the micro-step.
144            let last_micro_step_status = self.ref_context().current_micro_step_status;
145
146            self.context.hook().after_micro_step(
147                model,
148                current_tick,
149                self.ref_context().current_micro_step_status.current(),
150            );
151
152            let active_context = ActiveExecutorContext {
153                current_tick_status: self.ref_context().current_tick_status,
154                next_micro_step_status: last_micro_step_status,
155                hook_delegate: self.context.hook_delegate,
156                source_handler: self.context.source_handler,
157                event_scheduler: self.context.event_scheduler,
158            };
159
160            MicroStepResult::Complete(active_context, last_micro_step_status)
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168    use crate::context::{ActiveExecutorContext, EventContext, SourceContext};
169    use crate::event_scheduler::EventScheduler;
170    use crate::modeling::event::{Event, EventPriority};
171    use crate::modeling::hook::instance::{HookDelegate, SharedHook};
172    use crate::modeling::model::Model;
173    use crate::primitive::time::{Duration, MicroStep, MicroStepStatus, SimTime, TickStatus};
174    use crate::source_handler::{SourceHandler, SourceReadyEntry, SourceView};
175    use std::rc::Rc;
176    use std::sync::Mutex;
177
178    #[derive(Debug, PartialEq, Eq, Clone, Copy)]
179    enum TestEvent {
180        A,
181    }
182
183    struct TestModel;
184
185    impl Model<TestEvent> for TestModel {
186        fn handle_event(
187            &mut self,
188            _context: &mut EventContext<TestEvent, Self>,
189            _event: &Event<TestEvent>,
190        ) {
191            // No-op
192        }
193    }
194
195    /// Hook used to track phase transition calls during tests.
196    struct TransitionTrackerHook {
197        called_before_source_phase: Rc<Mutex<bool>>,
198        called_before_event_phase: Rc<Mutex<bool>>,
199        called_after_micro_step: Rc<Mutex<bool>>,
200    }
201
202    impl Hook<TestEvent, TestModel> for TransitionTrackerHook {
203        fn before_simulation(&self, _model: &TestModel) {
204            // none
205        }
206        fn after_simulation(&self, _model: &TestModel, _end_tick: SimTime) {
207            // none
208        }
209        fn before_tick(
210            &self,
211            _model: &TestModel,
212            _current_tick: SimTime,
213            _skipped_duration: Duration,
214        ) {
215            // none
216        }
217        fn after_tick(
218            &self,
219            _model: &TestModel,
220            _current_tick: SimTime,
221            _last_micro_step: MicroStep,
222        ) {
223            // none
224        }
225        fn before_micro_step(
226            &self,
227            _model: &TestModel,
228            _current_tick: SimTime,
229            _current_micro_step: MicroStep,
230        ) {
231            // none
232        }
233        fn after_micro_step(
234            &self,
235            _model: &TestModel,
236            _current_tick: SimTime,
237            _current_micro_step: MicroStep,
238        ) {
239            *self.called_after_micro_step.lock().unwrap() = true;
240        }
241        fn on_discard_remain_micro_step(
242            &self,
243            _model: &TestModel,
244            _current_tick: SimTime,
245            _first_discarded_micro_step: MicroStep,
246            _discarded_sources: &[SourceReadyEntry],
247            _discarded_events: &[Event<TestEvent>],
248        ) {
249            // none
250        }
251        fn before_register_source(&self, _model: &TestModel, _name: &str) {
252            // none
253        }
254        fn after_register_source(&self, _model: &TestModel, _name: &str) {
255            // none
256        }
257        fn before_source_phase(
258            &self,
259            _model: &TestModel,
260            _current_tick: SimTime,
261            _current_micro_step: MicroStep,
262        ) {
263            *self.called_before_source_phase.lock().unwrap() = true;
264        }
265        fn before_source(
266            &self,
267            _model: &TestModel,
268            _current_tick: SimTime,
269            _current_micro_step: MicroStep,
270            _source_view: &SourceView,
271        ) {
272            // none
273        }
274        fn after_source(
275            &self,
276            _model: &TestModel,
277            _current_tick: SimTime,
278            _current_micro_step: MicroStep,
279            _source_view: &SourceView,
280            _computed_next_fire: Option<SimTime>,
281        ) {
282            // none
283        }
284        fn cancel_source(
285            &self,
286            _model: &TestModel,
287            _current_tick: SimTime,
288            _current_micro_step: MicroStep,
289            _scheduled_at: SimTime,
290            _source_view: &SourceView,
291        ) {
292            // none
293        }
294        fn discard_source(
295            &self,
296            _model: &TestModel,
297            _current_tick: SimTime,
298            _current_micro_step: MicroStep,
299            _source_view: &SourceView,
300        ) {
301            // none
302        }
303        fn after_source_phase(
304            &self,
305            _model: &TestModel,
306            _current_tick: SimTime,
307            _current_micro_step: MicroStep,
308        ) {
309            // none
310        }
311        fn before_event_phase(
312            &self,
313            _model: &TestModel,
314            _current_tick: SimTime,
315            _current_micro_step: MicroStep,
316        ) {
317            *self.called_before_event_phase.lock().unwrap() = true;
318        }
319        fn before_event(
320            &self,
321            _model: &TestModel,
322            _current_tick: SimTime,
323            _current_micro_step: MicroStep,
324            _event: &Event<TestEvent>,
325        ) {
326            // none
327        }
328        fn after_event(
329            &self,
330            _model: &TestModel,
331            _current_tick: SimTime,
332            _current_micro_step: MicroStep,
333            _event: &Event<TestEvent>,
334        ) {
335            // none
336        }
337        fn cancel_event(
338            &self,
339            _model: &TestModel,
340            _current_tick: SimTime,
341            _current_micro_step: MicroStep,
342            _scheduled_at: SimTime,
343            _event: &Event<TestEvent>,
344        ) {
345            // none
346        }
347        fn discard_event(
348            &self,
349            _model: &TestModel,
350            _current_tick: SimTime,
351            _current_micro_step: MicroStep,
352            _event: &Event<TestEvent>,
353        ) {
354            // none
355        }
356        fn after_event_phase(
357            &self,
358            _model: &TestModel,
359            _current_tick: SimTime,
360            _current_micro_step: MicroStep,
361        ) {
362            // none
363        }
364    }
365
366    fn setup_tracker_hook() -> SharedHook<TestEvent, TestModel, TransitionTrackerHook> {
367        SharedHook::new(TransitionTrackerHook {
368            called_before_source_phase: Rc::new(Mutex::new(false)),
369            called_before_event_phase: Rc::new(Mutex::new(false)),
370            called_after_micro_step: Rc::new(Mutex::new(false)),
371        })
372    }
373
374    #[test]
375    fn test_ref_context_and_mut() {
376        let mut handler = MicroStepHandler::new(42);
377        assert_eq!(*handler.ref_context(), 42);
378
379        *handler.ref_mut_context() = 100;
380        assert_eq!(*handler.ref_context(), 100);
381    }
382
383    #[test]
384    fn test_start_source_phase() {
385        let model = TestModel;
386        let hook = setup_tracker_hook();
387        let mut hook_delegate = HookDelegate::new();
388        hook_delegate.add_shared_hook(hook.clone());
389
390        let active_context = ActiveExecutorContext {
391            current_tick_status: TickStatus::initialize(),
392            next_micro_step_status: MicroStepStatus::initialize(),
393            hook_delegate,
394            source_handler: SourceHandler::new(),
395            event_scheduler: EventScheduler::new(),
396        };
397
398        let handler = MicroStepHandler::new(active_context);
399        let mut source_phase = handler.start_source_phase(&model);
400
401        assert!(source_phase.source_handler.is_some());
402        assert!(source_phase.get_context().source_handler.is_none());
403        assert!(*hook.get_ref().called_before_source_phase.lock().unwrap());
404    }
405
406    #[test]
407    fn test_to_event_phase() {
408        let model = TestModel;
409        let hook = setup_tracker_hook();
410        let mut hook_delegate = HookDelegate::new();
411        hook_delegate.add_shared_hook(hook.clone());
412
413        let source_context = SourceContext {
414            current_tick_status: TickStatus::initialize(),
415            current_micro_step_status: MicroStepStatus::initialize(),
416            hook_delegate,
417            source_handler: Some(SourceHandler::new()),
418            event_scheduler: EventScheduler::new(),
419        };
420
421        let handler = MicroStepHandler::new(source_context);
422        let _event_phase = handler.to_event_phase(&model);
423
424        assert!(*hook.get_ref().called_before_event_phase.lock().unwrap());
425    }
426
427    #[test]
428    fn test_end_micro_step_continue() {
429        let model = TestModel;
430        let hook = setup_tracker_hook();
431        let mut hook_delegate = HookDelegate::new();
432        hook_delegate.add_shared_hook(hook.clone());
433
434        let tick_status = TickStatus::initialize();
435        let current_time = tick_status.current();
436
437        let mut event_scheduler = EventScheduler::new();
438        event_scheduler.schedule(
439            current_time,
440            Duration::zero(),
441            EventPriority::minimum(),
442            TestEvent::A,
443        );
444        event_scheduler.flush_pending();
445
446        assert_eq!(event_scheduler.peek_next_time(), Some(current_time));
447
448        let event_context = EventContext {
449            current_tick_status: tick_status,
450            current_micro_step_status: MicroStepStatus::initialize(),
451            hook_delegate,
452            source_handler: SourceHandler::new(),
453            event_scheduler,
454        };
455
456        let handler = MicroStepHandler::new(event_context);
457        let result = handler.end_micro_step(&model);
458
459        match result {
460            MicroStepResult::Continue(_) => {
461                // pass
462            }
463            MicroStepResult::Complete(_, _) => {
464                panic!("Expected MicroStepResult::Continue, but got Complete");
465            }
466        }
467        assert!(*hook.get_ref().called_after_micro_step.lock().unwrap());
468    }
469
470    #[test]
471    fn test_end_micro_step_complete() {
472        let model = TestModel;
473        let hook = setup_tracker_hook();
474        let mut hook_delegate = HookDelegate::new();
475        hook_delegate.add_shared_hook(hook.clone());
476
477        let event_context = EventContext {
478            current_tick_status: TickStatus::initialize(),
479            current_micro_step_status: MicroStepStatus::initialize(),
480            hook_delegate,
481            source_handler: SourceHandler::new(),
482            event_scheduler: EventScheduler::new(),
483        };
484
485        let handler = MicroStepHandler::new(event_context);
486        let result = handler.end_micro_step(&model);
487
488        match result {
489            MicroStepResult::Complete(_, _) => {
490                // pass
491            }
492            MicroStepResult::Continue(_) => {
493                panic!("Expected MicroStepResult::Complete, but got Continue");
494            }
495        }
496        assert!(*hook.get_ref().called_after_micro_step.lock().unwrap());
497    }
498}