alice_physics/
profiling.rs1#[cfg(not(feature = "std"))]
15use alloc::vec;
16#[cfg(not(feature = "std"))]
17use alloc::vec::Vec;
18
19#[derive(Clone, Debug)]
21pub struct ProfileEntry {
22 pub name: &'static str,
24 pub total_ticks: u64,
26 pub call_count: u64,
28 pub last_ticks: u64,
30 pub peak_ticks: u64,
32}
33
34impl ProfileEntry {
35 #[must_use]
37 pub const fn new(name: &'static str) -> Self {
38 Self {
39 name,
40 total_ticks: 0,
41 call_count: 0,
42 last_ticks: 0,
43 peak_ticks: 0,
44 }
45 }
46
47 #[inline]
49 #[must_use]
50 pub const fn average_ticks(&self) -> u64 {
51 match self.total_ticks.checked_div(self.call_count) {
52 Some(avg) => avg,
53 None => 0,
54 }
55 }
56
57 pub fn record(&mut self, ticks: u64) {
61 self.total_ticks = self.total_ticks.saturating_add(ticks);
62 self.call_count = self.call_count.saturating_add(1);
63 self.last_ticks = ticks;
64 if ticks > self.peak_ticks {
65 self.peak_ticks = ticks;
66 }
67 }
68
69 pub fn reset(&mut self) {
71 self.total_ticks = 0;
72 self.call_count = 0;
73 self.last_ticks = 0;
74 self.peak_ticks = 0;
75 }
76}
77
78#[derive(Clone, Copy, Debug, Default)]
80pub struct StepStats {
81 pub broadphase_pairs: u32,
83 pub narrowphase_tests: u32,
85 pub active_contacts: u32,
87 pub active_manifolds: u32,
89 pub active_bodies: u32,
91 pub static_bodies: u32,
93 pub solver_iterations: u32,
95 pub ccd_checks: u32,
97 pub joint_count: u32,
99}
100
101pub struct PhysicsProfiler {
103 entries: Vec<ProfileEntry>,
105 pub stats: StepStats,
107 pub frame_count: u64,
109 pub enabled: bool,
111}
112
113pub const STAGE_BROADPHASE: usize = 0;
115pub const STAGE_NARROWPHASE: usize = 1;
117pub const STAGE_SOLVER: usize = 2;
119pub const STAGE_CCD: usize = 3;
121pub const STAGE_INTEGRATION: usize = 4;
123pub const STAGE_CONTACT_CACHE: usize = 5;
125pub const STAGE_TOTAL_STEP: usize = 6;
127
128impl PhysicsProfiler {
129 #[must_use]
131 pub fn new() -> Self {
132 let entries = vec![
133 ProfileEntry::new("broadphase"),
134 ProfileEntry::new("narrowphase"),
135 ProfileEntry::new("solver"),
136 ProfileEntry::new("ccd"),
137 ProfileEntry::new("integration"),
138 ProfileEntry::new("contact_cache"),
139 ProfileEntry::new("total_step"),
140 ];
141
142 Self {
143 entries,
144 stats: StepStats::default(),
145 frame_count: 0,
146 enabled: true,
147 }
148 }
149
150 #[inline]
152 pub fn record(&mut self, stage: usize, ticks: u64) {
153 if self.enabled && stage < self.entries.len() {
154 self.entries[stage].record(ticks);
155 }
156 }
157
158 pub fn begin_frame(&mut self) {
160 self.stats = StepStats::default();
161 self.frame_count += 1;
162 }
163
164 #[must_use]
166 pub fn get(&self, stage: usize) -> Option<&ProfileEntry> {
167 self.entries.get(stage)
168 }
169
170 #[must_use]
172 pub fn last_ticks(&self, stage: usize) -> u64 {
173 self.entries.get(stage).map_or(0, |e| e.last_ticks)
174 }
175
176 pub fn average_ticks(&self, stage: usize) -> u64 {
179 self.entries
180 .get(stage)
181 .map_or(0, ProfileEntry::average_ticks)
182 }
183
184 pub fn reset(&mut self) {
186 for entry in &mut self.entries {
187 entry.reset();
188 }
189 self.stats = StepStats::default();
190 self.frame_count = 0;
191 }
192
193 #[must_use]
195 pub fn summary(&self) -> Vec<(&'static str, u64, u64, u64)> {
196 self.entries
197 .iter()
198 .map(|e| (e.name, e.last_ticks, e.average_ticks(), e.peak_ticks))
199 .collect()
200 }
201}
202
203impl Default for PhysicsProfiler {
204 fn default() -> Self {
205 Self::new()
206 }
207}
208
209pub struct TickCounter {
214 current: u64,
215}
216
217impl TickCounter {
218 #[must_use]
220 pub const fn new() -> Self {
221 Self { current: 0 }
222 }
223
224 #[inline]
226 #[must_use]
227 pub const fn start(&self) -> u64 {
228 self.current
229 }
230
231 #[inline]
233 #[must_use]
234 pub const fn elapsed(&self, start: u64) -> u64 {
235 self.current.wrapping_sub(start)
236 }
237
238 #[inline]
240 pub fn advance(&mut self, ticks: u64) {
241 self.current = self.current.wrapping_add(ticks);
242 }
243
244 #[inline]
246 #[must_use]
247 pub const fn now(&self) -> u64 {
248 self.current
249 }
250}
251
252impl Default for TickCounter {
253 fn default() -> Self {
254 Self::new()
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn test_profile_entry() {
264 let mut entry = ProfileEntry::new("test");
265 entry.record(100);
266 entry.record(200);
267 entry.record(150);
268
269 assert_eq!(entry.call_count, 3);
270 assert_eq!(entry.total_ticks, 450);
271 assert_eq!(entry.average_ticks(), 150);
272 assert_eq!(entry.peak_ticks, 200);
273 assert_eq!(entry.last_ticks, 150);
274 }
275
276 #[test]
277 fn test_profiler() {
278 let mut profiler = PhysicsProfiler::new();
279 profiler.begin_frame();
280
281 profiler.record(STAGE_BROADPHASE, 100);
282 profiler.record(STAGE_NARROWPHASE, 200);
283 profiler.record(STAGE_SOLVER, 500);
284
285 assert_eq!(profiler.last_ticks(STAGE_BROADPHASE), 100);
286 assert_eq!(profiler.last_ticks(STAGE_SOLVER), 500);
287 }
288
289 #[test]
290 fn test_profiler_summary() {
291 let mut profiler = PhysicsProfiler::new();
292 profiler.record(STAGE_BROADPHASE, 42);
293
294 let summary = profiler.summary();
295 assert!(summary.len() >= 7);
296 assert_eq!(summary[STAGE_BROADPHASE].1, 42); }
298
299 #[test]
300 fn test_tick_counter() {
301 let mut counter = TickCounter::new();
302 let start = counter.start();
303 counter.advance(100);
304 let elapsed = counter.elapsed(start);
305 assert_eq!(elapsed, 100);
306 }
307
308 #[test]
309 fn test_step_stats() {
310 let stats = StepStats::default();
311 assert_eq!(stats.broadphase_pairs, 0);
312 assert_eq!(stats.active_contacts, 0);
313 }
314
315 #[test]
316 fn test_profiler_reset() {
317 let mut profiler = PhysicsProfiler::new();
318 profiler.record(STAGE_BROADPHASE, 100);
319 profiler.reset();
320 assert_eq!(profiler.last_ticks(STAGE_BROADPHASE), 0);
321 assert_eq!(profiler.frame_count, 0);
322 }
323}