#[cfg(not(feature = "std"))]
use alloc::vec;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[derive(Clone, Debug)]
pub struct ProfileEntry {
pub name: &'static str,
pub total_ticks: u64,
pub call_count: u64,
pub last_ticks: u64,
pub peak_ticks: u64,
}
impl ProfileEntry {
#[must_use]
pub const fn new(name: &'static str) -> Self {
Self {
name,
total_ticks: 0,
call_count: 0,
last_ticks: 0,
peak_ticks: 0,
}
}
#[inline]
#[must_use]
pub const fn average_ticks(&self) -> u64 {
match self.total_ticks.checked_div(self.call_count) {
Some(avg) => avg,
None => 0,
}
}
pub fn record(&mut self, ticks: u64) {
self.total_ticks = self.total_ticks.saturating_add(ticks);
self.call_count = self.call_count.saturating_add(1);
self.last_ticks = ticks;
if ticks > self.peak_ticks {
self.peak_ticks = ticks;
}
}
pub fn reset(&mut self) {
self.total_ticks = 0;
self.call_count = 0;
self.last_ticks = 0;
self.peak_ticks = 0;
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct StepStats {
pub broadphase_pairs: u32,
pub narrowphase_tests: u32,
pub active_contacts: u32,
pub active_manifolds: u32,
pub active_bodies: u32,
pub static_bodies: u32,
pub solver_iterations: u32,
pub ccd_checks: u32,
pub joint_count: u32,
}
pub struct PhysicsProfiler {
entries: Vec<ProfileEntry>,
pub stats: StepStats,
pub frame_count: u64,
pub enabled: bool,
}
pub const STAGE_BROADPHASE: usize = 0;
pub const STAGE_NARROWPHASE: usize = 1;
pub const STAGE_SOLVER: usize = 2;
pub const STAGE_CCD: usize = 3;
pub const STAGE_INTEGRATION: usize = 4;
pub const STAGE_CONTACT_CACHE: usize = 5;
pub const STAGE_TOTAL_STEP: usize = 6;
impl PhysicsProfiler {
#[must_use]
pub fn new() -> Self {
let entries = vec![
ProfileEntry::new("broadphase"),
ProfileEntry::new("narrowphase"),
ProfileEntry::new("solver"),
ProfileEntry::new("ccd"),
ProfileEntry::new("integration"),
ProfileEntry::new("contact_cache"),
ProfileEntry::new("total_step"),
];
Self {
entries,
stats: StepStats::default(),
frame_count: 0,
enabled: true,
}
}
#[inline]
pub fn record(&mut self, stage: usize, ticks: u64) {
if self.enabled && stage < self.entries.len() {
self.entries[stage].record(ticks);
}
}
pub fn begin_frame(&mut self) {
self.stats = StepStats::default();
self.frame_count += 1;
}
#[must_use]
pub fn get(&self, stage: usize) -> Option<&ProfileEntry> {
self.entries.get(stage)
}
#[must_use]
pub fn last_ticks(&self, stage: usize) -> u64 {
self.entries.get(stage).map_or(0, |e| e.last_ticks)
}
pub fn average_ticks(&self, stage: usize) -> u64 {
self.entries
.get(stage)
.map_or(0, ProfileEntry::average_ticks)
}
pub fn reset(&mut self) {
for entry in &mut self.entries {
entry.reset();
}
self.stats = StepStats::default();
self.frame_count = 0;
}
#[must_use]
pub fn summary(&self) -> Vec<(&'static str, u64, u64, u64)> {
self.entries
.iter()
.map(|e| (e.name, e.last_ticks, e.average_ticks(), e.peak_ticks))
.collect()
}
}
impl Default for PhysicsProfiler {
fn default() -> Self {
Self::new()
}
}
pub struct TickCounter {
current: u64,
}
impl TickCounter {
#[must_use]
pub const fn new() -> Self {
Self { current: 0 }
}
#[inline]
#[must_use]
pub const fn start(&self) -> u64 {
self.current
}
#[inline]
#[must_use]
pub const fn elapsed(&self, start: u64) -> u64 {
self.current.wrapping_sub(start)
}
#[inline]
pub fn advance(&mut self, ticks: u64) {
self.current = self.current.wrapping_add(ticks);
}
#[inline]
#[must_use]
pub const fn now(&self) -> u64 {
self.current
}
}
impl Default for TickCounter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_profile_entry() {
let mut entry = ProfileEntry::new("test");
entry.record(100);
entry.record(200);
entry.record(150);
assert_eq!(entry.call_count, 3);
assert_eq!(entry.total_ticks, 450);
assert_eq!(entry.average_ticks(), 150);
assert_eq!(entry.peak_ticks, 200);
assert_eq!(entry.last_ticks, 150);
}
#[test]
fn test_profiler() {
let mut profiler = PhysicsProfiler::new();
profiler.begin_frame();
profiler.record(STAGE_BROADPHASE, 100);
profiler.record(STAGE_NARROWPHASE, 200);
profiler.record(STAGE_SOLVER, 500);
assert_eq!(profiler.last_ticks(STAGE_BROADPHASE), 100);
assert_eq!(profiler.last_ticks(STAGE_SOLVER), 500);
}
#[test]
fn test_profiler_summary() {
let mut profiler = PhysicsProfiler::new();
profiler.record(STAGE_BROADPHASE, 42);
let summary = profiler.summary();
assert!(summary.len() >= 7);
assert_eq!(summary[STAGE_BROADPHASE].1, 42); }
#[test]
fn test_tick_counter() {
let mut counter = TickCounter::new();
let start = counter.start();
counter.advance(100);
let elapsed = counter.elapsed(start);
assert_eq!(elapsed, 100);
}
#[test]
fn test_step_stats() {
let stats = StepStats::default();
assert_eq!(stats.broadphase_pairs, 0);
assert_eq!(stats.active_contacts, 0);
}
#[test]
fn test_profiler_reset() {
let mut profiler = PhysicsProfiler::new();
profiler.record(STAGE_BROADPHASE, 100);
profiler.reset();
assert_eq!(profiler.last_ticks(STAGE_BROADPHASE), 0);
assert_eq!(profiler.frame_count, 0);
}
}