use async_trait::async_trait;
use std::time::Duration;
use moonpool_core::{TimeError, TimeProvider};
use crate::sim::WeakSimWorld;
#[derive(Debug, Clone)]
pub struct SimTimeProvider {
sim: WeakSimWorld,
}
impl SimTimeProvider {
pub fn new(sim: WeakSimWorld) -> Self {
Self { sim }
}
}
#[async_trait(?Send)]
impl TimeProvider for SimTimeProvider {
async fn sleep(&self, duration: Duration) -> Result<(), TimeError> {
let sleep_future = self.sim.sleep(duration).map_err(|_| TimeError::Shutdown)?;
let _ = sleep_future.await;
Ok(())
}
fn now(&self) -> Duration {
self.sim.now().unwrap_or(Duration::ZERO)
}
fn timer(&self) -> Duration {
self.sim.timer().unwrap_or(Duration::ZERO)
}
async fn timeout<F, T>(&self, duration: Duration, future: F) -> Result<T, TimeError>
where
F: std::future::Future<Output = T>,
{
let sleep_future = self.sim.sleep(duration).map_err(|_| TimeError::Shutdown)?;
tokio::select! {
result = future => Ok(result),
_ = sleep_future => Err(TimeError::Elapsed),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sim::SimWorld;
#[tokio::test]
async fn test_sim_time_provider_basic() {
let sim = SimWorld::new();
let time_provider = SimTimeProvider::new(sim.downgrade());
let now = time_provider.now();
assert_eq!(now, Duration::ZERO);
let timer = time_provider.timer();
assert!(timer >= now);
let result = time_provider
.timeout(Duration::from_millis(100), async { 42 })
.await;
assert_eq!(result, Ok(42));
}
#[test]
fn test_sim_time_provider_with_simulation() {
let sim = SimWorld::new();
let time_provider = SimTimeProvider::new(sim.downgrade());
let now = time_provider.now();
assert_eq!(now, Duration::ZERO);
let timer = time_provider.timer();
assert!(timer >= now);
assert!(timer <= now + Duration::from_millis(100));
}
#[test]
fn test_clock_drift_bounds() {
let sim = SimWorld::new();
let time_provider = SimTimeProvider::new(sim.downgrade());
for _ in 0..100 {
let now = time_provider.now();
let timer = time_provider.timer();
assert!(timer >= now, "timer ({:?}) < now ({:?})", timer, now);
assert!(
timer <= now + Duration::from_millis(100),
"timer ({:?}) > now + 100ms ({:?})",
timer,
now + Duration::from_millis(100)
);
}
}
}