glitcher_core/
registry.rs

1use std::collections::HashMap;
2use std::marker::PhantomData;
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6pub enum RegistryError {
7    #[error("Registry capacity exceeded")]
8    CapacityExceeded,
9    #[error("Handler '{0}' already exists")]
10    AlreadyExists(String),
11    #[error("Slot generation exhausted")]
12    SlotExhausted,
13    #[error("Memory allocation failed: {0}")]
14    AllocationFailed(String),
15}
16
17/// A lightweight, copyable handle acting as a safe pointer.
18pub struct HandlerId<T> {
19    pub(crate) index: u32,
20    pub(crate) generation: u32,
21    pub(crate) _marker: PhantomData<T>,
22}
23
24// Manual implementations to avoid T: Trait bounds
25impl<T> Clone for HandlerId<T> {
26    fn clone(&self) -> Self {
27        *self
28    }
29}
30
31impl<T> Copy for HandlerId<T> {}
32
33impl<T> PartialEq for HandlerId<T> {
34    fn eq(&self, other: &Self) -> bool {
35        self.index == other.index && self.generation == other.generation
36    }
37}
38
39impl<T> Eq for HandlerId<T> {}
40
41impl<T> std::hash::Hash for HandlerId<T> {
42    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
43        self.index.hash(state);
44        self.generation.hash(state);
45    }
46}
47
48impl<T> std::fmt::Debug for HandlerId<T> {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        f.debug_struct("HandlerId")
51            .field("index", &self.index)
52            .field("generation", &self.generation)
53            .finish()
54    }
55}
56
57struct Slot<T> {
58    item: Option<T>,
59    generation: u32,
60    name: Option<String>,
61}
62
63/// A strict, capacity-limited registry that separates Cold Path (registration)
64/// from Hot Path (access) to ensure consistent performance.
65pub struct Registry<T> {
66    slots: Vec<Slot<T>>,
67    free_indices: Vec<usize>,
68    name_map: HashMap<String, usize>,
69    capacity: usize,
70}
71
72impl<T> Registry<T> {
73    /// Creates a new registry with a fixed capacity.
74    /// Pre-allocates all necessary memory to avoid runtime allocations.
75    pub fn new(capacity: usize) -> Result<Self, RegistryError> {
76        let mut slots = Vec::new();
77        if let Err(e) = slots.try_reserve(capacity) {
78            return Err(RegistryError::AllocationFailed(e.to_string()));
79        }
80
81        // Initialize slots with empty state to avoid runtime resizing
82        for _ in 0..capacity {
83            slots.push(Slot {
84                item: None,
85                generation: 0,
86                name: None,
87            });
88        }
89
90        let mut free_indices = Vec::new();
91        if let Err(e) = free_indices.try_reserve(capacity) {
92            return Err(RegistryError::AllocationFailed(e.to_string()));
93        }
94
95        // Fill free indices in reverse order so we use index 0 first
96        for i in (0..capacity).rev() {
97            free_indices.push(i);
98        }
99
100        let mut name_map = HashMap::new();
101        if let Err(e) = name_map.try_reserve(capacity) {
102            return Err(RegistryError::AllocationFailed(e.to_string()));
103        }
104
105        Ok(Self {
106            slots,
107            free_indices,
108            name_map,
109            capacity,
110        })
111    }
112
113    /// [Cold Path] Registers an item with a string ID.
114    /// Fails if capacity is exceeded or name already exists.
115    pub fn register(
116        &mut self,
117        name: impl Into<String>,
118        item: T,
119    ) -> Result<HandlerId<T>, RegistryError> {
120        let name = name.into();
121        if self.name_map.contains_key(&name) {
122            return Err(RegistryError::AlreadyExists(name));
123        }
124
125        // Pop a free index. If empty, we are at capacity.
126        let index = self
127            .free_indices
128            .pop()
129            .ok_or(RegistryError::CapacityExceeded)?;
130
131        let slot = &mut self.slots[index];
132
133        // Safety Check: Generation overflow
134        if slot.generation == u32::MAX {
135            return Err(RegistryError::SlotExhausted);
136        }
137
138        slot.item = Some(item);
139        slot.name = Some(name.clone());
140
141        // Map name -> index
142        self.name_map.insert(name, index);
143
144        Ok(HandlerId {
145            index: index as u32,
146            generation: slot.generation,
147            _marker: PhantomData,
148        })
149    }
150
151    /// [Hot Path] Access item by ID (No allocation, O(1))
152    #[inline]
153    pub fn get(&self, id: HandlerId<T>) -> Option<&T> {
154        if let Some(slot) = self.slots.get(id.index as usize) {
155            if slot.generation == id.generation {
156                return slot.item.as_ref();
157            }
158        }
159        None
160    }
161
162    /// [Hot Path] Mutable access item by ID (No allocation, O(1))
163    #[inline]
164    pub fn get_mut(&mut self, id: HandlerId<T>) -> Option<&mut T> {
165        if let Some(slot) = self.slots.get_mut(id.index as usize) {
166            if slot.generation == id.generation {
167                return slot.item.as_mut();
168            }
169        }
170        None
171    }
172
173    /// [Cold Path] Look up a handler ID by name
174    pub fn lookup(&self, name: &str) -> Option<HandlerId<T>> {
175        if let Some(&index) = self.name_map.get(name) {
176            if let Some(slot) = self.slots.get(index) {
177                // Validity check, though name_map should generally be consistent
178                if slot.item.is_some() {
179                    return Some(HandlerId {
180                        index: index as u32,
181                        generation: slot.generation,
182                        _marker: PhantomData,
183                    });
184                }
185            }
186        }
187        None
188    }
189
190    /// [Cold Path] Remove an item by ID.
191    /// Updates generation to invalidate old handles and cleans up name map.
192    pub fn remove(&mut self, id: HandlerId<T>) -> Option<T> {
193        if let Some(slot) = self.slots.get_mut(id.index as usize) {
194            if slot.generation == id.generation {
195                // 1. Increment generation to invalidate old handles
196                slot.generation = slot.generation.wrapping_add(1);
197
198                // 2. Remove item
199                let item = slot.item.take();
200
201                // 3. Clean up name map using stored name
202                if let Some(name) = slot.name.take() {
203                    self.name_map.remove(&name);
204                }
205
206                // 4. Return index to free list
207                self.free_indices.push(id.index as usize);
208
209                return item;
210            }
211        }
212        None
213    }
214
215    /// Returns the fixed capacity of the registry.
216    pub fn capacity(&self) -> usize {
217        self.capacity
218    }
219
220    /// Returns the current number of items.
221    pub fn count(&self) -> usize {
222        self.capacity - self.free_indices.len()
223    }
224
225    /// Returns an iterator over all registered names.
226    pub fn keys(&self) -> impl Iterator<Item = &String> {
227        self.name_map.keys()
228    }
229
230    /// Returns an iterator over all registered items with their names.
231    pub fn iter(&self) -> impl Iterator<Item = (&String, &T)> {
232        self.slots.iter().filter_map(|slot| {
233            if let (Some(name), Some(item)) = (&slot.name, &slot.item) {
234                Some((name, item))
235            } else {
236                None
237            }
238        })
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn test_capacity_limit() {
248        let mut registry = Registry::<i32>::new(2).unwrap();
249
250        assert!(registry.register("a", 1).is_ok());
251        assert!(registry.register("b", 2).is_ok());
252
253        let result = registry.register("c", 3);
254        assert!(matches!(result, Err(RegistryError::CapacityExceeded)));
255    }
256
257    #[test]
258    fn test_access_by_id() {
259        let mut registry = Registry::<String>::new(10).unwrap();
260        let id = registry.register("test", "Hello".to_string()).unwrap();
261
262        assert_eq!(registry.get(id).unwrap(), "Hello");
263        assert_eq!(registry.get_mut(id).unwrap(), "Hello");
264
265        *registry.get_mut(id).unwrap() = "World".to_string();
266        assert_eq!(registry.get(id).unwrap(), "World");
267    }
268
269    #[test]
270    fn test_removal_and_generation() {
271        let mut registry = Registry::<i32>::new(10).unwrap();
272        let id1 = registry.register("item", 100).unwrap();
273
274        // Remove it
275        let val = registry.remove(id1);
276        assert_eq!(val, Some(100));
277
278        // Old ID should be invalid
279        assert!(registry.get(id1).is_none());
280
281        // Register new item (likely reuses same slot)
282        let id2 = registry.register("item2", 200).unwrap();
283
284        // If it reused the slot (index 0), indices match but generations differ
285        if id1.index == id2.index {
286            assert_ne!(id1.generation, id2.generation);
287        }
288
289        // Old ID still invalid for new item
290        assert!(registry.get(id1).is_none());
291        assert_eq!(registry.get(id2).unwrap(), &200);
292    }
293
294    #[test]
295    fn test_name_map_consistency() {
296        let mut registry = Registry::<i32>::new(10).unwrap();
297        let id = registry.register("my_item", 123).unwrap();
298
299        // Lookup works
300        let looked_up_id = registry.lookup("my_item").unwrap();
301        assert_eq!(id, looked_up_id);
302
303        // Remove by ID
304        registry.remove(id);
305
306        // Lookup should fail
307        assert!(registry.lookup("my_item").is_none());
308
309        // Register with same name should work now
310        assert!(registry.register("my_item", 456).is_ok());
311    }
312
313    #[test]
314    fn test_duplicate_name() {
315        let mut registry = Registry::<i32>::new(10).unwrap();
316        registry.register("dup", 1).unwrap();
317
318        let result = registry.register("dup", 2);
319        assert!(matches!(result, Err(RegistryError::AlreadyExists(_))));
320    }
321}