1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9
10use std::{fmt, ops, time::Duration};
11
12use parking_lot::RwLock;
13use zng_app_context::app_local;
14
15#[cfg(not(target_arch = "wasm32"))]
16use std::time::Instant;
17
18#[cfg(target_arch = "wasm32")]
19use web_time::Instant;
20
21pub struct INSTANT;
23impl INSTANT {
24 pub fn now(&self) -> DInstant {
31 if zng_app_context::LocalContext::current_app().is_some()
32 && let Some(now) = INSTANT_SV.read().now
33 {
34 return now;
35 }
36 DInstant(self.epoch().elapsed())
37 }
38
39 pub fn epoch(&self) -> Instant {
41 if let Some(t) = *EPOCH.read() {
42 return t;
43 }
44 *EPOCH.write().get_or_insert_with(|| {
45 let mut now = Instant::now();
46 for t in [60 * 60 * 24, 60 * 60, 60 * 30, 60 * 15, 60 * 10, 60] {
48 if let Some(t) = now.checked_sub(Duration::from_secs(t)) {
49 now = t;
50 break;
51 }
52 }
53 now
54 })
55 }
56
57 pub fn mode(&self) -> InstantMode {
59 if zng_app_context::LocalContext::current_app().is_some() {
60 INSTANT_SV.read().mode
61 } else {
62 InstantMode::Now
63 }
64 }
65}
66
67#[expect(non_camel_case_types)]
69pub struct INSTANT_APP;
70impl INSTANT_APP {
71 pub fn set_mode(&self, mode: InstantMode) {
75 tracing::trace!("set INSTANT mode to {mode:?}");
76 let mut sv = INSTANT_SV.write();
77 sv.mode = mode;
78 if let InstantMode::Now = mode {
79 sv.now = None;
80 }
81 }
82
83 pub fn set_now(&self, now: DInstant) {
91 tracing::trace!("set INSTANT.now to {now:?}");
92
93 let mut sv = INSTANT_SV.write();
94 if let InstantMode::Now = sv.mode {
95 panic!("cannot set now with `TimeMode::Now`");
96 }
97 sv.now = Some(now);
98 }
99
100 pub fn advance_now(&self, advance: Duration) {
108 let mut sv = INSTANT_SV.write();
109 if let InstantMode::Manual = sv.mode {
110 tracing::trace!("advance INSTANT.now by {advance:?}");
111 *sv.now.get_or_insert_with(|| DInstant(INSTANT.epoch().elapsed())) += advance;
112 } else {
113 panic!("cannot advance now, not `InstantMode::Manual`");
114 }
115 }
116
117 pub fn unset_now(&self) {
119 tracing::trace!("unset custom INSTANT.now");
120 INSTANT_SV.write().now = None;
121 }
122
123 pub fn custom_now(&self) -> Option<DInstant> {
129 INSTANT_SV.read().now
130 }
131
132 pub fn pause_for_update(&self) -> Option<InstantUpdatePause> {
135 let mut sv = INSTANT_SV.write();
136 match sv.mode {
137 InstantMode::UpdatePaused => {
138 tracing::trace!("pause time for update");
139 let now = DInstant(INSTANT.epoch().elapsed());
140 sv.now = Some(now);
141 Some(InstantUpdatePause { now })
142 }
143 _ => None,
144 }
145 }
146}
147
148#[must_use = "unset_now on drop"]
152pub struct InstantUpdatePause {
153 now: DInstant,
154}
155impl Drop for InstantUpdatePause {
156 fn drop(&mut self) {
157 let mut sv = INSTANT_SV.write();
158 if sv.now == Some(self.now) {
159 tracing::trace!("unpause time after update");
160 sv.now = None;
161 }
162 }
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
169pub struct DInstant(Duration);
170impl DInstant {
171 pub fn elapsed(self) -> Duration {
173 INSTANT.now().0 - self.0
174 }
175
176 pub fn duration_since(self, earlier: DInstant) -> Duration {
179 self.0 - earlier.0
180 }
181
182 pub fn checked_add(&self, duration: Duration) -> Option<DInstant> {
184 self.0.checked_add(duration).map(Self)
185 }
186
187 pub fn checked_sub(self, duration: Duration) -> Option<DInstant> {
190 self.0.checked_sub(duration).map(Self)
191 }
192
193 pub fn checked_duration_since(&self, earlier: DInstant) -> Option<Duration> {
195 self.0.checked_sub(earlier.0)
196 }
197
198 pub fn saturating_duration_since(&self, earlier: DInstant) -> Duration {
200 self.0.saturating_sub(earlier.0)
201 }
202
203 pub const EPOCH: DInstant = DInstant(Duration::ZERO);
205
206 pub const MAX: DInstant = DInstant(Duration::MAX);
208}
209impl ops::Add<Duration> for DInstant {
210 type Output = Self;
211
212 fn add(self, rhs: Duration) -> Self {
213 Self(self.0.saturating_add(rhs))
214 }
215}
216impl ops::AddAssign<Duration> for DInstant {
217 fn add_assign(&mut self, rhs: Duration) {
218 self.0 = self.0.saturating_add(rhs);
219 }
220}
221impl ops::Sub<Duration> for DInstant {
222 type Output = Self;
223
224 fn sub(self, rhs: Duration) -> Self {
225 Self(self.0.saturating_sub(rhs))
226 }
227}
228impl ops::SubAssign<Duration> for DInstant {
229 fn sub_assign(&mut self, rhs: Duration) {
230 self.0 = self.0.saturating_sub(rhs);
231 }
232}
233impl ops::Sub for DInstant {
234 type Output = Duration;
235
236 fn sub(self, rhs: Self) -> Self::Output {
237 self.0.saturating_sub(rhs.0)
238 }
239}
240impl From<DInstant> for Instant {
241 fn from(t: DInstant) -> Self {
242 INSTANT.epoch() + t.0
243 }
244}
245impl From<Instant> for DInstant {
246 fn from(value: Instant) -> Self {
247 DInstant(value - INSTANT.epoch())
248 }
249}
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
255pub enum InstantMode {
256 UpdatePaused,
259 Now,
261 Manual,
263}
264
265static EPOCH: RwLock<Option<Instant>> = RwLock::new(None);
266
267app_local! {
268 static INSTANT_SV: InstantService = const {
269 InstantService {
270 mode: InstantMode::UpdatePaused,
271 now: None,
272 }
273 };
274}
275
276struct InstantService {
277 mode: InstantMode,
278 now: Option<DInstant>,
279}
280
281#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
302pub struct Deadline(pub DInstant);
303impl Deadline {
304 pub fn timeout(dur: Duration) -> Self {
306 Deadline(INSTANT.now() + dur)
307 }
308
309 pub fn has_elapsed(self) -> bool {
311 self.0 <= INSTANT.now()
312 }
313
314 pub fn time_left(self) -> Option<Duration> {
316 self.0.checked_duration_since(INSTANT.now())
317 }
318
319 pub fn min(self, other: Deadline) -> Deadline {
321 Deadline(self.0.min(other.0))
322 }
323
324 pub fn max(self, other: Deadline) -> Deadline {
326 Deadline(self.0.max(other.0))
327 }
328
329 pub const ELAPSED: Deadline = Deadline(DInstant::EPOCH);
331
332 pub const MAX: Deadline = Deadline(DInstant::MAX);
334}
335impl fmt::Display for Deadline {
336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337 let dur = self.0 - INSTANT.now();
338 write!(f, "{dur:?} left")
339 }
340}
341impl fmt::Debug for Deadline {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343 write!(f, "Deadline({self})")
344 }
345}
346impl From<DInstant> for Deadline {
347 fn from(value: DInstant) -> Self {
348 Deadline(value)
349 }
350}
351impl From<Duration> for Deadline {
352 fn from(value: Duration) -> Self {
353 Deadline::timeout(value)
354 }
355}
356impl From<Instant> for Deadline {
357 fn from(value: Instant) -> Self {
358 DInstant::from(value).into()
359 }
360}
361impl ops::Add<Duration> for Deadline {
362 type Output = Self;
363
364 fn add(mut self, rhs: Duration) -> Self {
365 self.0 += rhs;
366 self
367 }
368}
369impl ops::AddAssign<Duration> for Deadline {
370 fn add_assign(&mut self, rhs: Duration) {
371 self.0 += rhs;
372 }
373}
374impl ops::Sub<Duration> for Deadline {
375 type Output = Self;
376
377 fn sub(mut self, rhs: Duration) -> Self {
378 self.0 -= rhs;
379 self
380 }
381}
382impl ops::SubAssign<Duration> for Deadline {
383 fn sub_assign(&mut self, rhs: Duration) {
384 self.0 -= rhs;
385 }
386}