Skip to main content

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}