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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//! Build-mode vocabulary: which diagnostics a build compiles in.
//!
//! retroglyph emits diagnostics that exist purely to shorten the debugging loop: a warning that
//! a sprite is bigger than the cells reserved for it, a warning that a tint was set on a cell
//! that resolved to a font glyph rather than a sprite. Each one costs something to produce (a
//! formatted message, and usually a side table so a 60fps redraw loop reports each offender once
//! instead of every frame), and none of it is worth anything in a shipped game, where nobody is
//! reading the log.
//!
//! [`BuildMode::CURRENT`] names which kind of build this is, and [`dev_only!`] gates a block on
//! it. In a release build the const is `false`, the branch folds away, and everything inside it
//! (message strings, the bookkeeping that dedupes them) is dropped as dead code.
//!
//! ```
//! use retroglyph_core::dev_only;
//!
//! # fn report(_: &str, _: usize) {}
//! # let cache_misses = 3;
//! dev_only!({
//! if cache_misses > 0 {
//! // Costs nothing in a release build: neither the check nor the message survives.
//! report("glyphs missed the sprite cache", cache_misses);
//! }
//! });
//! ```
//!
//! # Two modes, not three
//!
//! Engines that own their whole toolchain usually expose three build modes. Flutter's
//! `debug`/`profile`/`release` is the clearest version: `debug` is unoptimized with every
//! assertion live, `profile` is optimized but keeps enough instrumentation to attribute a frame
//! budget, and `release` is what ships.
//!
//! Cargo has no `profile` mode in that sense. A profiling build is a release build that keeps
//! debug symbols (`[profile.profiling] inherits = "release"`, plus `debug = true`), and it is
//! *supposed* to be one: measuring a build whose diagnostics differ from the shipped build
//! measures the wrong program. So there are two modes here, and a profiling build resolves to
//! [`Release`](BuildMode::Release).
//!
//! That is also why the gate is written as "is this a dev build" rather than "is this not a
//! release build". Flutter's own guidance on its `kReleaseMode` constant is to prefer `kDebugMode`
//! or `assert` precisely because gating on *not release* is what makes a profile build behave
//! unlike the release build it is meant to predict.
//!
//! # How a mode is chosen
//!
//! | Build | [`BuildMode::CURRENT`] |
//! | --- | --- |
//! | `cargo build`, `cargo test`, `cargo run` | [`Dev`](BuildMode::Dev) |
//! | `cargo build --release` | [`Release`](BuildMode::Release) |
//! | a profiling profile inheriting `release` | [`Release`](BuildMode::Release) |
//! | any build with the `dev` feature on | [`Dev`](BuildMode::Dev) |
//! | any build with `-C debug-assertions=on` | [`Dev`](BuildMode::Dev) |
//!
//! The default signal is `debug_assertions`, which Cargo turns on for the `dev` profile and off
//! for `release`. It follows whichever profile the consumer built with, so a game gets
//! diagnostics from `cargo run` and none from `cargo run --release` without configuring anything.
//!
//! The `dev` feature forces [`Dev`](BuildMode::Dev) on regardless, for an optimized build that
//! still reports. This is the equivalent of Unity's "Development Build" checkbox or Bevy's `dev`
//! feature: release codegen, because an unoptimized build of a renderer is too slow to reproduce
//! anything frame-dependent, but with the instrumentation left in.
//!
//! # Turning diagnostics off in a dev build
//!
//! There is deliberately no feature for this. Cargo features are additive, so a `no-dev` feature
//! would be silently defeated by any other crate in the graph that wanted diagnostics.
//!
//! Every diagnostic in this workspace goes through the `log` crate, so the two working controls
//! are the consumer's own log filter at runtime, and `log`'s `max_level_*` /
//! `release_max_level_*` features, which drop the calls at compile time. Those cut deeper than
//! this module does: they apply to every `log` user in the graph, not just retroglyph.
/// Which diagnostics this build compiles in.
///
/// Read [`CURRENT`](Self::CURRENT) for this build's mode, or use [`dev_only!`](crate::dev_only)
/// to gate a block on it. See the [module docs](self) for how a mode is chosen and why there are
/// two of them rather than three.
/// Whether this build compiles in development diagnostics: [`BuildMode::CURRENT`] as a `bool`.
///
/// Prefer [`dev_only!`](crate::dev_only) for gating a block. Reach for this constant directly
/// when the shape of the code makes a macro awkward, such as an early return or a struct field
/// that only one mode populates.
pub const DEV: bool = CURRENT.is_dev;
/// Runs `body` only in a build that compiles in development diagnostics.
///
/// Expands to `if DEV { body }`. Because [`DEV`] is a `const`, a release build folds the branch
/// away and drops `body` with it, including any message strings and bookkeeping it alone
/// references.
///
/// `body` is type-checked in every mode. That is the point: a diagnostic that only compiles on
/// one profile rots, and the rot surfaces as a broken release build. The cost is that `body` may
/// not reference items that themselves exist only in a dev build.
///
/// # Examples
///
/// ```
/// use retroglyph_core::dev_only;
///
/// # fn warn_overflow(_: (u32, u32), _: (u32, u32)) {}
/// let sprite_px = (32, 32);
/// let cell_px = (16, 16);
///
/// dev_only!({
/// if sprite_px > cell_px {
/// warn_overflow(sprite_px, cell_px);
/// }
/// });
/// ```
///
/// The block form is not required; any statements work.
///
/// ```
/// # use retroglyph_core::dev_only;
/// # let mut misses = 0;
/// dev_only!(misses += 1;);
/// ```