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}