cranpose_app_shell/
fps_monitor.rs1use std::collections::VecDeque;
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::sync::RwLock;
11use web_time::Instant;
12
13static FPS_TRACKER: RwLock<Option<FpsTracker>> = RwLock::new(None);
15
16static RECOMPOSITION_COUNT: AtomicU64 = AtomicU64::new(0);
18
19const FRAME_HISTORY_SIZE: usize = 60;
21
22pub struct FpsTracker {
24 frame_times: VecDeque<Instant>,
26 last_fps: f32,
28 frame_count: u64,
30 avg_frame_ms: f32,
32 last_recomp_count: u64,
34 recomps_per_second: u64,
36 last_recomp_calc: Instant,
38}
39
40impl FpsTracker {
41 fn new() -> Self {
42 Self {
43 frame_times: VecDeque::with_capacity(FRAME_HISTORY_SIZE + 1),
44 last_fps: 0.0,
45 frame_count: 0,
46 avg_frame_ms: 0.0,
47 last_recomp_count: 0,
48 recomps_per_second: 0,
49 last_recomp_calc: Instant::now(),
50 }
51 }
52
53 fn record_frame(&mut self) {
54 let now = Instant::now();
55
56 self.frame_times.push_back(now);
57 self.frame_count += 1;
58
59 while self.frame_times.len() > FRAME_HISTORY_SIZE {
61 self.frame_times.pop_front();
62 }
63
64 if self.frame_times.len() >= 2 {
66 let first = self.frame_times.front().unwrap();
67 let last = self.frame_times.back().unwrap();
68 let duration = last.duration_since(*first).as_secs_f32();
69 if duration > 0.0 {
70 self.last_fps = (self.frame_times.len() - 1) as f32 / duration;
71 self.avg_frame_ms = duration * 1000.0 / (self.frame_times.len() - 1) as f32;
72 }
73 }
74
75 let elapsed = now.duration_since(self.last_recomp_calc).as_secs_f32();
77 if elapsed >= 1.0 {
78 let current_recomp = RECOMPOSITION_COUNT.load(Ordering::Relaxed);
79 self.recomps_per_second = current_recomp - self.last_recomp_count;
80 self.last_recomp_count = current_recomp;
81 self.last_recomp_calc = now;
82 }
83 }
84
85 fn stats(&self) -> FpsStats {
86 FpsStats {
87 fps: self.last_fps,
88 avg_ms: self.avg_frame_ms,
89 frame_count: self.frame_count,
90 recompositions: RECOMPOSITION_COUNT.load(Ordering::Relaxed),
91 recomps_per_second: self.recomps_per_second,
92 }
93 }
94}
95
96#[derive(Clone, Copy, Debug, Default)]
98pub struct FpsStats {
99 pub fps: f32,
101 pub avg_ms: f32,
103 pub frame_count: u64,
105 pub recompositions: u64,
107 pub recomps_per_second: u64,
109}
110
111pub fn init_fps_tracker() {
113 let mut tracker = FPS_TRACKER.write().unwrap();
114 *tracker = Some(FpsTracker::new());
115}
116
117pub fn record_frame() {
119 if let Ok(mut tracker) = FPS_TRACKER.write() {
120 if let Some(ref mut t) = *tracker {
121 t.record_frame();
122 }
123 }
124}
125
126pub fn record_recomposition() {
128 RECOMPOSITION_COUNT.fetch_add(1, Ordering::Relaxed);
129}
130
131pub fn current_fps() -> f32 {
133 if let Ok(tracker) = FPS_TRACKER.read() {
134 if let Some(ref t) = *tracker {
135 return t.last_fps;
136 }
137 }
138 0.0
139}
140
141pub fn fps_stats() -> FpsStats {
143 if let Ok(tracker) = FPS_TRACKER.read() {
144 if let Some(ref t) = *tracker {
145 return t.stats();
146 }
147 }
148 FpsStats::default()
149}
150
151pub fn fps_display() -> String {
153 let stats = fps_stats();
154 format!("{:.0} FPS ({:.1}ms)", stats.fps, stats.avg_ms)
155}
156
157pub fn fps_display_detailed() -> String {
159 let stats = fps_stats();
160 format!(
161 "{:.0} FPS | {:.1}ms | recomp: {}/s",
162 stats.fps, stats.avg_ms, stats.recomps_per_second
163 )
164}