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