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 #[cfg(not(any(backend_metal, backend_dx, backend_vk)))]
41 note("backend", "none");
42}
43
44const MAX_NOTES: usize = 16;
45const MAX_NOTE_KEY_BYTES: usize = 32;
46const MAX_NOTE_VALUE_BYTES: usize = 128;
47
48static NOTES: Mutex<Vec<(String, String)>> = Mutex::new(Vec::new());
51
52pub fn note(key: &str, value: &str) {
56 let key = clamp(key, MAX_NOTE_KEY_BYTES);
57 let value = clamp(value, MAX_NOTE_VALUE_BYTES);
58 let mut notes = NOTES.lock().unwrap_or_else(|p| p.into_inner());
59 if let Some(slot) = notes.iter_mut().find(|(k, _)| *k == key) {
60 slot.1 = value;
61 } else if notes.len() < MAX_NOTES {
62 notes.push((key, value));
63 }
64}
65
66pub(crate) fn report_device_lost(detail: &str) {
70 let report = report::CrashReport::gather(report::ReportKind::DeviceLost, detail.to_string());
71 if write::emit(&report).is_none() {
72 tracing::warn!("crash report for device loss could not be written");
73 }
74}
75
76pub(crate) fn notes_snapshot() -> Vec<(String, String)> {
77 NOTES.lock().unwrap_or_else(|p| p.into_inner()).clone()
78}
79
80#[cfg(any(target_os = "macos", target_os = "windows"))]
82pub(crate) fn try_notes_snapshot() -> Vec<(String, String)> {
83 match NOTES.try_lock() {
84 Ok(notes) => notes.clone(),
85 Err(_) => Vec::new(),
86 }
87}
88
89fn clamp(s: &str, cap: usize) -> String {
90 let mut end = s.len().min(cap);
91 while !s.is_char_boundary(end) {
92 end -= 1;
93 }
94 s[..end].to_string()
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
104 fn notes_replace_by_key_and_stay_bounded() {
105 note("probe-key", "first");
106 note("probe-key", "second");
107 let snap = notes_snapshot();
108 assert_eq!(snap.iter().filter(|(k, _)| k == "probe-key").count(), 1);
109 assert!(snap.contains(&("probe-key".to_string(), "second".to_string())));
110
111 note("probe-long", &"v".repeat(1000));
112 let snap = notes_snapshot();
113 let long = snap.iter().find(|(k, _)| k == "probe-long").unwrap();
114 assert_eq!(long.1.len(), MAX_NOTE_VALUE_BYTES);
115
116 for i in 0..2 * MAX_NOTES {
117 note(&format!("probe-fill-{i}"), "x");
118 }
119 assert!(notes_snapshot().len() <= MAX_NOTES);
120 }
121}