Skip to main content

animato_devtools/
perf_monitor.rs

1//! Performance monitoring state.
2
3use animato_driver::{AnimationId, DriverFrameProfile};
4use std::collections::VecDeque;
5
6/// Per-animation update cost retained by the performance monitor.
7#[derive(Clone, Debug, PartialEq)]
8pub struct AnimationCostRecord {
9    /// Stable animation id.
10    pub id: AnimationId,
11    /// Optional label.
12    pub label: Option<String>,
13    /// Update cost in milliseconds.
14    pub update_time_ms: f32,
15}
16
17/// Rolling performance monitor for animation workloads.
18#[derive(Clone, Debug, PartialEq)]
19pub struct PerformanceMonitor {
20    frame_times: VecDeque<f32>,
21    window_size: usize,
22    animation_costs: Vec<AnimationCostRecord>,
23    active_animation_count: usize,
24}
25
26impl PerformanceMonitor {
27    /// Create a monitor with a rolling window size.
28    pub fn new(window_size: usize) -> Self {
29        Self {
30            frame_times: VecDeque::with_capacity(window_size.max(1)),
31            window_size: window_size.max(1),
32            animation_costs: Vec::new(),
33            active_animation_count: 0,
34        }
35    }
36
37    /// Record a frame delta in seconds.
38    pub fn record_frame(&mut self, dt: f32) {
39        if self.frame_times.len() == self.window_size {
40            self.frame_times.pop_front();
41        }
42        self.frame_times.push_back(dt.max(0.0));
43    }
44
45    /// Record driver profile data.
46    pub fn record_profile(&mut self, profile: &DriverFrameProfile) {
47        self.record_frame(profile.dt);
48        self.active_animation_count = profile.animation_costs.len();
49        self.animation_costs.clear();
50        self.animation_costs
51            .extend(
52                profile
53                    .animation_costs
54                    .iter()
55                    .map(|cost| AnimationCostRecord {
56                        id: cost.id,
57                        label: cost.label.clone(),
58                        update_time_ms: cost.update_time_ms,
59                    }),
60            );
61    }
62
63    /// Rolling frames per second.
64    pub fn fps(&self) -> f32 {
65        let avg = self.avg_frame_time_ms();
66        if avg <= f32::EPSILON {
67            0.0
68        } else {
69            1000.0 / avg
70        }
71    }
72
73    /// Average frame time in milliseconds.
74    pub fn avg_frame_time_ms(&self) -> f32 {
75        if self.frame_times.is_empty() {
76            return 0.0;
77        }
78        self.frame_times.iter().sum::<f32>() * 1000.0 / self.frame_times.len() as f32
79    }
80
81    /// Maximum frame time in milliseconds.
82    pub fn max_frame_time_ms(&self) -> f32 {
83        self.frame_times.iter().copied().fold(0.0_f32, f32::max) * 1000.0
84    }
85
86    /// Frame budget usage where `1.0` means 100% of budget.
87    pub fn frame_budget_usage(&self, target_fps: f32) -> f32 {
88        if target_fps <= 0.0 {
89            return 0.0;
90        }
91        self.avg_frame_time_ms() / (1000.0 / target_fps)
92    }
93
94    /// Whether the rolling average exceeds the target frame budget.
95    pub fn exceeds_budget(&self, target_fps: f32) -> bool {
96        self.frame_budget_usage(target_fps) > 1.0
97    }
98
99    /// Latest per-animation costs.
100    pub fn animation_costs(&self) -> &[AnimationCostRecord] {
101        &self.animation_costs
102    }
103
104    /// Latest active animation count.
105    pub fn active_animation_count(&self) -> usize {
106        self.active_animation_count
107    }
108}
109
110impl Default for PerformanceMonitor {
111    fn default() -> Self {
112        Self::new(120)
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn calculates_fps_and_budget() {
122        let mut monitor = PerformanceMonitor::new(2);
123        monitor.record_frame(1.0 / 60.0);
124        monitor.record_frame(1.0 / 30.0);
125        assert!(monitor.fps() > 0.0);
126        assert!(monitor.avg_frame_time_ms() > 0.0);
127        assert!(monitor.max_frame_time_ms() >= monitor.avg_frame_time_ms());
128        assert!(monitor.frame_budget_usage(60.0) > 0.0);
129    }
130}