Skip to main content

siglus_scene_vm/
platform_time.rs

1//! Cross-platform time helpers.
2//!
3//! wasm32-unknown-unknown does not support std::time::Instant::now() or
4//! std::time::SystemTime::now(). Use this module anywhere runtime code needs
5//! wall-clock or monotonic time.
6
7pub use std::time::Duration;
8
9#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
10pub use web_time::Instant;
11
12#[cfg(all(
13    not(all(target_arch = "wasm32", target_os = "unknown")),
14    not(feature = "virtual-clock")
15))]
16pub use std::time::Instant;
17
18#[cfg(feature = "virtual-clock")]
19pub use virtual_clock::{Instant, advance_virtual_clock};
20
21/// A monotonic clock that only moves when told to, with the parts of
22/// `std::time::Instant`'s API the engine uses: VM replays then run the same
23/// on every machine and at any speed.
24#[cfg(feature = "virtual-clock")]
25mod virtual_clock {
26    use std::ops::{Add, AddAssign, Sub, SubAssign};
27    use std::sync::atomic::{AtomicU64, Ordering};
28    use std::time::Duration;
29
30    /// Nanoseconds since the virtual epoch; starts at one day so that
31    /// subtracting small durations from `now()` stays valid.
32    static NOW_NS: AtomicU64 = AtomicU64::new(86_400_000_000_000);
33
34    pub fn advance_virtual_clock(by: Duration) {
35        NOW_NS.fetch_add(by.as_nanos() as u64, Ordering::SeqCst);
36    }
37
38    #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
39    pub struct Instant(u64);
40
41    impl Instant {
42        pub fn now() -> Self {
43            Self(NOW_NS.load(Ordering::SeqCst))
44        }
45
46        pub fn elapsed(&self) -> Duration {
47            Self::now().saturating_duration_since(*self)
48        }
49
50        pub fn duration_since(&self, earlier: Self) -> Duration {
51            self.saturating_duration_since(earlier)
52        }
53
54        pub fn saturating_duration_since(&self, earlier: Self) -> Duration {
55            Duration::from_nanos(self.0.saturating_sub(earlier.0))
56        }
57
58        pub fn checked_duration_since(&self, earlier: Self) -> Option<Duration> {
59            self.0.checked_sub(earlier.0).map(Duration::from_nanos)
60        }
61
62        pub fn checked_add(&self, d: Duration) -> Option<Self> {
63            self.0.checked_add(d.as_nanos() as u64).map(Self)
64        }
65
66        pub fn checked_sub(&self, d: Duration) -> Option<Self> {
67            self.0.checked_sub(d.as_nanos() as u64).map(Self)
68        }
69    }
70
71    impl Add<Duration> for Instant {
72        type Output = Self;
73        fn add(self, d: Duration) -> Self {
74            self.checked_add(d).expect("virtual instant overflow")
75        }
76    }
77
78    impl AddAssign<Duration> for Instant {
79        fn add_assign(&mut self, d: Duration) {
80            *self = *self + d;
81        }
82    }
83
84    impl Sub<Duration> for Instant {
85        type Output = Self;
86        fn sub(self, d: Duration) -> Self {
87            self.checked_sub(d).expect("virtual instant underflow")
88        }
89    }
90
91    impl SubAssign<Duration> for Instant {
92        fn sub_assign(&mut self, d: Duration) {
93            *self = *self - d;
94        }
95    }
96
97    impl Sub<Instant> for Instant {
98        type Output = Duration;
99        fn sub(self, other: Instant) -> Duration {
100            self.saturating_duration_since(other)
101        }
102    }
103}
104
105#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
106pub fn unix_time_millis() -> u128 {
107    let ms = js_sys::Date::now();
108    if ms.is_finite() && ms > 0.0 {
109        ms as u128
110    } else {
111        0
112    }
113}
114
115#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
116pub fn unix_time_millis() -> u128 {
117    std::time::SystemTime::now()
118        .duration_since(std::time::UNIX_EPOCH)
119        .unwrap_or_default()
120        .as_millis()
121}
122
123pub fn unix_time_secs() -> u64 {
124    (unix_time_millis() / 1000) as u64
125}
126
127#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
128pub struct LocalTimeFields {
129    pub year: i32,
130    pub month: u32,
131    pub day: u32,
132    /// 0 = Sunday, 6 = Saturday, matching Windows SYSTEMTIME.wDayOfWeek.
133    pub weekday_sunday0: u32,
134    pub hour: u32,
135    pub minute: u32,
136    pub second: u32,
137    pub millisecond: u32,
138}
139
140#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
141pub fn local_time_fields() -> LocalTimeFields {
142    let now = js_sys::Date::new_0();
143    LocalTimeFields {
144        year: now.get_full_year() as i32,
145        month: now.get_month() + 1,
146        day: now.get_date(),
147        weekday_sunday0: now.get_day(),
148        hour: now.get_hours(),
149        minute: now.get_minutes(),
150        second: now.get_seconds(),
151        millisecond: now.get_milliseconds(),
152    }
153}
154
155#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
156pub fn local_time_fields() -> LocalTimeFields {
157    use chrono::{Datelike, Timelike};
158
159    let now = chrono::Local::now();
160    LocalTimeFields {
161        year: now.year(),
162        month: now.month(),
163        day: now.day(),
164        weekday_sunday0: now.weekday().num_days_from_sunday(),
165        hour: now.hour(),
166        minute: now.minute(),
167        second: now.second(),
168        millisecond: now.timestamp_subsec_millis(),
169    }
170}
171
172pub fn local_log_timestamp() -> String {
173    let t = local_time_fields();
174    format!(
175        "[{year:04}-{month:02}-{day:02} {hour:02}:{minute:02}:{second:02}]",
176        year = t.year,
177        month = t.month,
178        day = t.day,
179        hour = t.hour,
180        minute = t.minute,
181        second = t.second,
182    )
183}