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}