Skip to main content

moonpool_sim/providers/
time.rs

1//! Simulation time provider implementation.
2
3use std::time::Duration;
4
5use moonpool_core::{TimeError, TimeProvider};
6
7use crate::sim::WeakSimWorld;
8
9/// Simulation time provider that integrates with `SimWorld`.
10#[derive(Debug, Clone)]
11pub struct SimTimeProvider {
12    sim: WeakSimWorld,
13}
14
15impl SimTimeProvider {
16    /// Create a new simulation time provider.
17    #[must_use]
18    pub fn new(sim: WeakSimWorld) -> Self {
19        Self { sim }
20    }
21}
22
23impl TimeProvider for SimTimeProvider {
24    async fn sleep(&self, duration: Duration) -> Result<(), TimeError> {
25        let sleep_future = self.sim.sleep(duration).map_err(|_| TimeError::Shutdown)?;
26        let _ = sleep_future.await;
27        Ok(())
28    }
29
30    fn now(&self) -> Duration {
31        // Return exact simulation time (equivalent to FDB's now())
32        self.sim.now().unwrap_or(Duration::ZERO)
33    }
34
35    fn timer(&self) -> Duration {
36        // Return drifted timer time (equivalent to FDB's timer())
37        // Can be up to clock_drift_max ahead of now()
38        self.sim.timer().unwrap_or(Duration::ZERO)
39    }
40
41    async fn timeout<F, T>(&self, duration: Duration, future: F) -> Result<T, TimeError>
42    where
43        F: std::future::Future<Output = T> + Send,
44        T: Send,
45    {
46        let sleep_future = self.sim.sleep(duration).map_err(|_| TimeError::Shutdown)?;
47
48        // Race the future against the timeout. Both become ready via distinct
49        // simulation events, so the event queue's deterministic ordering (not
50        // branch order) decides the winner; `biased;` makes that explicit and
51        // keeps the select free of any offset draw.
52        moonpool_core::select! {
53            biased;
54            result = future => Ok(result),
55            _ = sleep_future => Err(TimeError::Elapsed),
56        }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use crate::sim::SimWorld;
64
65    #[tokio::test]
66    async fn test_sim_time_provider_basic() {
67        let sim = SimWorld::new();
68        let time_provider = SimTimeProvider::new(sim.downgrade());
69
70        // Test now - should return simulation time (starts at zero)
71        let now = time_provider.now();
72        assert_eq!(now, Duration::ZERO);
73
74        // Test timer - should be >= now
75        let timer = time_provider.timer();
76        assert!(timer >= now);
77
78        // Test timeout - quick completion (no simulation advancement needed)
79        let result = time_provider
80            .timeout(Duration::from_millis(100), async { 42 })
81            .await;
82        assert_eq!(result, Ok(42));
83    }
84
85    #[test]
86    fn test_sim_time_provider_with_simulation() {
87        let sim = SimWorld::new();
88        let time_provider = SimTimeProvider::new(sim.downgrade());
89
90        // Test now - should return simulation time (starts at zero)
91        let now = time_provider.now();
92        assert_eq!(now, Duration::ZERO);
93
94        // Test timer - should be >= now but <= now + clock_drift_max
95        let timer = time_provider.timer();
96        assert!(timer >= now);
97        // Default clock_drift_max is 100ms
98        assert!(timer <= now + Duration::from_millis(100));
99    }
100
101    #[test]
102    fn test_clock_drift_bounds() {
103        let sim = SimWorld::new();
104        let time_provider = SimTimeProvider::new(sim.downgrade());
105
106        // Call timer multiple times to exercise drift logic
107        for _ in 0..100 {
108            let now = time_provider.now();
109            let timer = time_provider.timer();
110
111            // timer should always be >= now
112            assert!(timer >= now, "timer ({timer:?}) < now ({now:?})");
113
114            // timer should not exceed now + clock_drift_max (100ms default)
115            assert!(
116                timer <= now + Duration::from_millis(100),
117                "timer ({:?}) > now + 100ms ({:?})",
118                timer,
119                now + Duration::from_millis(100)
120            );
121        }
122    }
123}