Skip to main content

ixa/triggers/
time.rs

1use super::TriggerCriterion;
2use crate::{Context, ExecutionPhase};
3
4/// Trigger criterion for a specific simulation time.
5///
6/// [`TimeTrigger`] observes the simulation clock and emits when the simulation reaches a configured
7/// time and execution phase.
8///
9/// ## Construction
10///
11/// ```rust,ignore
12/// TimeTrigger::at(at)
13/// TimeTrigger::at_phase(at, phase)
14/// TimeTrigger::at(at).with_phase(phase)
15/// ```
16///
17/// ## Observation
18///
19/// The observation data passed to
20/// [`TriggerCriterion::emit_with`](super::TriggerCriterion::emit_with) is [`TimeTriggerEvent`]. It
21/// contains the simulation time observed when the scheduled plan runs and the phase used to schedule
22/// it:
23///
24/// ```rust,ignore
25/// pub struct TimeTriggerEvent {
26///     pub time: f64,
27///     pub phase: ExecutionPhase,
28/// }
29/// ```
30///
31/// ## Semantics
32///
33/// This trigger is equivalent to scheduling a plan that emits an event with
34/// [`context.add_plan`](crate::Context::add_plan) /
35/// [`context.add_plan_with_phase`](crate::Context::add_plan_with_phase).
36///
37/// [`TimeTrigger::at`] uses [`ExecutionPhase::Normal`](crate::ExecutionPhase::Normal).
38/// Constructor time inputs are converted to `f64` and validated when the trigger is installed.
39/// Since time is monotonic, this criterion does not use [`Direction`](super::Direction) or
40/// [`TriggerMode`](super::TriggerMode). It emits once, when its scheduled plan executes. If several
41/// plans are scheduled for the same time, the selected [`ExecutionPhase`](crate::ExecutionPhase)
42/// controls phase ordering.
43///
44/// ## Example
45///
46/// ```rust
47/// use ixa::{Context, ExecutionPhase, IxaEvent};
48/// use ixa::triggers::{ContextTriggersExt, TimeTrigger, TriggerCriterion};
49///
50/// #[derive(IxaEvent)]
51/// struct StopTimeReached {
52///     time: f64,
53///     phase: ExecutionPhase,
54/// }
55///
56/// let mut context = Context::new();
57///
58/// context.register_trigger(
59///     TimeTrigger::at_phase(50.0, ExecutionPhase::Last)
60///         .emit_with(|observation| StopTimeReached {
61///             time: observation.time,
62///             phase: observation.phase,
63///         }),
64/// );
65///
66/// context.subscribe_to_event(|context, _event: StopTimeReached| {
67///     context.shutdown();
68/// });
69/// ```
70pub struct TimeTrigger {
71    at: f64,
72    phase: ExecutionPhase,
73}
74
75#[derive(Clone, Copy, Debug)]
76pub struct TimeTriggerEvent {
77    pub time: f64,
78    pub phase: ExecutionPhase,
79}
80
81impl TimeTrigger {
82    #[must_use]
83    pub fn at(at: impl Into<f64>) -> Self {
84        Self {
85            at: at.into(),
86            phase: ExecutionPhase::Normal,
87        }
88    }
89
90    #[must_use]
91    pub fn at_phase(at: impl Into<f64>, phase: ExecutionPhase) -> Self {
92        Self {
93            at: at.into(),
94            phase,
95        }
96    }
97
98    #[must_use]
99    pub fn with_phase(mut self, phase: ExecutionPhase) -> Self {
100        self.phase = phase;
101        self
102    }
103}
104
105impl TriggerCriterion for TimeTrigger {
106    type Observation = TimeTriggerEvent;
107
108    fn install<F>(self, context: &mut Context, on_match: F)
109    where
110        F: Fn(&mut Context, Self::Observation) + 'static,
111    {
112        let phase = self.phase;
113        context.add_plan_with_phase(
114            self.at,
115            move |context| {
116                let event = TimeTriggerEvent {
117                    time: context.get_current_time(),
118                    phase,
119                };
120                on_match(context, event);
121            },
122            phase,
123        );
124    }
125}