Skip to main content

ruvector_mincut/
time_compat.rs

1//! WASM-compatible time abstraction
2//!
3//! Provides a monotonic time source that works across native and WASM targets.
4//! On native targets, uses `std::time::Instant` for accurate timing.
5//! On WASM targets (when `wasm` feature is enabled), uses a monotonic counter
6//! since `std::time::Instant` is not supported in wasm32-unknown-unknown.
7
8use std::sync::atomic::{AtomicU64, Ordering};
9
10/// Global monotonic counter for WASM builds
11#[cfg(feature = "wasm")]
12static MONOTONIC_COUNTER: AtomicU64 = AtomicU64::new(0);
13
14/// A WASM-compatible instant type
15#[derive(Debug, Clone, Copy)]
16pub struct PortableInstant {
17    #[cfg(not(feature = "wasm"))]
18    inner: std::time::Instant,
19    #[cfg(feature = "wasm")]
20    counter: u64,
21}
22
23impl PortableInstant {
24    /// Get the current instant
25    #[cfg(not(feature = "wasm"))]
26    pub fn now() -> Self {
27        Self {
28            inner: std::time::Instant::now(),
29        }
30    }
31
32    /// Get the current instant (WASM version - uses monotonic counter)
33    #[cfg(feature = "wasm")]
34    pub fn now() -> Self {
35        let counter = MONOTONIC_COUNTER.fetch_add(1, Ordering::SeqCst);
36        Self { counter }
37    }
38
39    /// Get elapsed time in microseconds
40    #[cfg(not(feature = "wasm"))]
41    pub fn elapsed_micros(&self) -> u64 {
42        self.inner.elapsed().as_micros() as u64
43    }
44
45    /// Get elapsed "time" in WASM (returns counter difference as proxy)
46    #[cfg(feature = "wasm")]
47    pub fn elapsed_micros(&self) -> u64 {
48        let current = MONOTONIC_COUNTER.load(Ordering::SeqCst);
49        // In WASM, we can't measure real time, so return counter diff
50        // This is sufficient for relative ordering and statistics
51        current.saturating_sub(self.counter)
52    }
53
54    /// Get elapsed time as Duration
55    #[cfg(not(feature = "wasm"))]
56    pub fn elapsed(&self) -> std::time::Duration {
57        self.inner.elapsed()
58    }
59
60    /// Get elapsed "time" as Duration in WASM (returns pseudo-duration)
61    #[cfg(feature = "wasm")]
62    pub fn elapsed(&self) -> std::time::Duration {
63        std::time::Duration::from_micros(self.elapsed_micros())
64    }
65
66    /// Get duration since another instant
67    #[cfg(not(feature = "wasm"))]
68    pub fn duration_since(&self, earlier: Self) -> std::time::Duration {
69        self.inner.duration_since(earlier.inner)
70    }
71
72    /// Get duration since another instant (WASM version)
73    #[cfg(feature = "wasm")]
74    pub fn duration_since(&self, earlier: Self) -> std::time::Duration {
75        let diff = self.counter.saturating_sub(earlier.counter);
76        std::time::Duration::from_micros(diff)
77    }
78}
79
80impl Default for PortableInstant {
81    fn default() -> Self {
82        Self::now()
83    }
84}
85
86/// A WASM-compatible timestamp type for certificates and audit logs
87#[derive(Debug, Clone, Copy)]
88pub struct PortableTimestamp {
89    /// Seconds since UNIX epoch (or monotonic counter in WASM)
90    pub secs: u64,
91}
92
93impl PortableTimestamp {
94    /// Get current timestamp
95    #[cfg(not(feature = "wasm"))]
96    pub fn now() -> Self {
97        let secs = std::time::SystemTime::now()
98            .duration_since(std::time::UNIX_EPOCH)
99            .unwrap_or_default()
100            .as_secs();
101        Self { secs }
102    }
103
104    /// Get current timestamp (WASM version - uses monotonic counter)
105    #[cfg(feature = "wasm")]
106    pub fn now() -> Self {
107        let secs = MONOTONIC_COUNTER.fetch_add(1, Ordering::SeqCst);
108        Self { secs }
109    }
110
111    /// Convert to u64 seconds
112    pub fn as_secs(&self) -> u64 {
113        self.secs
114    }
115}
116
117impl Default for PortableTimestamp {
118    fn default() -> Self {
119        Self::now()
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn test_portable_instant() {
129        let start = PortableInstant::now();
130        // Do some work
131        let _sum: u64 = (0..1000).sum();
132        let elapsed = start.elapsed_micros();
133        // Should be non-zero on native, may be 0 on WASM due to counter
134        #[cfg(not(feature = "wasm"))]
135        assert!(elapsed >= 0);
136    }
137
138    #[test]
139    fn test_portable_timestamp() {
140        let ts1 = PortableTimestamp::now();
141        let ts2 = PortableTimestamp::now();
142        // Second timestamp should be >= first
143        assert!(ts2.secs >= ts1.secs);
144    }
145
146    #[test]
147    fn test_instant_ordering() {
148        let t1 = PortableInstant::now();
149        let t2 = PortableInstant::now();
150        let d = t2.duration_since(t1);
151        // Duration should be non-negative
152        assert!(d.as_micros() >= 0);
153    }
154}