use arc_swap::ArcSwap;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::Mutex;
#[cfg(not(target_arch = "wasm32"))]
use std::thread::JoinHandle;
use web_time::{Duration, Instant, SystemTime, UNIX_EPOCH};
pub(crate) struct Oracle {
pub(crate) inner: Arc<Inner>,
}
impl Drop for Oracle {
fn drop(&mut self) {
self.shutdown();
}
}
pub(crate) struct Inner {
pub(crate) timestamp: AtomicU64,
pub(crate) reference: ArcSwap<(u64, Instant)>,
pub(crate) resync_enabled: AtomicBool,
#[cfg(not(target_arch = "wasm32"))]
pub(crate) resync_handle: Mutex<Option<JoinHandle<()>>>,
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub(crate) resync_interval: Duration,
}
impl Oracle {
pub fn new(resync_interval: Duration) -> Arc<Self> {
let reference_unix = Self::current_unix_ns();
let reference_time = Instant::now();
let oracle = Self {
inner: Arc::new(Inner {
timestamp: AtomicU64::new(reference_unix),
reference: ArcSwap::new(Arc::new((reference_unix, reference_time))),
resync_enabled: AtomicBool::new(true),
#[cfg(not(target_arch = "wasm32"))]
resync_handle: Mutex::new(None),
resync_interval,
}),
};
#[cfg(not(target_arch = "wasm32"))]
oracle.worker_resync();
Arc::new(oracle)
}
#[cfg(test)]
#[inline]
pub fn current_timestamp(&self) -> u64 {
self.inner.timestamp.load(Ordering::Acquire)
}
#[inline]
pub(crate) fn current_unix_ns() -> u64 {
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH);
timestamp.unwrap_or_default().as_nanos() as u64
}
#[inline]
pub(crate) fn current_time_ns(&self) -> u64 {
let reference = self.inner.reference.load();
reference.0 + reference.1.elapsed().as_nanos() as u64
}
fn shutdown(&self) {
self.inner.resync_enabled.store(false, Ordering::Release);
#[cfg(not(target_arch = "wasm32"))]
if let Some(handle) = self.inner.resync_handle.lock().unwrap().take() {
handle.thread().unpark();
handle.join().unwrap();
}
}
#[cfg(not(target_arch = "wasm32"))]
fn worker_resync(&self) {
let oracle = self.inner.clone();
let interval = oracle.resync_interval;
let handle = std::thread::spawn(move || {
while oracle.resync_enabled.load(Ordering::Acquire) {
std::thread::park_timeout(interval);
let reference_unix = Self::current_unix_ns();
let reference_time = Instant::now();
oracle.reference.store(Arc::new((reference_unix, reference_time)));
}
});
*self.inner.resync_handle.lock().unwrap() = Some(handle);
}
}