asynchronix/time/
clock.rs

1use std::time::{Duration, Instant, SystemTime};
2
3use crate::time::MonotonicTime;
4
5/// A type that can be used to synchronize a simulation.
6///
7/// This trait abstract over the different types of clocks, such as
8/// as-fast-as-possible and real-time clocks.
9///
10/// A clock can be associated to a simulation at initialization time by calling
11/// [`SimInit::init_with_clock()`](crate::simulation::SimInit::init_with_clock).
12pub trait Clock: Send {
13    /// Blocks until the deadline.
14    fn synchronize(&mut self, deadline: MonotonicTime) -> SyncStatus;
15}
16
17/// The current synchronization status of a clock.
18#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
19pub enum SyncStatus {
20    /// The clock is synchronized.
21    Synchronized,
22    /// The clock is lagging behind by the specified offset.
23    OutOfSync(Duration),
24}
25
26/// A dummy [`Clock`] that ignores synchronization.
27///
28/// Choosing this clock effectively makes the simulation run as fast as
29/// possible.
30#[derive(Copy, Clone, Debug, Default)]
31pub struct NoClock {}
32
33impl NoClock {
34    /// Constructs a new `NoClock` object.
35    pub fn new() -> Self {
36        Self {}
37    }
38}
39
40impl Clock for NoClock {
41    /// Returns immediately with status `SyncStatus::Synchronized`.
42    fn synchronize(&mut self, _: MonotonicTime) -> SyncStatus {
43        SyncStatus::Synchronized
44    }
45}
46
47/// A real-time [`Clock`] based on the system's monotonic clock.
48///
49/// This clock accepts an arbitrary reference time and remains synchronized with
50/// the system's monotonic clock.
51#[derive(Copy, Clone, Debug)]
52pub struct SystemClock {
53    wall_clock_ref: Instant,
54    simulation_ref: MonotonicTime,
55}
56
57impl SystemClock {
58    /// Constructs a `SystemClock` with an offset between simulation clock and
59    /// wall clock specified by a simulation time matched to an [`Instant`]
60    /// timestamp.
61    ///
62    /// The provided reference time may lie in the past or in the future.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use std::time::{Duration, Instant};
68    ///
69    /// use asynchronix::simulation::SimInit;
70    /// use asynchronix::time::{MonotonicTime, SystemClock};
71    ///
72    /// let t0 = MonotonicTime::new(1_234_567_890, 0);
73    ///
74    /// // Make the simulation start in 1s.
75    /// let clock = SystemClock::from_instant(t0, Instant::now() + Duration::from_secs(1));
76    ///
77    /// let simu = SimInit::new()
78    /// //  .add_model(...)
79    /// //  .add_model(...)
80    ///     .init_with_clock(t0, clock);
81    /// ```
82    pub fn from_instant(simulation_ref: MonotonicTime, wall_clock_ref: Instant) -> Self {
83        Self {
84            wall_clock_ref,
85            simulation_ref,
86        }
87    }
88
89    /// Constructs a `SystemClock` with an offset between simulation clock and
90    /// wall clock specified by a simulation time matched to a [`SystemTime`]
91    /// timestamp.
92    ///
93    /// The provided reference time may lie in the past or in the future.
94    ///
95    /// Note that, even though the wall clock reference is specified with the
96    /// (non-monotonic) system clock, the [`synchronize()`](Clock::synchronize)
97    /// method will still use the system's _monotonic_ clock. This constructor
98    /// makes a best-effort attempt at synchronizing the monotonic clock with
99    /// the non-monotonic system clock _at construction time_, but this
100    /// synchronization will be lost if the system clock is subsequently
101    /// modified through administrative changes, introduction of leap second or
102    /// otherwise.
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// use std::time::{Duration, UNIX_EPOCH};
108    ///
109    /// use asynchronix::simulation::SimInit;
110    /// use asynchronix::time::{MonotonicTime, SystemClock};
111    ///
112    /// let t0 = MonotonicTime::new(1_234_567_890, 0);
113    ///
114    /// // Make the simulation start at the next full second boundary.
115    /// let now_secs = UNIX_EPOCH.elapsed().unwrap().as_secs();
116    /// let start_time = UNIX_EPOCH + Duration::from_secs(now_secs + 1);
117    ///
118    /// let clock = SystemClock::from_system_time(t0, start_time);
119    ///
120    /// let simu = SimInit::new()
121    /// //  .add_model(...)
122    /// //  .add_model(...)
123    ///     .init_with_clock(t0, clock);
124    /// ```
125    pub fn from_system_time(simulation_ref: MonotonicTime, wall_clock_ref: SystemTime) -> Self {
126        // Select the best-correlated `Instant`/`SystemTime` pair from several
127        // samples to improve robustness towards possible thread suspension
128        // between the calls to `SystemTime::now()` and `Instant::now()`.
129        const SAMPLES: usize = 3;
130
131        let mut last_instant = Instant::now();
132        let mut min_delta = Duration::MAX;
133        let mut ref_time = None;
134
135        // Select the best-correlated instant/date pair.
136        for _ in 0..SAMPLES {
137            // The inner loop is to work around monotonic clock platform bugs
138            // that may cause `checked_duration_since` to fail.
139            let (date, instant, delta) = loop {
140                let date = SystemTime::now();
141                let instant = Instant::now();
142                let delta = instant.checked_duration_since(last_instant);
143                last_instant = instant;
144
145                if let Some(delta) = delta {
146                    break (date, instant, delta);
147                }
148            };
149
150            // Store the current instant/date if the time elapsed since the last
151            // measurement is shorter than the previous candidate.
152            if min_delta > delta {
153                min_delta = delta;
154                ref_time = Some((instant, date));
155            }
156        }
157
158        // Set the selected instant/date as the wall clock reference and adjust
159        // the simulation reference accordingly.
160        let (instant_ref, date_ref) = ref_time.unwrap();
161        let simulation_ref = if date_ref > wall_clock_ref {
162            let correction = date_ref.duration_since(wall_clock_ref).unwrap();
163
164            simulation_ref + correction
165        } else {
166            let correction = wall_clock_ref.duration_since(date_ref).unwrap();
167
168            simulation_ref - correction
169        };
170
171        Self {
172            wall_clock_ref: instant_ref,
173            simulation_ref,
174        }
175    }
176}
177
178impl Clock for SystemClock {
179    /// Blocks until the system time corresponds to the specified simulation
180    /// time.
181    fn synchronize(&mut self, deadline: MonotonicTime) -> SyncStatus {
182        let target_time = if deadline >= self.simulation_ref {
183            self.wall_clock_ref + deadline.duration_since(self.simulation_ref)
184        } else {
185            self.wall_clock_ref - self.simulation_ref.duration_since(deadline)
186        };
187
188        let now = Instant::now();
189
190        match target_time.checked_duration_since(now) {
191            Some(sleep_duration) => {
192                spin_sleep::sleep(sleep_duration);
193
194                SyncStatus::Synchronized
195            }
196            None => SyncStatus::OutOfSync(now.duration_since(target_time)),
197        }
198    }
199}
200
201/// An automatically initialized real-time [`Clock`] based on the system's
202/// monotonic clock.
203///
204/// This clock is similar to [`SystemClock`] except that the first call to
205/// [`synchronize()`](Clock::synchronize) never blocks and implicitly defines
206/// the reference time. In other words, the clock starts running on its first
207/// invocation.
208#[derive(Copy, Clone, Debug, Default)]
209pub struct AutoSystemClock {
210    inner: Option<SystemClock>,
211}
212
213impl AutoSystemClock {
214    /// Constructs a new `AutoSystemClock`.
215    pub fn new() -> Self {
216        Self::default()
217    }
218}
219
220impl Clock for AutoSystemClock {
221    /// Initializes the time reference and returns immediately on the first
222    /// call, otherwise blocks until the system time corresponds to the
223    /// specified simulation time.
224    fn synchronize(&mut self, deadline: MonotonicTime) -> SyncStatus {
225        match &mut self.inner {
226            None => {
227                let now = Instant::now();
228                self.inner = Some(SystemClock::from_instant(deadline, now));
229
230                SyncStatus::Synchronized
231            }
232            Some(clock) => clock.synchronize(deadline),
233        }
234    }
235}