Skip to content

Commit c103f94

Browse files
committed
sim-lib: add implementation of simulated clock
1 parent b5a413c commit c103f94

File tree

2 files changed

+74
-1
lines changed

2 files changed

+74
-1
lines changed

simln-lib/src/clock.rs

Lines changed: 72 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 {
@@ -23,3 +26,71 @@ impl Clock for SystemClock {
2326
time::sleep(wait).await;
2427
}
2528
}
29+
30+
/// Provides an implementation of the Clock trait that speeds up wall clock time by some factor.
31+
#[derive(Clone)]
32+
pub struct SimulationClock {
33+
// The multiplier
34+
speedup_multiplier: u32,
35+
36+
/// Tracked so that we can calculate our "fast-forwarded" present relative to the time that we started running at.
37+
/// This is useful, because it allows us to still rely on the wall clock, then just convert based on our speedup.
38+
/// This field is expressed as a Duration for convenience.
39+
start_instant: Instant,
40+
}
41+
42+
impl SimulationClock {
43+
/// Creates a new simulated clock that will speed up wall clock time by the multiplier provided, which must be in
44+
/// [1;1000] because our asynchronous sleep only supports a duration of ms granularity.
45+
pub fn new(speedup_multiplier: u32) -> Result<Self, SimulationError> {
46+
if speedup_multiplier < 1 {
47+
return Err(SimulationError::SimulatedNetworkError(
48+
"speedup_multiplier must be at least 1".to_string(),
49+
));
50+
}
51+
52+
if speedup_multiplier > 1000 {
53+
return Err(SimulationError::SimulatedNetworkError(
54+
"speedup_multiplier must be less than 1000, because the simulation sleeps with millisecond
55+
granularity".to_string(),
56+
));
57+
}
58+
59+
Ok(SimulationClock {
60+
speedup_multiplier,
61+
start_instant: Instant::now(),
62+
})
63+
}
64+
65+
/// Calculates the current simulation time based on the current wall clock time. Included for testing purposes
66+
/// so that we can fix the current wall clock time.
67+
fn calc_now(&self, now: SystemTime) -> SystemTime {
68+
now.checked_add(self.simulated_to_wall_clock(self.start_instant.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)
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)
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())
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+
}

simln-lib/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,8 @@ pub enum SimulationError {
204204
PaymentGenerationError(PaymentGenerationError),
205205
#[error("Destination Generation Error: {0}")]
206206
DestinationGenerationError(DestinationGenerationError),
207+
#[error("Simulated Time Error: {0}")]
208+
SimulatedTimeError(String),
207209
}
208210

209211
#[derive(Debug, Error)]

0 commit comments

Comments
 (0)