use std::collections::VecDeque;
use serde_json::{Value, json};
use crate::config::{DivergenceConfig, PLAYER_STATE_LEN};
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Source {
Camera,
State,
}
impl Source {
fn as_str(self) -> &'static str {
match self {
Source::Camera => "camera",
Source::State => "state",
}
}
}
pub struct Observation<'a> {
pub source: Source,
pub predicted: &'a [f32],
pub authoritative: &'a [f32; PLAYER_STATE_LEN],
pub server_time: f64,
pub local_now: f64,
pub offset: f64,
pub input_seq: u32,
pub replayed: Option<(f64, f64, usize)>,
}
pub struct DivergenceTracker {
cfg: DivergenceConfig,
records: VecDeque<Value>,
samples: u64,
violations: u64,
dropped: u64,
max_delta: [f32; PLAYER_STATE_LEN],
}
impl DivergenceTracker {
pub fn new(mut cfg: DivergenceConfig) -> Self {
cfg.capacity = cfg.capacity.max(1);
Self {
cfg,
records: VecDeque::new(),
samples: 0,
violations: 0,
dropped: 0,
max_delta: [0.0; PLAYER_STATE_LEN],
}
}
pub fn observe(&mut self, obs: Observation) {
self.samples += 1;
let width = obs.predicted.len().min(PLAYER_STATE_LEN);
let mut deltas = Vec::with_capacity(width);
let mut exceeded = Vec::new();
for index in 0..width {
let delta = obs.predicted[index] - obs.authoritative[index];
deltas.push(delta);
if delta.abs() > self.max_delta[index] {
self.max_delta[index] = delta.abs();
}
if delta.abs() > self.cfg.threshold(index) {
exceeded.push(index);
}
}
if exceeded.is_empty() {
return;
}
self.violations += 1;
if self.records.len() >= self.cfg.capacity {
self.records.pop_front();
self.dropped += 1;
}
let thresholds: Vec<Value> = (0..width)
.map(|index| json!(round4(self.cfg.threshold(index))))
.collect();
self.records.push_back(json!({
"source": obs.source.as_str(),
"serverTime": obs.server_time,
"localNow": obs.local_now,
"offset": obs.offset,
"inputSeq": obs.input_seq,
"replayed": obs.replayed.map(|(from, to, count)| json!({
"from": from,
"to": to,
"count": count,
})),
"predicted": floats(&obs.predicted[..width]),
"authoritative": floats(&obs.authoritative[..width]),
"delta": floats(&deltas),
"exceeded": exceeded,
"thresholds": thresholds,
}));
}
pub fn take_json(&mut self) -> String {
let records: Vec<Value> = self.records.drain(..).collect();
json!({
"samples": self.samples,
"violations": self.violations,
"dropped": self.dropped,
"maxDelta": floats(&self.max_delta),
"records": records,
})
.to_string()
}
}
fn floats(values: &[f32]) -> Vec<Value> {
values.iter().map(|v| json!(round4(*v))).collect()
}
fn round4(value: f32) -> f64 {
((value as f64) * 10_000.0).round() / 10_000.0
}