Skip to main content

ember_core/
time.rs

1//! Compact monotonic time utilities.
2//!
3//! Uses a process-local monotonic clock for timestamps that are smaller
4//! than std::time::Instant (8 bytes vs 16 bytes for Option<Instant>).
5
6use std::sync::OnceLock;
7use std::time::Instant;
8
9/// Returns current monotonic time in milliseconds since process start.
10#[inline]
11pub fn now_ms() -> u64 {
12    static START: OnceLock<Instant> = OnceLock::new();
13    let start = START.get_or_init(Instant::now);
14    start.elapsed().as_millis() as u64
15}
16
17/// Returns current monotonic time in seconds since process start, as u32.
18///
19/// Used for LRU last-access tracking where millisecond precision isn't
20/// needed and the smaller type improves cache-line packing. Wraps after
21/// ~136 years — well beyond any realistic process lifetime.
22#[inline]
23pub fn now_secs() -> u32 {
24    static START: OnceLock<Instant> = OnceLock::new();
25    let start = START.get_or_init(Instant::now);
26    start.elapsed().as_secs() as u32
27}
28
29/// Sentinel value meaning "no expiry".
30pub const NO_EXPIRY: u64 = 0;
31
32/// Returns true if the given expiry timestamp has passed.
33#[inline]
34pub fn is_expired(expires_at_ms: u64) -> bool {
35    expires_at_ms != NO_EXPIRY && now_ms() >= expires_at_ms
36}
37
38/// Converts a Duration to an absolute expiry timestamp.
39#[inline]
40pub fn expiry_from_duration(ttl: Option<std::time::Duration>) -> u64 {
41    ttl.map(|d| {
42        let ms = d.as_millis().min(u64::MAX as u128) as u64;
43        now_ms().saturating_add(ms)
44    })
45    .unwrap_or(NO_EXPIRY)
46}
47
48/// Returns remaining TTL in seconds, or None if no expiry.
49#[inline]
50pub fn remaining_secs(expires_at_ms: u64) -> Option<u64> {
51    remaining(expires_at_ms, 1000)
52}
53
54/// Returns remaining TTL in milliseconds, or None if no expiry.
55#[inline]
56pub fn remaining_ms(expires_at_ms: u64) -> Option<u64> {
57    remaining(expires_at_ms, 1)
58}
59
60/// Shared implementation for remaining TTL with a unit divisor.
61#[inline]
62fn remaining(expires_at_ms: u64, divisor: u64) -> Option<u64> {
63    if expires_at_ms == NO_EXPIRY {
64        None
65    } else {
66        let now = now_ms();
67        Some(expires_at_ms.saturating_sub(now) / divisor)
68    }
69}