concinnity_memory/tag.rs
1// concinnity-memory/src/tag.rs
2//
3// The vocabulary subsystems account against: what a block of memory is for, and
4// which memory it sits in.
5//
6// It is deliberately a small closed set rather than an open string registry. A
7// fixed set indexes straight into a flat array of counters, which is what lets
8// the ledger stay allocation-free and readable from a global allocator's
9// neighbourhood; it also keeps a readout's rows stable frame to frame instead of
10// appearing and reordering as strings are interned.
11
12/// Which memory a report is about. The two are counted separately because they
13/// are separately budgeted and separately exhausted: a host allocation and a
14/// device allocation for the same texture are two different costs.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum Realm {
17 /// Process memory: what the CPU allocates and the Rust heap holds.
18 Host,
19 /// Device memory: what a GPU backend allocates, whether that is discrete
20 /// VRAM or a unified-memory working set.
21 Device,
22}
23
24impl Realm {
25 /// Number of realms.
26 pub const COUNT: usize = 2;
27 /// Every realm, in readout order.
28 pub const ALL: [Realm; Self::COUNT] = [Realm::Host, Realm::Device];
29
30 /// The realm's position in a per-realm table.
31 pub const fn index(self) -> usize {
32 self as usize
33 }
34
35 /// How a readout names the realm.
36 pub const fn name(self) -> &'static str {
37 match self {
38 Realm::Host => "RAM",
39 Realm::Device => "VRAM",
40 }
41 }
42}
43
44/// What a block of memory is for. `Other` is the honest bucket for a reporter
45/// that has no better answer; it is not a catch-all for everything unreported,
46/// since the ledger only ever holds what someone reports into it.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
48pub enum MemTag {
49 /// Texture images.
50 Textures,
51 /// Mesh geometry.
52 Meshes,
53 /// Streamed world chunks.
54 Chunks,
55 /// Compiled shader binaries and pipeline state.
56 Shaders,
57 /// Decoded audio clips and mixer buffers.
58 Audio,
59 /// Physics bodies, colliders, and broad-phase structures.
60 Physics,
61 /// Overlay and HUD geometry.
62 Ui,
63 /// Per-frame working memory: arenas and pools that are reset or reused
64 /// rather than freed.
65 Scratch,
66 /// Anything with no better bucket.
67 Other,
68}
69
70impl MemTag {
71 /// Number of tags.
72 pub const COUNT: usize = 9;
73 /// Every tag, in the order a readout lists them. Fixed, so rows never
74 /// reorder under a reader as the numbers move.
75 pub const ALL: [MemTag; Self::COUNT] = [
76 MemTag::Textures,
77 MemTag::Meshes,
78 MemTag::Chunks,
79 MemTag::Shaders,
80 MemTag::Audio,
81 MemTag::Physics,
82 MemTag::Ui,
83 MemTag::Scratch,
84 MemTag::Other,
85 ];
86
87 /// The tag's position in a per-tag table.
88 pub const fn index(self) -> usize {
89 self as usize
90 }
91
92 /// How a readout names the tag.
93 pub const fn name(self) -> &'static str {
94 match self {
95 MemTag::Textures => "Textures",
96 MemTag::Meshes => "Meshes",
97 MemTag::Chunks => "Chunks",
98 MemTag::Shaders => "Shaders",
99 MemTag::Audio => "Audio",
100 MemTag::Physics => "Physics",
101 MemTag::Ui => "UI",
102 MemTag::Scratch => "Scratch",
103 MemTag::Other => "Other",
104 }
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 // `index` is what selects a counter out of the ledger's flat array, so the
113 // discriminants must cover 0..COUNT exactly once.
114 #[test]
115 fn tag_indices_are_dense_and_match_their_position() {
116 assert_eq!(MemTag::ALL.len(), MemTag::COUNT);
117 for (i, tag) in MemTag::ALL.iter().enumerate() {
118 assert_eq!(tag.index(), i);
119 }
120 }
121
122 #[test]
123 fn realm_indices_are_dense_and_match_their_position() {
124 assert_eq!(Realm::ALL.len(), Realm::COUNT);
125 for (i, realm) in Realm::ALL.iter().enumerate() {
126 assert_eq!(realm.index(), i);
127 }
128 }
129
130 #[test]
131 fn every_tag_names_itself_distinctly() {
132 for (i, a) in MemTag::ALL.iter().enumerate() {
133 assert!(!a.name().is_empty());
134 for b in &MemTag::ALL[i + 1..] {
135 assert_ne!(a.name(), b.name());
136 }
137 }
138 }
139}