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}