cranpose_render_common/
debug_toggles.rs1use std::{
2 collections::BTreeMap,
3 ffi::{OsStr, OsString},
4 str::FromStr,
5 sync::{Mutex, OnceLock, PoisonError, atomic::Ordering},
6};
7
8use portable_atomic::AtomicU64;
9
10static GENERATION: AtomicU64 = AtomicU64::new(0);
11
12pub struct DebugToggle {
15 name: &'static str,
16 cached: Mutex<Option<(u64, Option<String>)>>,
17 absent_generation: AtomicU64,
18}
19
20impl DebugToggle {
21 pub const fn new(name: &'static str) -> Self {
22 Self {
23 name,
24 cached: Mutex::new(None),
25 absent_generation: AtomicU64::new(u64::MAX),
26 }
27 }
28
29 pub fn with<R>(&self, read: impl FnOnce(Option<&str>) -> R) -> R {
31 let generation = GENERATION.load(Ordering::Acquire);
32 if self.absent_generation.load(Ordering::Acquire) == generation {
33 return read(None);
34 }
35 let mut cached = self.cached.lock().unwrap_or_else(PoisonError::into_inner);
36 if cached.as_ref().is_none_or(|(seen, _)| *seen != generation) {
37 *cached = Some((generation, debug_toggle(self.name)));
38 }
39 let value = cached.as_ref().and_then(|(_, value)| value.as_deref());
40 if value.is_none() {
41 self.absent_generation.store(generation, Ordering::Release);
42 }
43 read(value)
44 }
45
46 pub fn is_set(&self) -> bool {
48 self.with(|value| value.is_some())
49 }
50
51 pub fn flag(&self) -> bool {
53 self.with(|value| matches!(value, Some("1" | "true" | "yes")))
54 }
55
56 pub fn equals(&self, expected: &str) -> bool {
58 self.with(|value| value == Some(expected))
59 }
60
61 pub fn parse<T: FromStr>(&self) -> Option<T> {
63 self.with(|value| value.and_then(|value| value.parse().ok()))
64 }
65}
66
67fn overrides() -> &'static Mutex<BTreeMap<&'static str, OsString>> {
68 static OVERRIDES: OnceLock<Mutex<BTreeMap<&'static str, OsString>>> = OnceLock::new();
69 OVERRIDES.get_or_init(|| Mutex::new(BTreeMap::new()))
70}
71
72#[doc(hidden)]
73pub fn debug_toggle(name: &'static str) -> Option<String> {
74 let map = overrides().lock().unwrap_or_else(PoisonError::into_inner);
75 if let Some(value) = map.get(name) {
76 return value.to_str().map(str::to_owned);
77 }
78 drop(map);
79 std::env::var(name).ok()
80}
81
82#[doc(hidden)]
83pub fn debug_toggle_os(name: &'static str) -> Option<OsString> {
84 let map = overrides().lock().unwrap_or_else(PoisonError::into_inner);
85 if let Some(value) = map.get(name) {
86 return Some(value.clone());
87 }
88 drop(map);
89 std::env::var_os(name)
90}
91
92#[doc(hidden)]
93pub fn set_debug_toggle(name: &'static str, value: Option<&str>) {
94 set_debug_toggle_os(name, value.map(OsStr::new));
95}
96
97#[doc(hidden)]
98pub fn set_debug_toggle_os(name: &'static str, value: Option<&OsStr>) {
99 let mut map = overrides().lock().unwrap_or_else(PoisonError::into_inner);
100 match value {
101 Some(value) => map.insert(name, value.to_owned()),
102 None => map.remove(name),
103 };
104 GENERATION.fetch_add(1, Ordering::Release);
105}
106
107#[cfg(all(test, unix))]
108#[path = "tests/debug_toggles_tests.rs"]
109mod tests;