use core::time::Duration;
use std::time::Instant;
use crate::protocol::opcode::CLK_RATE_NONE;
use crate::types::{CatchEvent, InputEvent, MotionEvent, TrafficEvent, UsageSnapshot};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ClockDomain {
HostChip,
DeviceChip,
}
impl ClockDomain {
pub const ALL: [ClockDomain; 2] = [ClockDomain::HostChip, ClockDomain::DeviceChip];
pub(crate) fn from_u8(v: u8) -> ClockDomain {
if v == 0 {
ClockDomain::HostChip
} else {
ClockDomain::DeviceChip
}
}
fn index(self) -> usize {
match self {
ClockDomain::HostChip => 0,
ClockDomain::DeviceChip => 1,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ClockEstimate {
pub offset_us: i32,
pub rate_ppb: Option<i32>,
pub delay_us: u16,
pub age: Option<Duration>,
}
impl ClockEstimate {
pub(crate) fn from_payload(p: &[u8]) -> Option<ClockEstimate> {
if p.len() < 12 {
return None;
}
let age_ms = u16::from_le_bytes([p[10], p[11]]);
Some(ClockEstimate {
offset_us: i32::from_le_bytes([p[0], p[1], p[2], p[3]]),
rate_ppb: match i32::from_le_bytes([p[4], p[5], p[6], p[7]]) {
CLK_RATE_NONE => None,
v => Some(v),
},
delay_us: u16::from_le_bytes([p[8], p[9]]),
age: if age_ms == u16::MAX {
None
} else {
Some(Duration::from_millis(age_ms as u64))
},
})
}
pub fn error_bound_us(&self) -> u16 {
self.delay_us / 2
}
pub fn to_host_domain(&self, device_us: u32) -> Option<i64> {
self.age?;
Some(device_us as i64 + self.offset_us as i64)
}
pub fn drift_us_over(&self, elapsed: Duration) -> i64 {
let Some(ppb) = self.rate_ppb else { return 0 };
(elapsed.as_micros() as i64).saturating_mul(ppb as i64) / 1_000_000_000
}
}
pub trait Timestamped {
fn ts_us(&self) -> u32;
fn clock(&self) -> ClockDomain;
}
impl Timestamped for CatchEvent {
fn ts_us(&self) -> u32 {
CatchEvent::ts_us(self)
}
fn clock(&self) -> ClockDomain {
CatchEvent::clock(self)
}
}
impl Timestamped for InputEvent {
fn ts_us(&self) -> u32 {
self.ts_us
}
fn clock(&self) -> ClockDomain {
self.clock
}
}
impl Timestamped for MotionEvent {
fn ts_us(&self) -> u32 {
self.ts_us
}
fn clock(&self) -> ClockDomain {
self.clock
}
}
impl Timestamped for UsageSnapshot {
fn ts_us(&self) -> u32 {
self.ts_us
}
fn clock(&self) -> ClockDomain {
self.clock
}
}
impl Timestamped for TrafficEvent {
fn ts_us(&self) -> u32 {
self.ts_us
}
fn clock(&self) -> ClockDomain {
self.clock
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Stamped {
pub host: Instant,
pub box_us: u64,
pub excess: Duration,
}
const FLOOR_BLOCK: u32 = 4096;
const RESYNC_NS: u64 = 1_000_000;
const HALF_RANGE: u32 = 1 << 31;
#[derive(Debug, Default, Clone, Copy)]
struct DomainState {
epoch: u64,
last: u32,
seen: bool,
min_now: Option<i128>,
min_prev: Option<i128>,
block: u32,
last_host_ns: u64,
samples: u64,
}
impl DomainState {
fn floor(&self) -> Option<i128> {
match (self.min_now, self.min_prev) {
(Some(a), Some(b)) => Some(a.min(b)),
(a, b) => a.or(b),
}
}
fn push_lag(&mut self, lag_ns: i128) -> i128 {
self.min_now = Some(self.min_now.map_or(lag_ns, |m| m.min(lag_ns)));
self.block += 1;
if self.block >= FLOOR_BLOCK {
self.min_prev = self.min_now;
self.min_now = None;
self.block = 0;
}
self.floor().unwrap_or(lag_ns)
}
}
#[derive(Debug)]
pub struct Timeline {
origin: Instant,
domains: [DomainState; 2],
}
impl Default for Timeline {
fn default() -> Timeline {
Timeline::new()
}
}
impl Timeline {
pub fn new() -> Timeline {
Timeline {
origin: Instant::now(),
domains: [DomainState::default(); 2],
}
}
pub fn observe(&mut self, event: &impl Timestamped) -> Stamped {
self.observe_at(event, Instant::now())
}
pub fn observe_at(&mut self, event: &impl Timestamped, now: Instant) -> Stamped {
self.observe_stamp(event.ts_us(), event.clock(), now)
}
pub fn observe_stamp(&mut self, ts_us: u32, domain: ClockDomain, now: Instant) -> Stamped {
let box_us = self.box_us_of(ts_us, domain);
let elapsed_ns = now.saturating_duration_since(self.origin).as_nanos() as i128;
let box_ns = (box_us as i128) * 1_000;
let lag_ns = elapsed_ns - box_ns;
let d = &mut self.domains[domain.index()];
d.samples += 1;
let floor = d.push_lag(lag_ns);
let raw_host_ns = (box_ns + floor).max(0).min(u64::MAX as i128) as u64;
let host_ns = if raw_host_ns + RESYNC_NS < d.last_host_ns {
raw_host_ns
} else {
raw_host_ns.max(d.last_host_ns)
};
d.last_host_ns = host_ns;
Stamped {
host: self.origin + Duration::from_nanos(host_ns),
box_us,
excess: Duration::from_nanos((lag_ns - floor).max(0).min(u64::MAX as i128) as u64),
}
}
pub fn box_us(&mut self, event: &impl Timestamped) -> u64 {
self.box_us_of(event.ts_us(), event.clock())
}
pub fn box_us_of(&mut self, raw: u32, domain: ClockDomain) -> u64 {
let d = &mut self.domains[domain.index()];
if !d.seen {
d.seen = true;
d.last = raw;
return d.epoch + raw as u64;
}
if raw.wrapping_sub(d.last) <= HALF_RANGE {
if raw < d.last {
d.epoch += 1u64 << 32;
}
d.last = raw;
d.epoch + raw as u64
} else {
(d.epoch + d.last as u64).saturating_sub(d.last.wrapping_sub(raw) as u64)
}
}
pub fn reset(&mut self, domain: ClockDomain) {
self.domains[domain.index()] = DomainState::default();
}
pub fn samples(&self, domain: ClockDomain) -> u64 {
self.domains[domain.index()].samples
}
}