mod hook;
mod memory;
mod report;
mod ring;
mod write;
#[cfg(any(target_os = "macos", target_os = "windows"))]
mod minidump;
#[cfg(any(target_os = "macos", target_os = "windows"))]
mod native;
pub use ring::RingLayer;
use std::sync::Mutex;
pub fn install() {
hook::install();
#[cfg(any(target_os = "macos", target_os = "windows"))]
native::install();
#[cfg(backend_metal)]
note("backend", "metal");
#[cfg(backend_dx)]
note("backend", "directx");
#[cfg(backend_vk)]
note("backend", "vulkan");
#[cfg(not(any(backend_metal, backend_dx, backend_vk)))]
note("backend", "none");
}
const MAX_NOTES: usize = 16;
const MAX_NOTE_KEY_BYTES: usize = 32;
const MAX_NOTE_VALUE_BYTES: usize = 128;
static NOTES: Mutex<Vec<(String, String)>> = Mutex::new(Vec::new());
pub fn note(key: &str, value: &str) {
let key = clamp(key, MAX_NOTE_KEY_BYTES);
let value = clamp(value, MAX_NOTE_VALUE_BYTES);
let mut notes = NOTES.lock().unwrap_or_else(|p| p.into_inner());
if let Some(slot) = notes.iter_mut().find(|(k, _)| *k == key) {
slot.1 = value;
} else if notes.len() < MAX_NOTES {
notes.push((key, value));
}
}
pub(crate) fn report_device_lost(detail: &str) {
let report = report::CrashReport::gather(report::ReportKind::DeviceLost, detail.to_string());
if write::emit(&report).is_none() {
tracing::warn!("crash report for device loss could not be written");
}
}
pub(crate) fn notes_snapshot() -> Vec<(String, String)> {
NOTES.lock().unwrap_or_else(|p| p.into_inner()).clone()
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
pub(crate) fn try_notes_snapshot() -> Vec<(String, String)> {
match NOTES.try_lock() {
Ok(notes) => notes.clone(),
Err(_) => Vec::new(),
}
}
fn clamp(s: &str, cap: usize) -> String {
let mut end = s.len().min(cap);
while !s.is_char_boundary(end) {
end -= 1;
}
s[..end].to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn notes_replace_by_key_and_stay_bounded() {
note("probe-key", "first");
note("probe-key", "second");
let snap = notes_snapshot();
assert_eq!(snap.iter().filter(|(k, _)| k == "probe-key").count(), 1);
assert!(snap.contains(&("probe-key".to_string(), "second".to_string())));
note("probe-long", &"v".repeat(1000));
let snap = notes_snapshot();
let long = snap.iter().find(|(k, _)| k == "probe-long").unwrap();
assert_eq!(long.1.len(), MAX_NOTE_VALUE_BYTES);
for i in 0..2 * MAX_NOTES {
note(&format!("probe-fill-{i}"), "x");
}
assert!(notes_snapshot().len() <= MAX_NOTES);
}
}