1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//! Cached reads of the `CRANPOSE_*` diagnostic environment variables.
//!
//! Every diagnostic switch in the framework is read from the environment, and
//! several of them sit on paths that run once per frame, once per state write,
//! or once per laid-out node. That is a problem, because reading an
//! environment variable is not a cheap lookup: `getenv` takes the environ
//! lock and walks the whole variable array, comparing each entry's name
//! prefix, until it finds a match or runs off the end. A switch that is
//! *turned off* is the worst case — it never matches, so every read pays for
//! a full scan of the process environment just to learn there is nothing to
//! print. On a device that showed up as a measurable share of an idle frame,
//! spent entirely inside `strncmp`.
//!
//! None of these switches can change after startup. Android's are seeded from
//! `debug.cranpose.*` system properties before the app shell exists, and on
//! every other platform they come from the process environment, which the
//! framework never mutates. So each one is read once and cached.
//!
//! Use [`env_flag!`] for presence switches and [`env_threshold_ms!`] for the
//! millisecond thresholds. Both expand to a `OnceLock` owned by the call site,
//! which costs an acquire load once warm.
/// Reads a presence-style environment switch once and caches the answer.
///
/// Expands to a `bool` that is `true` when the variable is set to anything at
/// all, including the empty string. The backing `OnceLock` belongs to the
/// expansion, so two call sites naming the same variable get their own cache
/// rather than sharing one; that keeps the macro usable from any crate without
/// a registry, at the cost of one pointer-sized static each.
///
/// ```ignore
/// if env_flag!("CRANPOSE_SCENE_UPDATE_DIAG") {
/// eprintln!("[scene-update-diag] dirty={dirty_nodes:?}");
/// }
/// ```
/// Reads a millisecond-threshold environment variable once and caches it.
///
/// Expands to an `Option<f64>`: `None` when the variable is unset or does not
/// parse, `Some(threshold)` otherwise. A threshold of `0` is preserved rather
/// than treated as absent, so `0` means "report every frame" — the setting
/// used to capture a full trace.
///
/// ```ignore
/// if let Some(threshold) = env_threshold_ms!("CRANPOSE_FRAME_STAGE_TELEMETRY_MS") {
/// if elapsed_ms >= threshold {
/// log::info!("[frame-stage] {elapsed_ms:.2}ms");
/// }
/// }
/// ```