use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicPtr};
use std::thread;
use std::thread::sleep;
use std::time::{Duration, SystemTime};
use tokio::time::Instant;
const UPDATE_CACHED_TIME_EVERY: Duration = Duration::from_millis(10);
pub trait TimeSource: std::fmt::Debug {
fn now(&self) -> Instant;
fn system_time_now(&self) -> SystemTime {
SystemTime::now()
}
}
#[derive(Clone, Copy, Debug)]
pub struct InstantTimeSrc(());
impl InstantTimeSrc {
pub fn new() -> Self {
InstantTimeSrc(())
}
}
impl TimeSource for InstantTimeSrc {
fn now(&self) -> Instant {
Instant::now()
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct CachingSystemTimeSrc(());
static GLOBAL_TIME_STATE: AtomicPtr<Instant> = AtomicPtr::new(std::ptr::null_mut());
impl CachingSystemTimeSrc {
#![allow(unused)]
pub(crate) fn new() -> Self {
let mut current_unix_epoch_ts = Instant::now();
if GLOBAL_TIME_STATE
.compare_exchange(
std::ptr::null_mut(),
(&mut current_unix_epoch_ts) as *mut _,
std::sync::atomic::Ordering::Acquire,
std::sync::atomic::Ordering::Relaxed,
)
.is_ok()
{
let drop_guard = Arc::new(AtomicBool::new(false));
let drop_guard_clone = drop_guard.clone();
thread::spawn(move || Self::update_instant(drop_guard_clone));
while !drop_guard.load(std::sync::atomic::Ordering::Acquire) {
std::hint::spin_loop();
}
}
CachingSystemTimeSrc(())
}
fn update_instant(drop_guard: Arc<AtomicBool>) {
let mut now = Instant::now();
GLOBAL_TIME_STATE.store(&mut now, std::sync::atomic::Ordering::Release);
drop_guard.store(true, std::sync::atomic::Ordering::Release);
loop {
now = Instant::now();
GLOBAL_TIME_STATE.store(&mut now, std::sync::atomic::Ordering::Release);
sleep(UPDATE_CACHED_TIME_EVERY);
}
}
}
impl TimeSource for CachingSystemTimeSrc {
fn now(&self) -> Instant {
unsafe { *GLOBAL_TIME_STATE.load(std::sync::atomic::Ordering::Acquire) }
}
}
#[cfg(test)]
fn mock_system_epoch() -> SystemTime {
SystemTime::UNIX_EPOCH + Duration::from_secs(1_000_000)
}
#[cfg(test)]
#[derive(Clone, Debug)]
pub struct MockTimeSource {
start_instant: Instant,
current_instant: Instant,
system_time_epoch: SystemTime,
}
#[cfg(test)]
impl MockTimeSource {
pub fn new(start_instant: Instant) -> Self {
MockTimeSource {
start_instant,
current_instant: start_instant,
system_time_epoch: mock_system_epoch(),
}
}
pub fn advance_time(&mut self, duration: Duration) {
self.current_instant += duration;
}
}
#[cfg(test)]
impl TimeSource for MockTimeSource {
fn now(&self) -> Instant {
self.current_instant
}
fn system_time_now(&self) -> SystemTime {
let elapsed = self.current_instant.duration_since(self.start_instant);
self.system_time_epoch + elapsed
}
}
#[cfg(test)]
#[derive(Clone, Debug)]
pub struct SharedMockTimeSource {
epoch: Instant,
elapsed: Arc<std::sync::Mutex<Duration>>,
system_time_epoch: SystemTime,
}
#[cfg(test)]
impl SharedMockTimeSource {
pub fn new() -> Self {
Self {
epoch: Instant::now(),
elapsed: Arc::new(std::sync::Mutex::new(Duration::ZERO)),
system_time_epoch: mock_system_epoch(),
}
}
#[allow(dead_code)] pub fn with_instant(start: Instant) -> Self {
Self {
epoch: start,
elapsed: Arc::new(std::sync::Mutex::new(Duration::ZERO)),
system_time_epoch: mock_system_epoch(),
}
}
pub fn advance_time(&self, duration: Duration) {
let mut guard = self.elapsed.lock().unwrap();
*guard += duration;
}
#[allow(dead_code)] pub fn current_time(&self) -> Instant {
self.epoch + *self.elapsed.lock().unwrap()
}
}
#[cfg(test)]
impl Default for SharedMockTimeSource {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
impl TimeSource for SharedMockTimeSource {
fn now(&self) -> Instant {
self.epoch + *self.elapsed.lock().unwrap()
}
fn system_time_now(&self) -> SystemTime {
self.system_time_epoch + *self.elapsed.lock().unwrap()
}
}
#[cfg(test)]
impl crate::simulation::TimeSource for SharedMockTimeSource {
fn now_nanos(&self) -> u64 {
let elapsed = *self.elapsed.lock().unwrap();
elapsed.as_nanos() as u64
}
fn sleep(
&self,
_duration: Duration,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>> {
Box::pin(async move {
})
}
fn sleep_until(
&self,
_deadline_nanos: u64,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>> {
Box::pin(async move {
})
}
fn timeout<F, T>(
&self,
_duration: Duration,
future: F,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Option<T>> + Send>>
where
F: std::future::Future<Output = T> + Send + 'static,
T: Send + 'static,
{
Box::pin(async move { Some(future.await) })
}
}
#[cfg(test)]
pub mod tests {
use super::*;
#[test]
fn test_instant_is_updated() {
let time_source = CachingSystemTimeSrc::new();
let first_instant = time_source.now();
assert!(first_instant.elapsed().as_millis() < 30);
sleep(Duration::from_millis(120));
let second_instant = time_source.now();
assert!(second_instant.elapsed().as_millis() < 30);
assert!(second_instant > first_instant);
}
}