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 {
@@ -23,3 +26,71 @@ impl Clock for SystemClock {
23
26
time:: sleep ( wait) . await ;
24
27
}
25
28
}
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
+ }
0 commit comments