1#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
4pub use std::time::Instant;
5
6#[cfg(all(target_family = "wasm", target_os = "unknown"))]
7pub use host::Instant;
8
9#[derive(Clone, Copy)]
11pub struct HostClock {
12 pub steady_ms: fn() -> f64,
14 pub wall_ms: fn() -> f64,
16}
17
18pub fn set_host_clock(clock: HostClock) {
20 #[cfg(all(target_family = "wasm", target_os = "unknown"))]
21 host::store(clock);
22 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
23 let _ = clock;
24}
25
26pub fn host_wall_ms() -> Option<f64> {
28 #[cfg(all(target_family = "wasm", target_os = "unknown"))]
29 return host::injected().map(|clock| (clock.wall_ms)());
30 #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
31 None
32}
33
34#[cfg(any(test, all(target_family = "wasm", target_os = "unknown")))]
36mod host {
37 use std::ops::{Add, AddAssign, Sub, SubAssign};
38 use std::sync::{PoisonError, RwLock};
39 use std::time::Duration;
40
41 use super::HostClock;
42
43 static CLOCK: RwLock<Option<HostClock>> = RwLock::new(None);
44
45 #[cfg_attr(
46 all(test, not(all(target_family = "wasm", target_os = "unknown"))),
47 expect(
48 dead_code,
49 reason = "原生平台的 set_host_clock 是空操作,测试也不注入全局时钟,避免并行测试互相干扰"
50 )
51 )]
52 pub(super) fn store(clock: HostClock) {
53 *CLOCK.write().unwrap_or_else(PoisonError::into_inner) = Some(clock);
54 }
55
56 pub(super) fn injected() -> Option<HostClock> {
57 *CLOCK.read().unwrap_or_else(PoisonError::into_inner)
58 }
59
60 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
62 pub struct Instant(Duration);
63
64 impl Instant {
65 pub fn now() -> Self {
66 Self::read(injected())
67 }
68
69 pub(super) fn read(clock: Option<HostClock>) -> Self {
71 Self::from_ms(clock.map_or(0.0, |clock| (clock.steady_ms)()))
72 }
73
74 pub(super) fn from_ms(millis: f64) -> Self {
76 if !millis.is_finite() || millis <= 0.0 {
77 return Instant(Duration::ZERO);
78 }
79 Instant(Duration::try_from_secs_f64(millis / 1000.0).unwrap_or(Duration::MAX))
80 }
81
82 pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
83 self.0.saturating_sub(earlier.0)
84 }
85
86 pub fn duration_since(&self, earlier: Instant) -> Duration {
88 self.saturating_duration_since(earlier)
89 }
90
91 pub fn elapsed(&self) -> Duration {
92 Instant::now().saturating_duration_since(*self)
93 }
94
95 pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
96 self.0.checked_add(duration).map(Instant)
97 }
98
99 pub fn checked_sub(&self, duration: Duration) -> Option<Instant> {
100 self.0.checked_sub(duration).map(Instant)
101 }
102 }
103
104 impl Add<Duration> for Instant {
105 type Output = Instant;
106 fn add(self, other: Duration) -> Instant {
108 Instant(self.0.saturating_add(other))
109 }
110 }
111
112 impl AddAssign<Duration> for Instant {
113 fn add_assign(&mut self, other: Duration) {
114 *self = *self + other;
115 }
116 }
117
118 impl Sub<Duration> for Instant {
119 type Output = Instant;
120 fn sub(self, other: Duration) -> Instant {
122 Instant(self.0.saturating_sub(other))
123 }
124 }
125
126 impl SubAssign<Duration> for Instant {
127 fn sub_assign(&mut self, other: Duration) {
128 *self = *self - other;
129 }
130 }
131
132 impl Sub<Instant> for Instant {
133 type Output = Duration;
134 fn sub(self, other: Instant) -> Duration {
135 self.saturating_duration_since(other)
136 }
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use std::time::Duration;
143
144 use super::host::Instant as HostInstant;
145 use super::HostClock;
146
147 #[test]
148 fn native_instant_is_std() {
149 let now: std::time::Instant = super::Instant::now();
150 assert!(now.elapsed() < Duration::from_secs(60));
151 }
152
153 #[test]
154 fn native_has_no_host_wall_clock() {
155 super::set_host_clock(HostClock {
156 steady_ms: || 5.0,
157 wall_ms: || 7.0,
158 });
159 assert_eq!(super::host_wall_ms(), None);
160 }
161
162 #[test]
163 fn uninjected_clock_reads_zero() {
164 let zero = HostInstant::read(None);
165 assert_eq!(zero, HostInstant::from_ms(0.0));
166 assert_eq!(
167 zero.saturating_duration_since(HostInstant::from_ms(0.0)),
168 Duration::ZERO
169 );
170 }
171
172 #[test]
173 fn host_instant_before_injection_is_the_origin() {
174 let now = HostInstant::now();
176 assert_eq!(now, HostInstant::from_ms(0.0));
177 assert_eq!(now.elapsed(), Duration::ZERO);
178 }
179
180 #[test]
181 fn injected_clock_is_read() {
182 let clock = HostClock {
183 steady_ms: || 1500.0,
184 wall_ms: || 0.0,
185 };
186 assert_eq!(
187 HostInstant::read(Some(clock)).duration_since(HostInstant::from_ms(0.0)),
188 Duration::from_millis(1500)
189 );
190 }
191
192 #[test]
193 fn invalid_readings_clamp_to_zero() {
194 let zero = HostInstant::from_ms(0.0);
195 assert_eq!(HostInstant::from_ms(-3.0), zero);
196 assert_eq!(HostInstant::from_ms(f64::NAN), zero);
197 assert_eq!(HostInstant::from_ms(f64::INFINITY), zero);
198 }
199
200 #[test]
201 fn arithmetic_matches_std_semantics() {
202 let early = HostInstant::from_ms(1000.0);
203 let late = HostInstant::from_ms(3500.0);
204 assert_eq!(late - early, Duration::from_millis(2500));
205 assert_eq!(early - late, Duration::ZERO);
206 assert_eq!(late.duration_since(early), Duration::from_millis(2500));
207 assert_eq!(early.duration_since(late), Duration::ZERO);
208 assert_eq!(early + Duration::from_millis(2500), late);
209 assert_eq!(late - Duration::from_millis(2500), early);
210 assert_eq!(
211 early.checked_sub(Duration::from_secs(2)),
212 None,
213 "before the clock origin"
214 );
215 assert_eq!(early.checked_add(Duration::from_millis(2500)), Some(late));
216 assert_eq!(
217 HostInstant::from_ms(f64::MAX).checked_add(Duration::MAX),
218 None
219 );
220 assert_eq!(
221 HostInstant::from_ms(f64::MAX) + Duration::MAX,
222 HostInstant::from_ms(f64::MAX)
223 );
224 let mut moving = early;
225 moving += Duration::from_millis(2500);
226 assert_eq!(moving, late);
227 moving -= Duration::from_millis(2500);
228 assert_eq!(moving, early);
229 assert!(early < late);
230 }
231
232 #[test]
233 fn subtracting_past_origin_stops_at_origin() {
234 assert_eq!(
235 HostInstant::from_ms(1.0) - Duration::from_secs(1),
236 HostInstant::from_ms(0.0)
237 );
238 }
239}