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 {
63 INSTANT_SV.read().mode
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 let mut sv = INSTANT_SV.write();
76 sv.mode = mode;
77 if let InstantMode::Now = mode {
78 sv.now = None;
79 }
80 }
81
82 pub fn set_now(&self, now: DInstant) {
90 let mut sv = INSTANT_SV.write();
91 if let InstantMode::Now = sv.mode {
92 panic!("cannot set now with `TimeMode::Now`");
93 }
94 sv.now = Some(now);
95 }
96
97 pub fn advance_now(&self, advance: Duration) {
105 let mut sv = INSTANT_SV.write();
106 if let InstantMode::Manual = sv.mode {
107 *sv.now.get_or_insert_with(|| DInstant(INSTANT.epoch().elapsed())) += advance;
108 } else {
109 panic!("cannot advance now, not `InstantMode::Manual`");
110 }
111 }
112
113 pub fn unset_now(&self) {
115 INSTANT_SV.write().now = None;
116 }
117
118 pub fn custom_now(&self) -> Option<DInstant> {
124 INSTANT_SV.read().now
125 }
126
127 pub fn pause_for_update(&self) -> Option<InstantUpdatePause> {
130 let mut sv = INSTANT_SV.write();
131 match sv.mode {
132 InstantMode::UpdatePaused => {
133 let now = DInstant(INSTANT.epoch().elapsed());
134 sv.now = Some(now);
135 Some(InstantUpdatePause { now })
136 }
137 _ => None,
138 }
139 }
140}
141
142#[must_use = "unset_now on drop"]
146pub struct InstantUpdatePause {
147 now: DInstant,
148}
149impl Drop for InstantUpdatePause {
150 fn drop(&mut self) {
151 let mut sv = INSTANT_SV.write();
152 if sv.now == Some(self.now) {
153 sv.now = None;
154 }
155 }
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
162pub struct DInstant(Duration);
163impl DInstant {
164 pub fn elapsed(self) -> Duration {
166 INSTANT.now().0 - self.0
167 }
168
169 pub fn duration_since(self, earlier: DInstant) -> Duration {
172 self.0 - earlier.0
173 }
174
175 pub fn checked_add(&self, duration: Duration) -> Option<DInstant> {
177 self.0.checked_add(duration).map(Self)
178 }
179
180 pub fn checked_sub(self, duration: Duration) -> Option<DInstant> {
183 self.0.checked_sub(duration).map(Self)
184 }
185
186 pub fn checked_duration_since(&self, earlier: DInstant) -> Option<Duration> {
188 self.0.checked_sub(earlier.0)
189 }
190
191 pub fn saturating_duration_since(&self, earlier: DInstant) -> Duration {
193 self.0.saturating_sub(earlier.0)
194 }
195
196 pub const EPOCH: DInstant = DInstant(Duration::ZERO);
198
199 pub const MAX: DInstant = DInstant(Duration::MAX);
201}
202impl ops::Add<Duration> for DInstant {
203 type Output = Self;
204
205 fn add(self, rhs: Duration) -> Self {
206 Self(self.0.saturating_add(rhs))
207 }
208}
209impl ops::AddAssign<Duration> for DInstant {
210 fn add_assign(&mut self, rhs: Duration) {
211 self.0 = self.0.saturating_add(rhs);
212 }
213}
214impl ops::Sub<Duration> for DInstant {
215 type Output = Self;
216
217 fn sub(self, rhs: Duration) -> Self {
218 Self(self.0.saturating_sub(rhs))
219 }
220}
221impl ops::SubAssign<Duration> for DInstant {
222 fn sub_assign(&mut self, rhs: Duration) {
223 self.0 = self.0.saturating_sub(rhs);
224 }
225}
226impl ops::Sub for DInstant {
227 type Output = Duration;
228
229 fn sub(self, rhs: Self) -> Self::Output {
230 self.0.saturating_sub(rhs.0)
231 }
232}
233impl From<DInstant> for Instant {
234 fn from(t: DInstant) -> Self {
235 INSTANT.epoch() + t.0
236 }
237}
238impl From<Instant> for DInstant {
239 fn from(value: Instant) -> Self {
240 DInstant(value - INSTANT.epoch())
241 }
242}
243
244#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
248pub enum InstantMode {
249 UpdatePaused,
252 Now,
254 Manual,
256}
257
258static EPOCH: RwLock<Option<Instant>> = RwLock::new(None);
259
260app_local! {
261 static INSTANT_SV: InstantService = const {
262 InstantService {
263 mode: InstantMode::UpdatePaused,
264 now: None,
265 }
266 };
267}
268
269struct InstantService {
270 mode: InstantMode,
271 now: Option<DInstant>,
272}
273
274#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
295pub struct Deadline(pub DInstant);
296impl Deadline {
297 pub fn timeout(dur: Duration) -> Self {
299 Deadline(INSTANT.now() + dur)
300 }
301
302 pub fn has_elapsed(self) -> bool {
304 self.0 <= INSTANT.now()
305 }
306
307 pub fn time_left(self) -> Option<Duration> {
309 self.0.checked_duration_since(INSTANT.now())
310 }
311
312 pub fn min(self, other: Deadline) -> Deadline {
314 Deadline(self.0.min(other.0))
315 }
316
317 pub fn max(self, other: Deadline) -> Deadline {
319 Deadline(self.0.max(other.0))
320 }
321
322 pub const ELAPSED: Deadline = Deadline(DInstant::EPOCH);
324
325 pub const MAX: Deadline = Deadline(DInstant::MAX);
327}
328impl fmt::Display for Deadline {
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 let dur = self.0 - INSTANT.now();
331 write!(f, "{dur:?} left")
332 }
333}
334impl fmt::Debug for Deadline {
335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336 write!(f, "Deadline({self})")
337 }
338}
339impl From<DInstant> for Deadline {
340 fn from(value: DInstant) -> Self {
341 Deadline(value)
342 }
343}
344impl From<Duration> for Deadline {
345 fn from(value: Duration) -> Self {
346 Deadline::timeout(value)
347 }
348}
349impl From<Instant> for Deadline {
350 fn from(value: Instant) -> Self {
351 DInstant::from(value).into()
352 }
353}
354impl ops::Add<Duration> for Deadline {
355 type Output = Self;
356
357 fn add(mut self, rhs: Duration) -> Self {
358 self.0 += rhs;
359 self
360 }
361}
362impl ops::AddAssign<Duration> for Deadline {
363 fn add_assign(&mut self, rhs: Duration) {
364 self.0 += rhs;
365 }
366}
367impl ops::Sub<Duration> for Deadline {
368 type Output = Self;
369
370 fn sub(mut self, rhs: Duration) -> Self {
371 self.0 -= rhs;
372 self
373 }
374}
375impl ops::SubAssign<Duration> for Deadline {
376 fn sub_assign(&mut self, rhs: Duration) {
377 self.0 -= rhs;
378 }
379}