#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
pub use std::time::Instant;
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
pub use host::Instant;
#[derive(Clone, Copy)]
pub struct HostClock {
pub steady_ms: fn() -> f64,
pub wall_ms: fn() -> f64,
}
pub fn set_host_clock(clock: HostClock) {
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
host::store(clock);
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
let _ = clock;
}
pub fn host_wall_ms() -> Option<f64> {
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
return host::injected().map(|clock| (clock.wall_ms)());
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
None
}
#[cfg(any(test, all(target_family = "wasm", target_os = "unknown")))]
mod host {
use std::ops::{Add, AddAssign, Sub, SubAssign};
use std::sync::{PoisonError, RwLock};
use std::time::Duration;
use super::HostClock;
static CLOCK: RwLock<Option<HostClock>> = RwLock::new(None);
#[cfg_attr(
all(test, not(all(target_family = "wasm", target_os = "unknown"))),
expect(
dead_code,
reason = "原生平台的 set_host_clock 是空操作,测试也不注入全局时钟,避免并行测试互相干扰"
)
)]
pub(super) fn store(clock: HostClock) {
*CLOCK.write().unwrap_or_else(PoisonError::into_inner) = Some(clock);
}
pub(super) fn injected() -> Option<HostClock> {
*CLOCK.read().unwrap_or_else(PoisonError::into_inner)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Instant(Duration);
impl Instant {
pub fn now() -> Self {
Self::read(injected())
}
pub(super) fn read(clock: Option<HostClock>) -> Self {
Self::from_ms(clock.map_or(0.0, |clock| (clock.steady_ms)()))
}
pub(super) fn from_ms(millis: f64) -> Self {
if !millis.is_finite() || millis <= 0.0 {
return Instant(Duration::ZERO);
}
Instant(Duration::try_from_secs_f64(millis / 1000.0).unwrap_or(Duration::MAX))
}
pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
self.0.saturating_sub(earlier.0)
}
pub fn duration_since(&self, earlier: Instant) -> Duration {
self.saturating_duration_since(earlier)
}
pub fn elapsed(&self) -> Duration {
Instant::now().saturating_duration_since(*self)
}
pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
self.0.checked_add(duration).map(Instant)
}
pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
self.0.checked_sub(duration).map(Instant)
}
}
impl Add<Duration> for Instant {
type Output = Instant;
fn add(self, other: Duration) -> Instant {
Instant(self.0.saturating_add(other))
}
}
impl AddAssign<Duration> for Instant {
fn add_assign(&mut self, other: Duration) {
*self = *self + other;
}
}
impl Sub<Duration> for Instant {
type Output = Instant;
fn sub(self, other: Duration) -> Instant {
Instant(self.0.saturating_sub(other))
}
}
impl SubAssign<Duration> for Instant {
fn sub_assign(&mut self, other: Duration) {
*self = *self - other;
}
}
impl Sub<Instant> for Instant {
type Output = Duration;
fn sub(self, other: Instant) -> Duration {
self.saturating_duration_since(other)
}
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::host::Instant as HostInstant;
use super::HostClock;
#[test]
fn native_instant_is_std() {
let now: std::time::Instant = super::Instant::now();
assert!(now.elapsed() < Duration::from_secs(60));
}
#[test]
fn native_has_no_host_wall_clock() {
super::set_host_clock(HostClock {
steady_ms: || 5.0,
wall_ms: || 7.0,
});
assert_eq!(super::host_wall_ms(), None);
}
#[test]
fn uninjected_clock_reads_zero() {
let zero = HostInstant::read(None);
assert_eq!(zero, HostInstant::from_ms(0.0));
assert_eq!(
zero.saturating_duration_since(HostInstant::from_ms(0.0)),
Duration::ZERO
);
}
#[test]
fn host_instant_before_injection_is_the_origin() {
let now = HostInstant::now();
assert_eq!(now, HostInstant::from_ms(0.0));
assert_eq!(now.elapsed(), Duration::ZERO);
}
#[test]
fn injected_clock_is_read() {
let clock = HostClock {
steady_ms: || 1500.0,
wall_ms: || 0.0,
};
assert_eq!(
HostInstant::read(Some(clock)).duration_since(HostInstant::from_ms(0.0)),
Duration::from_millis(1500)
);
}
#[test]
fn invalid_readings_clamp_to_zero() {
let zero = HostInstant::from_ms(0.0);
assert_eq!(HostInstant::from_ms(-3.0), zero);
assert_eq!(HostInstant::from_ms(f64::NAN), zero);
assert_eq!(HostInstant::from_ms(f64::INFINITY), zero);
}
#[test]
fn arithmetic_matches_std_semantics() {
let early = HostInstant::from_ms(1000.0);
let late = HostInstant::from_ms(3500.0);
assert_eq!(late - early, Duration::from_millis(2500));
assert_eq!(early - late, Duration::ZERO);
assert_eq!(late.duration_since(early), Duration::from_millis(2500));
assert_eq!(early.duration_since(late), Duration::ZERO);
assert_eq!(early + Duration::from_millis(2500), late);
assert_eq!(late - Duration::from_millis(2500), early);
assert_eq!(
early.checked_sub(Duration::from_secs(2)),
None,
"before the clock origin"
);
assert_eq!(early.checked_add(Duration::from_millis(2500)), Some(late));
assert_eq!(
HostInstant::from_ms(f64::MAX).checked_add(Duration::MAX),
None
);
assert_eq!(
HostInstant::from_ms(f64::MAX) + Duration::MAX,
HostInstant::from_ms(f64::MAX)
);
let mut moving = early;
moving += Duration::from_millis(2500);
assert_eq!(moving, late);
moving -= Duration::from_millis(2500);
assert_eq!(moving, early);
assert!(early < late);
}
#[test]
fn subtracting_past_origin_stops_at_origin() {
assert_eq!(
HostInstant::from_ms(1.0) - Duration::from_secs(1),
HostInstant::from_ms(0.0)
);
}
}