use std::time::Instant;
use serde::Serialize;
#[derive(Debug, Clone, Serialize, Default)]
pub struct StreamStats {
pub frames: u64,
pub dropped_frames: u64,
pub decode_errors: u64,
pub resets: u64,
pub late_frames: u64,
pub duplicate_frames: u64,
pub frame_rate: f64,
pub server_latency: Option<f64>,
pub total_latency: Option<f64>,
pub clock_offset: Option<f64>,
pub rigid_bodies: usize,
pub tracked_rigid_bodies: usize,
pub labeled_markers: usize,
pub last_frame_number: Option<i32>,
#[serde(skip)]
last_frame_at: Option<Instant>,
#[serde(skip)]
mean_interval: f64,
#[serde(skip)]
late_run: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Sequence {
First,
Next,
Duplicate,
Late,
Restart,
}
pub const REORDER_WINDOW: u32 = 1000;
pub const MAX_GAP: u32 = 100_000;
pub const MAX_LATE_RUN: u32 = 60;
impl StreamStats {
pub fn record_frame(&mut self, frame_number: i32, now: Instant) -> Sequence {
self.frames += 1;
let sequence = match self.last_frame_number {
None => Sequence::First,
Some(last) => {
let delta = (frame_number as u32).wrapping_sub(last as u32);
let behind = (last as u32).wrapping_sub(frame_number as u32);
if delta == 0 {
self.duplicate_frames += 1;
Sequence::Duplicate
} else if delta <= MAX_GAP {
self.dropped_frames += u64::from(delta - 1);
Sequence::Next
} else if delta <= u32::MAX / 2 {
self.resets += 1;
Sequence::Restart
} else if behind <= REORDER_WINDOW && self.late_run < MAX_LATE_RUN {
self.late_frames += 1;
self.late_run += 1;
return Sequence::Late;
} else {
self.resets += 1;
Sequence::Restart
}
}
};
self.late_run = 0;
self.last_frame_number = Some(frame_number);
if let Some(prev) = self.last_frame_at {
let dt = now.duration_since(prev).as_secs_f64();
if dt > 0.0 {
self.mean_interval = if self.mean_interval == 0.0 {
dt
} else {
0.95 * self.mean_interval + 0.05 * dt
};
self.frame_rate = 1.0 / self.mean_interval;
}
}
self.last_frame_at = Some(now);
sequence
}
#[must_use]
pub fn new_frames(&self) -> u64 {
self.frames - self.late_frames - self.duplicate_frames
}
#[must_use]
pub fn since_last_frame(&self, now: Instant) -> Option<f64> {
self.last_frame_at
.map(|t| now.duration_since(t).as_secs_f64())
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::*;
#[test]
fn drops_resets_and_rate() {
let mut s = StreamStats::default();
let t0 = Instant::now();
assert_eq!(s.record_frame(10, t0), Sequence::First);
s.record_frame(11, t0 + Duration::from_millis(10));
assert_eq!(
s.record_frame(14, t0 + Duration::from_millis(20)),
Sequence::Next
);
assert_eq!(s.dropped_frames, 2);
assert_eq!(
s.record_frame(3, t0 + Duration::from_millis(30)),
Sequence::Late
);
assert_eq!(
s.record_frame(14, t0 + Duration::from_millis(30)),
Sequence::Duplicate
);
assert_eq!(
s.record_frame(-5000, t0 + Duration::from_millis(30)),
Sequence::Restart
);
assert_eq!((s.resets, s.late_frames, s.duplicate_frames), (1, 1, 1));
assert!((s.frame_rate - 100.0).abs() < 1e-6);
assert_eq!(s.frames, 6);
assert_eq!(s.new_frames(), 4);
}
fn rates(offsets_ms: &[f64]) -> Vec<f64> {
let mut s = StreamStats::default();
let t0 = Instant::now();
(1..)
.zip(offsets_ms)
.map(|(n, &ms)| {
s.record_frame(n, t0 + Duration::from_secs_f64(ms / 1e3));
s.frame_rate
})
.collect()
}
#[test]
fn jittered_intervals_average_to_the_true_rate() {
let offsets: Vec<f64> = (0..400)
.scan(0.0, |t, i| {
*t += if i % 2 == 0 { 5.0 } else { 15.0 };
Some(*t)
})
.collect();
let rates = rates(&offsets);
for rate in &rates[200..] {
assert!((rate - 100.0).abs() < 1.5, "{rate} Hz");
}
}
#[test]
fn a_bunched_pair_does_not_spike_the_rate() {
let mut offsets: Vec<f64> = (0..100).map(|i| f64::from(i) * 10.0).collect();
offsets.extend([1009.996, 1010.0]);
offsets.extend((102..200).map(|i| f64::from(i) * 10.0));
let rates = rates(&offsets);
let peak = rates.iter().copied().fold(0.0, f64::max);
assert!(peak < 110.0, "{peak} Hz");
}
#[test]
fn frame_numbers_wrap() {
let mut s = StreamStats::default();
let t0 = Instant::now();
s.record_frame(i32::MAX - 1, t0);
assert_eq!(s.record_frame(i32::MIN + 1, t0), Sequence::Next);
assert_eq!(s.dropped_frames, 2);
assert_eq!(s.record_frame(i32::MAX, t0), Sequence::Late);
assert_eq!(s.record_frame(1_000_000, t0), Sequence::Restart);
assert_eq!(s.dropped_frames, 2);
}
#[test]
fn long_late_runs_restart() {
let mut s = StreamStats::default();
let t0 = Instant::now();
s.record_frame(500, t0);
let seq: Vec<Sequence> = (400..=(400 + MAX_LATE_RUN as i32))
.map(|n| s.record_frame(n, t0))
.collect();
assert!(
seq[..MAX_LATE_RUN as usize]
.iter()
.all(|q| *q == Sequence::Late)
);
assert_eq!(seq[MAX_LATE_RUN as usize], Sequence::Restart);
assert_eq!(
s.record_frame(400 + MAX_LATE_RUN as i32 + 1, t0),
Sequence::Next
);
}
}