#![allow(missing_docs)]
#![allow(dead_code)]
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhysicsStats {
pub step: u64,
pub dt: f64,
pub body_count: usize,
pub sleeping_count: usize,
pub contact_count: usize,
pub island_count: usize,
pub solve_iterations: usize,
pub broad_phase_pairs: usize,
pub kinetic_energy: f64,
pub elapsed_ms: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhysicsAverages {
pub dt: f64,
pub kinetic_energy: f64,
pub elapsed_ms: f64,
pub sample_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TelemetrySession {
window: VecDeque<PhysicsStats>,
pub window_size: usize,
pub total_steps: u64,
}
impl TelemetrySession {
pub fn new(window_size: usize) -> Self {
assert!(window_size > 0, "window_size must be positive");
Self {
window: VecDeque::with_capacity(window_size),
window_size,
total_steps: 0,
}
}
pub fn push(&mut self, stats: PhysicsStats) {
if self.window.len() == self.window_size {
self.window.pop_front();
}
self.window.push_back(stats);
self.total_steps += 1;
}
pub fn clear(&mut self) {
self.window.clear();
}
pub fn latest(&self) -> Option<&PhysicsStats> {
self.window.back()
}
pub fn len(&self) -> usize {
self.window.len()
}
pub fn is_empty(&self) -> bool {
self.window.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &PhysicsStats> {
self.window.iter()
}
pub fn average(&self) -> Option<PhysicsAverages> {
let n = self.window.len();
if n == 0 {
return None;
}
let n_f = n as f64;
let mut sum_dt = 0.0_f64;
let mut sum_ke = 0.0_f64;
let mut sum_ms = 0.0_f64;
for s in &self.window {
sum_dt += s.dt;
sum_ke += s.kinetic_energy;
sum_ms += s.elapsed_ms;
}
Some(PhysicsAverages {
dt: sum_dt / n_f,
kinetic_energy: sum_ke / n_f,
elapsed_ms: sum_ms / n_f,
sample_count: n,
})
}
pub fn peak_kinetic_energy(&self) -> Option<f64> {
self.window
.iter()
.map(|s| s.kinetic_energy)
.reduce(f64::max)
}
pub fn peak_elapsed_ms(&self) -> Option<f64> {
self.window.iter().map(|s| s.elapsed_ms).reduce(f64::max)
}
pub fn avg_elapsed_ms(&self) -> Option<f64> {
self.average().map(|a| a.elapsed_ms)
}
pub fn energy_trend(&self) -> f64 {
let n = self.window.len();
if n < 2 {
return 0.0;
}
let n_f = n as f64;
let sum_x: f64 = (0..n).map(|i| i as f64).sum();
let sum_y: f64 = self.window.iter().map(|s| s.kinetic_energy).sum();
let sum_xx: f64 = (0..n).map(|i| (i as f64) * (i as f64)).sum();
let sum_xy: f64 = self
.window
.iter()
.enumerate()
.map(|(i, s)| (i as f64) * s.kinetic_energy)
.sum();
let denom = n_f * sum_xx - sum_x * sum_x;
if denom.abs() < 1e-12 {
return 0.0;
}
(n_f * sum_xy - sum_x * sum_y) / denom
}
pub fn to_csv(&self) -> String {
let mut out = String::from(
"step,dt,body_count,sleeping_count,contact_count,\
island_count,solve_iterations,broad_phase_pairs,kinetic_energy,elapsed_ms\n",
);
for s in &self.window {
out.push_str(&format!(
"{},{},{},{},{},{},{},{},{},{}\n",
s.step,
s.dt,
s.body_count,
s.sleeping_count,
s.contact_count,
s.island_count,
s.solve_iterations,
s.broad_phase_pairs,
s.kinetic_energy,
s.elapsed_ms,
));
}
out
}
}