mod default;
mod instant;
use std::sync::atomic::{self, AtomicBool};
pub use instant::Instant;
pub use std::time::{Duration, SystemTime};
type U64Microsecond = u64;
static mut UPTIME_SOURCE: fn() -> U64Microsecond = default::default_uptime_source;
static mut UPTIME_PAUSE: fn(bool) = default::default_uptime_pause;
static mut SYSTEM_TIME_VALID: fn() -> bool = || true;
static mut IMPLEMENTATION_NAME: &str = default::DEFAULT_IMPL_NAME;
static IS_PAUSED: AtomicBool = AtomicBool::new(false);
static PAUSE_IMPLEMENTED: AtomicBool = AtomicBool::new(true);
static TIME_IMPL_FROZEN: atomic::AtomicBool = atomic::AtomicBool::new(false);
#[inline]
pub fn uptime() -> Duration {
TIME_IMPL_FROZEN.store(true, atomic::Ordering::Relaxed);
Duration::from_micros(unsafe { UPTIME_SOURCE() })
}
#[allow(missing_docs)]
#[derive(Debug, thiserror::Error, Clone, Copy)]
pub enum PauseError {
#[error("Pause not implemented for {} time source", unsafe { IMPLEMENTATION_NAME })]
PauseNotImplemented,
#[error("Cannot pause if already paused")]
AlreadyPaused,
#[error("Cannot un-pause if not paused")]
NotPaused,
}
#[inline]
pub fn try_pause(should_pause: bool) -> Result<(), PauseError> {
if IS_PAUSED.swap(should_pause, atomic::Ordering::Relaxed) == should_pause {
if should_pause {
Err(PauseError::AlreadyPaused)
} else {
Err(PauseError::NotPaused)
}
} else if !PAUSE_IMPLEMENTED.load(atomic::Ordering::Relaxed) {
Err(PauseError::PauseNotImplemented)
} else {
unsafe { UPTIME_PAUSE(should_pause) };
Ok(())
}
}
#[inline]
pub fn is_paused() -> bool {
IS_PAUSED.load(atomic::Ordering::Relaxed)
}
#[inline]
pub fn pause_implemented() -> bool {
PAUSE_IMPLEMENTED.load(atomic::Ordering::Relaxed)
}
#[inline]
pub fn system_time() -> Option<SystemTime> {
TIME_IMPL_FROZEN.store(true, atomic::Ordering::Relaxed);
if unsafe { SYSTEM_TIME_VALID() } {
Some(SystemTime::now())
} else {
None
}
}
#[doc(hidden)]
pub mod __private {
#[derive(Debug, Clone, Copy)]
pub struct TimeImplementation {
pub implementation_name: &'static str,
pub uptime: fn() -> super::U64Microsecond,
pub pause: Option<fn(bool) -> ()>,
pub system_time_valid: fn() -> bool,
}
pub unsafe fn set_time_implementation(time_imp: TimeImplementation) {
use std::sync::atomic::Ordering;
assert!(
!super::TIME_IMPL_FROZEN.swap(true, Ordering::SeqCst),
"Cannot set time source after it has been used or previously set(old: {}, new: {})",
super::IMPLEMENTATION_NAME,
time_imp.implementation_name
);
super::UPTIME_SOURCE = time_imp.uptime;
super::IMPLEMENTATION_NAME = time_imp.implementation_name;
super::SYSTEM_TIME_VALID = time_imp.system_time_valid;
if let Some(pause) = time_imp.pause {
super::PAUSE_IMPLEMENTED.store(true, Ordering::SeqCst);
super::UPTIME_PAUSE = pause;
} else {
super::PAUSE_IMPLEMENTED.store(false, Ordering::SeqCst);
super::UPTIME_PAUSE = |_| {};
}
}
}
#[cfg(test)]
mod test {
use std::thread;
fn test_time() {
use super::*;
let start = uptime().as_micros();
thread::sleep(Duration::from_millis(100));
let end = uptime().as_micros();
assert!(end.saturating_sub(start) >= 100_000);
}
fn test_pause() {
use super::*;
try_pause(true).expect("Pause Error");
let start = uptime().as_micros();
thread::sleep(Duration::from_millis(1000));
let end = uptime().as_micros();
assert!(end.saturating_sub(start) < 100);
try_pause(false).expect("Pause Error");
thread::sleep(Duration::from_millis(1000));
let end = uptime().as_micros();
assert!(end.saturating_sub(start) >= 1_000_000);
}
#[test]
fn test_all() {
test_time();
test_pause();
}
}