1
1
use async_trait:: async_trait;
2
+ use std:: ops:: { Div , Mul } ;
2
3
use std:: time:: { Duration , SystemTime } ;
3
- use tokio:: time;
4
+ use tokio:: time:: { self , Instant } ;
5
+
6
+ use crate :: SimulationError ;
4
7
5
8
#[ async_trait]
6
9
pub trait Clock : Send + Sync {
@@ -22,3 +25,112 @@ impl Clock for SystemClock {
22
25
time:: sleep ( wait) . await ;
23
26
}
24
27
}
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