Skip to main content

fret_runtime/
runner_surface_config_diagnostics.rs

1use 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}