xdevs/simulator.rs
1use crate::traits::{AbstractSimulator, AsyncInput, Bag};
2use core::time::Duration;
3
4#[cfg(feature = "std")]
5pub mod std;
6
7#[cfg(feature = "embassy")]
8pub mod embassy;
9
10/// Configuration for the DEVS simulator.
11#[derive(Debug, Clone, Copy)]
12pub struct Config {
13 /// The start time of the simulation.
14 pub t_start: f64,
15
16 /// The stop time of the simulation.
17 pub t_stop: f64,
18
19 /// The time scale factor for the simulation.
20 ///
21 /// If `time_scale` is greater than 1.0, the simulation runs faster than real time.
22 /// If `time_scale` is less than 1.0, the simulation runs slower than real time.
23 pub time_scale: f64,
24
25 /// The maximum jitter duration allowed in the simulation.
26 ///
27 /// If `None`, jitter is not checked. If `Some(duration)`, the simulator will panic
28 /// if the wall-clock time drift exceeds this duration.
29 pub max_jitter: Option<Duration>,
30}
31
32impl Config {
33 /// Creates a new `SimulatorConfig` with the specified parameters.
34 #[inline]
35 pub fn new(t_start: f64, t_stop: f64, time_scale: f64, max_jitter: Option<Duration>) -> Self {
36 Self {
37 t_start,
38 t_stop,
39 time_scale,
40 max_jitter,
41 }
42 }
43}
44
45impl Default for Config {
46 /// Default configuration runs from time 0.0 to infinity, with a
47 /// time scale of 1.0 (real-time simulation) and no maximum jitter.
48 #[inline]
49 fn default() -> Self {
50 Self::new(0.0, f64::INFINITY, 1.0, None)
51 }
52}
53
54/// A DEVS simulator.
55#[repr(transparent)]
56pub struct Simulator<M: AbstractSimulator> {
57 model: M,
58}
59
60impl<M: AbstractSimulator> Simulator<M> {
61 /// Creates a new `Simulator` with the given DEVS model.
62 #[inline]
63 pub const fn new(model: M) -> Self {
64 Self { model }
65 }
66
67 /// It executes the simulation of the inner DEVS model from `t_start` to `t_stop`.
68 /// It provides support for real time execution via the following arguments:
69 ///
70 /// - `wait_until`: a closure that is called between state transitions.
71 /// It receives the current time, the time of the next state transition and a
72 /// mutable reference to the input ports. It returns the actual time "waited".
73 /// If the returned time is equal to the input time, an internal/confluent state transition is performed.
74 /// Otherwise, it assumes that an external event happened and executes the external transition function.
75 ///
76 /// - `propagate_output`: a closure that is called after output functions.
77 /// It receives a mutable reference to the output ports so the closure can access to output events.
78 #[inline]
79 pub fn simulate_rt(
80 &mut self,
81 config: &Config,
82 mut wait_until: impl FnMut(f64, f64, &mut M::Input) -> f64,
83 mut propagate_output: impl FnMut(&M::Output),
84 ) {
85 let t_start = config.t_start;
86 let t_stop = config.t_stop;
87 let mut t = t_start;
88 let mut t_next_internal = self.model.start(t);
89 while t < t_stop {
90 let t_until = f64::min(t_next_internal, t_stop);
91 t = wait_until(t, t_until, self.model.get_input_mut());
92 if t >= t_next_internal {
93 self.model.lambda(t);
94 propagate_output(self.model.get_output());
95 } else if self.model.get_input().is_empty() {
96 continue; // avoid spurious external transitions
97 }
98 t_next_internal = self.model.delta(t);
99 }
100 self.model.stop(t_stop);
101 }
102
103 /// It executes the simulation of the inner DEVS model from `t_start` to `t_stop`.
104 /// It uses a virtual clock (i.e., no real time is used).
105 #[inline]
106 pub fn simulate_vt(&mut self, config: &Config) {
107 self.simulate_rt(config, |_, t_until, _| t_until, |_| {});
108 }
109
110 /// Asynchronous version of the `simulate_rt` method.
111 ///
112 /// The main difference is that the `wait_until` function has been replaced with an
113 /// [`AsyncInput`] trait, which allows for asynchronous handling of input events.
114 pub async fn simulate_rt_async(
115 &mut self,
116 config: &Config,
117 mut input_handler: impl AsyncInput<Input = M::Input>,
118 mut propagate_output: impl FnMut(&M::Output),
119 ) {
120 let mut t = config.t_start;
121 let mut t_next_internal = self.model.start(t);
122 while t < config.t_stop {
123 let t_until = f64::min(t_next_internal, config.t_stop);
124 t = input_handler
125 .handle(config, t, t_until, self.model.get_input_mut())
126 .await;
127 if t >= t_next_internal {
128 self.model.lambda(t);
129 propagate_output(self.model.get_output());
130 } else if self.model.get_input().is_empty() {
131 continue; // avoid spurious external transitions
132 }
133 t_next_internal = self.model.delta(t);
134 }
135 self.model.stop(config.t_stop);
136 }
137}