Skip to main content

oxihuman_core/
frame_counter.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5/// A frame counter that tracks frame timing statistics.
6#[allow(dead_code)]
7#[derive(Debug, Clone)]
8pub struct FrameCounter {
9    frame: u64,
10    total_time: f64,
11    frame_times: Vec<f64>,
12    max_samples: usize,
13}
14
15#[allow(dead_code)]
16impl FrameCounter {
17    pub fn new(max_samples: usize) -> Self {
18        Self {
19            frame: 0,
20            total_time: 0.0,
21            frame_times: Vec::with_capacity(max_samples),
22            max_samples,
23        }
24    }
25
26    pub fn tick(&mut self, dt: f64) {
27        self.frame += 1;
28        self.total_time += dt;
29        if self.frame_times.len() >= self.max_samples {
30            self.frame_times.remove(0);
31        }
32        self.frame_times.push(dt);
33    }
34
35    pub fn frame(&self) -> u64 {
36        self.frame
37    }
38
39    pub fn total_time(&self) -> f64 {
40        self.total_time
41    }
42
43    pub fn avg_frame_time(&self) -> f64 {
44        if self.frame_times.is_empty() {
45            return 0.0;
46        }
47        let sum: f64 = self.frame_times.iter().sum();
48        sum / self.frame_times.len() as f64
49    }
50
51    pub fn fps(&self) -> f64 {
52        let avg = self.avg_frame_time();
53        if avg <= 0.0 {
54            0.0
55        } else {
56            1.0 / avg
57        }
58    }
59
60    pub fn min_frame_time(&self) -> f64 {
61        self.frame_times
62            .iter()
63            .copied()
64            .reduce(f64::min)
65            .unwrap_or(0.0)
66    }
67
68    pub fn max_frame_time(&self) -> f64 {
69        self.frame_times
70            .iter()
71            .copied()
72            .reduce(f64::max)
73            .unwrap_or(0.0)
74    }
75
76    pub fn sample_count(&self) -> usize {
77        self.frame_times.len()
78    }
79
80    pub fn reset(&mut self) {
81        self.frame = 0;
82        self.total_time = 0.0;
83        self.frame_times.clear();
84    }
85
86    pub fn last_frame_time(&self) -> f64 {
87        self.frame_times.last().copied().unwrap_or(0.0)
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_new() {
97        let fc = FrameCounter::new(60);
98        assert_eq!(fc.frame(), 0);
99        assert!((fc.total_time()).abs() < 1e-12);
100    }
101
102    #[test]
103    fn test_tick() {
104        let mut fc = FrameCounter::new(60);
105        fc.tick(0.016);
106        assert_eq!(fc.frame(), 1);
107        assert!((fc.total_time() - 0.016).abs() < 1e-12);
108    }
109
110    #[test]
111    fn test_avg_frame_time() {
112        let mut fc = FrameCounter::new(60);
113        fc.tick(0.010);
114        fc.tick(0.020);
115        assert!((fc.avg_frame_time() - 0.015).abs() < 1e-12);
116    }
117
118    #[test]
119    fn test_fps() {
120        let mut fc = FrameCounter::new(60);
121        fc.tick(0.01);
122        assert!((fc.fps() - 100.0).abs() < 1e-6);
123    }
124
125    #[test]
126    fn test_min_max() {
127        let mut fc = FrameCounter::new(60);
128        fc.tick(0.010);
129        fc.tick(0.005);
130        fc.tick(0.020);
131        assert!((fc.min_frame_time() - 0.005).abs() < 1e-12);
132        assert!((fc.max_frame_time() - 0.020).abs() < 1e-12);
133    }
134
135    #[test]
136    fn test_max_samples() {
137        let mut fc = FrameCounter::new(3);
138        fc.tick(1.0);
139        fc.tick(2.0);
140        fc.tick(3.0);
141        fc.tick(4.0);
142        assert_eq!(fc.sample_count(), 3);
143        assert!((fc.min_frame_time() - 2.0).abs() < 1e-12);
144    }
145
146    #[test]
147    fn test_reset() {
148        let mut fc = FrameCounter::new(60);
149        fc.tick(0.016);
150        fc.tick(0.016);
151        fc.reset();
152        assert_eq!(fc.frame(), 0);
153        assert_eq!(fc.sample_count(), 0);
154    }
155
156    #[test]
157    fn test_last_frame_time() {
158        let mut fc = FrameCounter::new(60);
159        fc.tick(0.01);
160        fc.tick(0.02);
161        assert!((fc.last_frame_time() - 0.02).abs() < 1e-12);
162    }
163
164    #[test]
165    fn test_empty_stats() {
166        let fc = FrameCounter::new(60);
167        assert!((fc.avg_frame_time()).abs() < 1e-12);
168        assert!((fc.fps()).abs() < 1e-12);
169        assert!((fc.last_frame_time()).abs() < 1e-12);
170    }
171
172    #[test]
173    fn test_total_time_accumulates() {
174        let mut fc = FrameCounter::new(2);
175        fc.tick(1.0);
176        fc.tick(2.0);
177        fc.tick(3.0);
178        assert!((fc.total_time() - 6.0).abs() < 1e-12);
179    }
180}