Skip to main content

moonpool_sim/providers/
time.rs

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