concinnity_engine/crash/
mod.rs1mod hook;
12mod memory;
13mod report;
14mod ring;
15mod write;
16
17#[cfg(any(target_os = "macos", target_os = "windows"))]
18mod minidump;
19#[cfg(any(target_os = "macos", target_os = "windows"))]
20mod native;
21
22pub use ring::RingLayer;
23
24use std::sync::Mutex;
25
26pub fn install() {
31 hook::install();
32 #[cfg(any(target_os = "macos", target_os = "windows"))]
33 native::install();
34 #[cfg(backend_metal)]
35 note("backend", "metal");
36 #[cfg(backend_dx)]
37 note("backend", "directx");
38 #[cfg(backend_vk)]
39 note("backend", "vulkan");
40}
41
42const MAX_NOTES: usize = 16;
43const MAX_NOTE_KEY_BYTES: usize = 32;
44const MAX_NOTE_VALUE_BYTES: usize = 128;
45
46static NOTES: Mutex<Vec<(String, String)>> = Mutex::new(Vec::new());
49
50pub fn note(key: &str, value: &str) {
54 let key = clamp(key, MAX_NOTE_KEY_BYTES);
55 let value = clamp(value, MAX_NOTE_VALUE_BYTES);
56 let mut notes = NOTES.lock().unwrap_or_else(|p| p.into_inner());
57 if let Some(slot) = notes.iter_mut().find(|(k, _)| *k == key) {
58 slot.1 = value;
59 } else if notes.len() < MAX_NOTES {
60 notes.push((key, value));
61 }
62}
63
64pub(crate) fn report_device_lost(detail: &str) {
68 let report = report::CrashReport::gather(report::ReportKind::DeviceLost, detail.to_string());
69 if write::emit(&report).is_none() {
70 tracing::warn!("crash report for device loss could not be written");
71 }
72}
73
74pub(crate) fn notes_snapshot() -> Vec<(String, String)> {
75 NOTES.lock().unwrap_or_else(|p| p.into_inner()).clone()
76}
77
78#[cfg(any(target_os = "macos", target_os = "windows"))]
80pub(crate) fn try_notes_snapshot() -> Vec<(String, String)> {
81 match NOTES.try_lock() {
82 Ok(notes) => notes.clone(),
83 Err(_) => Vec::new(),
84 }
85}
86
87fn clamp(s: &str, cap: usize) -> String {
88 let mut end = s.len().min(cap);
89 while !s.is_char_boundary(end) {
90 end -= 1;
91 }
92 s[..end].to_string()
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 #[test]
102 fn notes_replace_by_key_and_stay_bounded() {
103 note("probe-key", "first");
104 note("probe-key", "second");
105 let snap = notes_snapshot();
106 assert_eq!(snap.iter().filter(|(k, _)| k == "probe-key").count(), 1);
107 assert!(snap.contains(&("probe-key".to_string(), "second".to_string())));
108
109 note("probe-long", &"v".repeat(1000));
110 let snap = notes_snapshot();
111 let long = snap.iter().find(|(k, _)| k == "probe-long").unwrap();
112 assert_eq!(long.1.len(), MAX_NOTE_VALUE_BYTES);
113
114 for i in 0..2 * MAX_NOTES {
115 note(&format!("probe-fill-{i}"), "x");
116 }
117 assert!(notes_snapshot().len() <= MAX_NOTES);
118 }
119}