fret_runtime/
runner_surface_config_diagnostics.rs1use std::collections::HashMap;
2use std::sync::{Arc, Mutex};
3
4use fret_core::{
5 AppWindowId, FrameId,
6 time::{SystemTime, UNIX_EPOCH},
7};
8
9#[derive(Debug, Clone, PartialEq, Eq, Default)]
10pub struct RunnerSurfaceConfigWindowSnapshot {
11 pub width_px: u32,
12 pub height_px: u32,
13 pub format: String,
14 pub present_mode: String,
15 pub desired_maximum_frame_latency: u32,
16 pub alpha_mode: String,
17 pub configure_count: u64,
18 pub last_configure_frame_id: u64,
19 pub last_configure_unix_ms: Option<u64>,
20}
21
22#[derive(Debug, Default)]
23struct RunnerSurfaceConfigDiagnosticsState {
24 windows: HashMap<AppWindowId, RunnerSurfaceConfigWindowSnapshot>,
25}
26
27#[derive(Debug, Clone, Default)]
28pub struct RunnerSurfaceConfigDiagnosticsStore {
29 inner: Arc<Mutex<RunnerSurfaceConfigDiagnosticsState>>,
30}
31
32impl RunnerSurfaceConfigDiagnosticsStore {
33 #[allow(clippy::too_many_arguments)]
34 pub fn record_config(
35 &self,
36 window: AppWindowId,
37 frame_id: FrameId,
38 width_px: u32,
39 height_px: u32,
40 format: impl Into<String>,
41 present_mode: impl Into<String>,
42 desired_maximum_frame_latency: u32,
43 alpha_mode: impl Into<String>,
44 ) {
45 let mut state = self.inner.lock().unwrap_or_else(|err| err.into_inner());
46 let entry = state.windows.entry(window).or_default();
47 entry.width_px = width_px;
48 entry.height_px = height_px;
49 entry.format = format.into();
50 entry.present_mode = present_mode.into();
51 entry.desired_maximum_frame_latency = desired_maximum_frame_latency;
52 entry.alpha_mode = alpha_mode.into();
53 entry.configure_count = entry.configure_count.saturating_add(1);
54 entry.last_configure_frame_id = frame_id.0;
55 entry.last_configure_unix_ms = Some(unix_ms_now());
56 }
57
58 pub fn window_snapshot(
59 &self,
60 window: AppWindowId,
61 ) -> Option<RunnerSurfaceConfigWindowSnapshot> {
62 self.inner
63 .lock()
64 .unwrap_or_else(|err| err.into_inner())
65 .windows
66 .get(&window)
67 .cloned()
68 }
69
70 pub fn clear_window(&self, window: AppWindowId) -> Option<RunnerSurfaceConfigWindowSnapshot> {
71 self.inner
72 .lock()
73 .unwrap_or_else(|err| err.into_inner())
74 .windows
75 .remove(&window)
76 }
77}
78
79fn unix_ms_now() -> u64 {
80 SystemTime::now()
81 .duration_since(UNIX_EPOCH)
82 .map(|duration| duration.as_millis() as u64)
83 .unwrap_or(0)
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use slotmap::KeyData;
90
91 #[test]
92 fn record_config_updates_window_snapshot() {
93 let store = RunnerSurfaceConfigDiagnosticsStore::default();
94 let window = AppWindowId::from(KeyData::from_ffi(7));
95
96 store.record_config(
97 window,
98 FrameId(11),
99 1000,
100 800,
101 "Bgra8UnormSrgb",
102 "Fifo",
103 2,
104 "Opaque",
105 );
106 store.record_config(
107 window,
108 FrameId(12),
109 1200,
110 900,
111 "Bgra8UnormSrgb",
112 "Mailbox",
113 3,
114 "PreMultiplied",
115 );
116
117 let snapshot = store.window_snapshot(window).expect("surface snapshot");
118 assert_eq!(snapshot.width_px, 1200);
119 assert_eq!(snapshot.height_px, 900);
120 assert_eq!(snapshot.format, "Bgra8UnormSrgb");
121 assert_eq!(snapshot.present_mode, "Mailbox");
122 assert_eq!(snapshot.desired_maximum_frame_latency, 3);
123 assert_eq!(snapshot.alpha_mode, "PreMultiplied");
124 assert_eq!(snapshot.configure_count, 2);
125 assert_eq!(snapshot.last_configure_frame_id, 12);
126 assert!(snapshot.last_configure_unix_ms.is_some());
127 }
128
129 #[test]
130 fn clear_window_removes_snapshot() {
131 let store = RunnerSurfaceConfigDiagnosticsStore::default();
132 let window = AppWindowId::from(KeyData::from_ffi(9));
133 store.record_config(window, FrameId(1), 1, 1, "fmt", "mode", 2, "alpha");
134 assert!(store.window_snapshot(window).is_some());
135 let removed = store.clear_window(window).expect("removed snapshot");
136 assert_eq!(removed.configure_count, 1);
137 assert!(store.window_snapshot(window).is_none());
138 }
139}