Skip to main content

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}