Skip to content

Commit 45a19a2

Browse files
committed
sim-lib: add implementation of simulated clock
1 parent a299cbe commit 45a19a2

File tree

1 file changed

+113
-1
lines changed

1 file changed

+113
-1
lines changed

simln-lib/src/clock.rs

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use async_trait::async_trait;
2+
use std::ops::{Div, Mul};
23
use std::time::{Duration, SystemTime};
3-
use tokio::time;
4+
use tokio::time::{self, Instant};
5+
6+
use crate::SimulationError;
47

58
#[async_trait]
69
pub trait Clock: Send + Sync {
@@ -22,3 +25,112 @@ impl Clock for SystemClock {
2225
time::sleep(wait).await;
2326
}
2427
}
28+
29+
/// Provides an implementation of the Clock trait that speeds up wall clock time by some factor.
30+
#[derive(Clone)]
31+
pub struct SimulationClock {
32+
// The multiplier that the regular wall clock is sped up by, must be in [1, 1000].
33+
speedup_multiplier: u16,
34+
35+
/// Tracked so that we can calculate our "fast-forwarded" present relative to the time that we started running at.
36+
/// This is useful, because it allows us to still rely on the wall clock, then just convert based on our speedup.
37+
/// This field is expressed as an Instant for convenience.
38+
start_instant: Instant,
39+
}
40+
41+
impl SimulationClock {
42+
/// Creates a new simulated clock that will speed up wall clock time by the multiplier provided, which must be in
43+
/// [1;1000] because our asynchronous sleep only supports a duration of ms granularity.
44+
pub fn new(speedup_multiplier: u16) -> Result<Self, SimulationError> {
45+
if speedup_multiplier < 1 {
46+
return Err(SimulationError::SimulatedNetworkError(
47+
"speedup_multiplier must be at least 1".to_string(),
48+
));
49+
}
50+
51+
if speedup_multiplier > 1000 {
52+
return Err(SimulationError::SimulatedNetworkError(
53+
"speedup_multiplier must be less than 1000, because the simulation sleeps with millisecond
54+
granularity".to_string(),
55+
));
56+
}
57+
58+
Ok(SimulationClock {
59+
speedup_multiplier,
60+
start_instant: Instant::now(),
61+
})
62+
}
63+
64+
/// Calculates the current simulation time based on the current wall clock time.
65+
///
66+
/// Separated for testing purposes so that we can fix the current wall clock time and elapsed interval.
67+
fn calc_now(&self, now: SystemTime, elapsed: Duration) -> SystemTime {
68+
now.checked_add(self.simulated_to_wall_clock(elapsed))
69+
.expect("simulation time overflow")
70+
}
71+
72+
/// Converts a duration expressed in wall clock time to the amount of equivalent time that should be used in our
73+
/// sped up time.
74+
fn wall_clock_to_simulated(&self, d: Duration) -> Duration {
75+
d.div(self.speedup_multiplier.into())
76+
}
77+
78+
/// Converts a duration expressed in sped up simulation time to the be expressed in wall clock time.
79+
fn simulated_to_wall_clock(&self, d: Duration) -> Duration {
80+
d.mul(self.speedup_multiplier.into())
81+
}
82+
}
83+
84+
#[async_trait]
85+
impl Clock for SimulationClock {
86+
/// To get the current time according to our simulation clock, we get the amount of wall clock time that has
87+
/// elapsed since the simulator clock was created and multiply it by our speedup.
88+
fn now(&self) -> SystemTime {
89+
self.calc_now(SystemTime::now(), self.start_instant.elapsed())
90+
}
91+
92+
/// To provide a sped up sleep time, we scale the proposed wait time by our multiplier and sleep.
93+
async fn sleep(&self, wait: Duration) {
94+
time::sleep(self.wall_clock_to_simulated(wait)).await;
95+
}
96+
}
97+
98+
#[cfg(test)]
99+
mod tests {
100+
use std::time::{Duration, SystemTime};
101+
102+
use crate::clock::SimulationClock;
103+
104+
/// Tests validation and that a multplier of 1 is a regular clock.
105+
#[test]
106+
fn test_simulation_clock() {
107+
assert!(SimulationClock::new(0).is_err());
108+
assert!(SimulationClock::new(1001).is_err());
109+
110+
let clock = SimulationClock::new(1).unwrap();
111+
let now = SystemTime::now();
112+
let elapsed = Duration::from_secs(15);
113+
114+
assert_eq!(
115+
clock.calc_now(now, elapsed),
116+
now.checked_add(elapsed).unwrap(),
117+
);
118+
}
119+
120+
/// Test that time is sped up by multiplier.
121+
#[test]
122+
fn test_clock_speedup() {
123+
let clock = SimulationClock::new(10).unwrap();
124+
let now = SystemTime::now();
125+
126+
assert_eq!(
127+
clock.calc_now(now, Duration::from_secs(1)),
128+
now.checked_add(Duration::from_secs(10)).unwrap(),
129+
);
130+
131+
assert_eq!(
132+
clock.calc_now(now, Duration::from_secs(50)),
133+
now.checked_add(Duration::from_secs(500)).unwrap(),
134+
);
135+
}
136+
}

0 commit comments

Comments
 (0)