use std::time::Instant;
pub fn monotonic_now_ns() -> i64 {
monotonic_base().elapsed().as_nanos() as i64
}
fn monotonic_base() -> Instant {
use std::sync::OnceLock;
static BASE: OnceLock<Instant> = OnceLock::new();
*BASE.get_or_init(Instant::now)
}
#[derive(Debug, Clone)]
pub struct ClockNormalizer {
device_origin_ns: Option<i64>,
monotonic_origin_ns: i64,
}
impl ClockNormalizer {
pub fn new() -> Self {
Self {
device_origin_ns: None,
monotonic_origin_ns: 0,
}
}
pub fn is_unset(&self) -> bool {
self.device_origin_ns.is_none()
}
pub fn normalize(&mut self, device_pts_ns: i64) -> i64 {
match self.device_origin_ns {
Some(origin) => self.monotonic_origin_ns + device_pts_ns.wrapping_sub(origin),
None => {
self.device_origin_ns = Some(device_pts_ns);
self.monotonic_origin_ns = monotonic_now_ns();
self.monotonic_origin_ns
}
}
}
}
impl Default for ClockNormalizer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn monotonic_is_non_decreasing() {
let a = monotonic_now_ns();
let b = monotonic_now_ns();
assert!(b >= a);
}
#[test]
fn first_sample_sets_origin_and_preserves_deltas() {
let mut n = ClockNormalizer::new();
assert!(n.is_unset());
let t0 = n.normalize(1_000_000); assert!(!n.is_unset());
let t1 = n.normalize(6_000_000);
assert_eq!(t1 - t0, 5_000_000);
let t2 = n.normalize(4_000_000);
assert_eq!(t2 - t0, 3_000_000);
}
#[test]
fn normalize_wrapping_sub_handles_i64_boundary() {
let mut n = ClockNormalizer::new();
let base = n.normalize(i64::MAX);
let next = n.normalize(i64::MIN);
assert_eq!(
next.wrapping_sub(base),
1,
"wrapping_sub 境界: MIN - MAX はラップして +1 のはず"
);
}
#[test]
fn first_normalize_ignores_device_value_for_origin() {
let mut n = ClockNormalizer::new();
let before = monotonic_now_ns();
let t0 = n.normalize(i64::MIN);
let after = monotonic_now_ns();
assert!(
t0 >= before && t0 <= after,
"初回は monotonic 原点を返すはず: {before} <= {t0} <= {after}"
);
assert!(!n.is_unset());
}
#[test]
fn default_is_unset_like_new() {
let n = ClockNormalizer::default();
assert!(n.is_unset());
}
}