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}