wasm_bindgen_test/rt/web_time/
instant.rs

1//! Re-implementation of [`std::time::Instant`].
2//!
3//! See <https://github.com/rust-lang/rust/blob/1.83.0/library/std/src/time.rs#L271-L468>.
4
5use core::ops::Sub;
6use core::time::Duration;
7
8use super::js::PERFORMANCE;
9#[cfg(target_feature = "atomics")]
10use super::js::TIME_ORIGIN;
11
12/// See [`std::time::Instant`].
13#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
14pub struct Instant(Duration);
15
16impl Instant {
17    /// See [`std::time::Instant::now()`].
18    ///
19    /// # Panics
20    ///
21    /// This call will panic if the [`Performance` object] was not found, e.g.
22    /// calling from a [worklet].
23    ///
24    /// [`Performance` object]: https://developer.mozilla.org/en-US/docs/Web/API/performance_property
25    /// [worklet]: https://developer.mozilla.org/en-US/docs/Web/API/Worklet
26    #[must_use]
27    pub fn now() -> Self {
28        let now = PERFORMANCE.with(|performance| {
29            let performance = performance
30                .as_ref()
31                .expect("`Performance` object not found");
32
33            #[cfg(not(target_feature = "atomics"))]
34            return performance.now();
35            #[cfg(target_feature = "atomics")]
36            TIME_ORIGIN.with(|origin| performance.now() + origin)
37        });
38
39        assert!(
40            now.is_sign_positive(),
41            "negative `DOMHighResTimeStamp`s are not supported"
42        );
43        Self(time_stamp_to_duration(now))
44    }
45
46    /// See [`std::time::Instant::duration_since()`].
47    #[must_use]
48    pub fn duration_since(&self, earlier: Self) -> Duration {
49        self.checked_duration_since(earlier).unwrap_or_default()
50    }
51
52    /// See [`std::time::Instant::checked_duration_since()`].
53    #[must_use]
54    pub fn checked_duration_since(&self, earlier: Self) -> Option<Duration> {
55        self.0.checked_sub(earlier.0)
56    }
57
58    /// See [`std::time::Instant::elapsed()`].
59    #[must_use]
60    pub fn elapsed(&self) -> Duration {
61        Self::now() - *self
62    }
63}
64
65impl Sub<Self> for Instant {
66    type Output = Duration;
67
68    /// Returns the amount of time elapsed from another instant to this one,
69    /// or zero duration if that instant is later than this one.
70    fn sub(self, rhs: Self) -> Duration {
71        self.duration_since(rhs)
72    }
73}
74
75/// Converts a `DOMHighResTimeStamp` to a [`Duration`].
76///
77/// # Note
78///
79/// Keep in mind that like [`Duration::from_secs_f64()`] this doesn't do perfect
80/// rounding.
81fn time_stamp_to_duration(time_stamp: f64) -> Duration {
82    let time_stamp = F64(time_stamp);
83
84    Duration::from_millis(time_stamp.trunc() as u64)
85        + Duration::from_nanos(F64(time_stamp.fract() * 1.0e6).internal_round_ties_even() as u64)
86}
87
88/// [`f64`] `no_std` compatibility wrapper.
89#[derive(Clone, Copy)]
90struct F64(f64);
91
92impl F64 {
93    /// See [`f64::trunc()`].
94    fn trunc(self) -> f64 {
95        libm::trunc(self.0)
96    }
97
98    /// See [`f64::fract()`].
99    fn fract(self) -> f64 {
100        self.0 - self.trunc()
101    }
102
103    /// A specialized version of [`f64::round_ties_even()`]. [`f64`] must be
104    /// positive and have an exponent smaller than `52`.
105    ///
106    /// - We expect `DOMHighResTimeStamp` to always be positive. We check that
107    ///   in [`Instant::now()`].
108    /// - We only round the fractional part after multiplying it by `1e6`. A
109    ///   fraction always has a negative exponent. `1e6` has an exponent of
110    ///   `19`. Therefor the resulting exponent can at most be `19`.
111    ///
112    /// [`f64::round_ties_even()`]: https://doc.rust-lang.org/1.83.0/std/primitive.f64.html#method.round_ties_even
113    fn internal_round_ties_even(self) -> f64 {
114        /// Put `debug_assert!` in a function to clap `coverage(off)` on it.
115        ///
116        /// See <https://github.com/rust-lang/rust/issues/80549>.
117        fn check(this: f64) {
118            debug_assert!(this.is_sign_positive(), "found negative input");
119            debug_assert!(
120                {
121                    let exponent: u64 = this.to_bits() >> 52 & 0x7ff;
122                    exponent < 0x3ff + 52
123                },
124                "found number with exponent bigger than 51"
125            );
126        }
127
128        check(self.0);
129
130        // See <https://github.com/rust-lang/libm/blob/libm-v0.2.11/src/math/rint.rs>.
131
132        let one_over_e = 1.0 / f64::EPSILON;
133        // REMOVED: We don't support numbers with exponents bigger than 51.
134        // REMOVED: We don't support negative numbers.
135        // REMOVED: We don't support numbers with exponents bigger than 51.
136        let xplusoneovere = self.0 + one_over_e;
137        xplusoneovere - one_over_e
138        // REMOVED: We don't support negative numbers.
139    }
140}