hyperion/resources.rs
1use std::any::{Any, TypeId};
2use std::collections::HashMap;
3
4/// A generational handle used to identify a resource stored in a `ResourceRegistry`.
5///
6/// # Fields
7/// - `id`: A unique identifier assigned when the resource is registered.
8/// - `version`: A generation number used to validate the handle's freshness.
9///
10/// # Safety
11/// - Handles become invalid if the associated resource is removed (version is incremented).
12#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
13pub struct ResourceHandle {
14 id: u64,
15 version: u32,
16}
17
18/// A type-erased registry for storing arbitrary resources using generational handles.
19///
20/// # Fields
21/// - `next_id`: Next available numeric id for newly registered resources.
22/// - `versions`: Per-id version counter (generation) for handle validation.
23/// - `stores`: Map of `TypeId` -> boxed `Vec<T>` type-erased.
24/// - `ids`: Map of id -> (TypeId, index in its store).
25/// - `reverse_ids`: Reverse map of (TypeId, index) -> id.
26/// - `unique_resources`: Map of `TypeId` -> unique resource instance.
27///
28/// # Safety
29/// - All type-erased accesses are guarded by `TypeId` checks and handle version
30/// validation to prevent type confusion and use-after-free via stale handles.
31pub struct ResourceRegistry {
32 next_id: u64,
33 versions: HashMap<u64, u32>,
34 stores: HashMap<TypeId, Box<dyn Any>>,
35 ids: HashMap<u64, (TypeId, usize)>,
36 reverse_ids: HashMap<(TypeId, usize), u64>,
37 unique_resources: HashMap<TypeId, Box<dyn Any>>,
38}
39
40impl ResourceRegistry {
41 /// Creates a new, empty `ResourceRegistry`.
42 pub fn new() -> Self {
43 Self {
44 next_id: 0,
45 versions: HashMap::new(),
46 stores: HashMap::new(),
47 ids: HashMap::new(),
48 reverse_ids: HashMap::new(),
49 unique_resources: HashMap::new(),
50 }
51 }
52
53 /// Registers or overwrites the unique resource of type `T`.
54 ///
55 /// If a resource of this type already exists, it will be replaced.
56 ///
57 /// # Type Parameters
58 /// - `T`: The type of the resource. Must be `'static`.
59 ///
60 /// # Parameters
61 /// - `resource`: The resource instance to store as a unique singleton.
62 pub fn register_unique<T: 'static>(&mut self, resource: T) {
63 let tid = TypeId::of::<T>();
64 self.unique_resources.insert(tid, Box::new(resource));
65 }
66
67 /// Gets an immutable reference to the unique resource of type `T`, if it exists.
68 ///
69 /// # Type Parameters
70 /// - `T`: The type of the resource requested.
71 ///
72 /// # Returns
73 /// - `Option<&T>`: Some reference to the resource if registered; None if not found.
74 pub fn get_unique<T: 'static>(&self) -> Option<&T> {
75 let tid = TypeId::of::<T>();
76 self.unique_resources.get(&tid)?.downcast_ref::<T>()
77 }
78
79 /// Gets a mutable reference to the unique resource of type `T`, if it exists.
80 ///
81 /// # Type Parameters
82 /// - `T`: The type of the resource requested.
83 ///
84 /// # Returns
85 /// - `Option<&mut T>`: Some mutable reference to the resource if registered; None if not found.
86 pub fn get_unique_mut<T: 'static>(&mut self) -> Option<&mut T> {
87 let tid = TypeId::of::<T>();
88 self.unique_resources.get_mut(&tid)?.downcast_mut::<T>()
89 }
90
91 /// Removes the unique resource of type `T` from storage and returns it.
92 ///
93 /// # Type Parameters
94 /// - `T`: The type of the resource to remove.
95 ///
96 /// # Returns
97 /// - `Option<T>`: The removed resource if it existed; None if no resource of type `T` was found.
98 pub fn remove_unique<T: 'static>(&mut self) -> Option<T> {
99 let tid = TypeId::of::<T>();
100 self.unique_resources
101 .remove(&tid)?
102 .downcast::<T>()
103 .ok()
104 .map(|boxed| *boxed)
105 }
106
107 /// Registers a resource of type `T` and returns a unique `ResourceHandle`.
108 ///
109 /// # Type Parameters
110 /// - `T`: The concrete type of the resource to store. Must be `'static`.
111 ///
112 /// # Parameters
113 /// - `resource`: The resource instance to store in the registry.
114 ///
115 /// # Returns
116 /// - `ResourceHandle`: A generational handle uniquely identifying the stored resource.
117 ///
118 /// # Notes
119 /// - The handle can later be used to retrieve or remove the resource.
120 /// - Handles are versioned to prevent use-after-free access.
121 pub fn register_resource<T: 'static>(&mut self, resource: T) -> ResourceHandle {
122 let tid = TypeId::of::<T>();
123
124 let store = self
125 .stores
126 .entry(tid)
127 .or_insert_with(|| Box::new(Vec::<T>::new()));
128 let vec = store.downcast_mut::<Vec<T>>().unwrap();
129
130 let index = vec.len();
131 vec.push(resource);
132
133 let id = self.next_id;
134 self.next_id += 1;
135
136 let version = 1;
137 self.versions.insert(id, version);
138 self.ids.insert(id, (tid, index));
139 self.reverse_ids.insert((tid, index), id);
140
141 ResourceHandle { id, version }
142 }
143
144 /// Retrieves an immutable reference to a resource using its handle.
145 ///
146 /// # Type Parameters
147 /// - `T`: The expected type of the resource to retrieve.
148 ///
149 /// # Parameters
150 /// - `handle`: A `ResourceHandle` returned from `register_resource`.
151 ///
152 /// # Returns
153 /// - `Some(&T)`: A reference to the resource if the handle is valid and the type matches.
154 /// - `None`: If the handle is invalid, outdated (version mismatch), or of the wrong type.
155 ///
156 /// # Safety
157 /// - Ensures the handle's version matches the current version of the stored resource.
158 pub fn get_resource<T: 'static>(&self, handle: ResourceHandle) -> Option<&T> {
159 let tid = TypeId::of::<T>();
160
161 let (stored_tid, index) = self.ids.get(&handle.id)?;
162 if *stored_tid != tid {
163 return None;
164 }
165
166 let current_version = self.versions.get(&handle.id)?;
167 if *current_version != handle.version {
168 return None;
169 }
170
171 let vec = self.stores.get(&tid)?.downcast_ref::<Vec<T>>()?;
172 vec.get(*index)
173 }
174
175 /// Retrieves a mutable reference to a resource using its handle.
176 ///
177 /// # Type Parameters
178 /// - `T`: The expected type of the resource to retrieve.
179 ///
180 /// # Parameters
181 /// - `handle`: A `ResourceHandle` returned from `register_resource`.
182 ///
183 /// # Returns
184 /// - `Some(&mut T)`: A mutable reference to the resource if the handle is valid and type matches.
185 /// - `None`: If the handle is invalid, outdated, or of the wrong type.
186 ///
187 /// # Safety
188 /// - Only succeeds if the handle is still valid and uniquely identifies a resource of type `T`.
189 pub fn get_resource_mut<T: 'static>(&mut self, handle: ResourceHandle) -> Option<&mut T> {
190 let tid = TypeId::of::<T>();
191
192 let (stored_tid, index) = self.ids.get(&handle.id)?;
193 if *stored_tid != tid {
194 return None;
195 }
196
197 let current_version = self.versions.get(&handle.id)?;
198 if *current_version != handle.version {
199 return None;
200 }
201
202 let vec = self.stores.get_mut(&tid)?.downcast_mut::<Vec<T>>()?;
203 vec.get_mut(*index)
204 }
205
206 /// Removes a resource from the registry, invalidating its handle.
207 ///
208 /// # Type Parameters
209 /// - `T`: The expected type of the resource to remove.
210 ///
211 /// # Parameters
212 /// - `handle`: The `ResourceHandle` identifying the resource to remove.
213 ///
214 /// # Returns
215 /// - `Some(T)`: The removed resource if the handle was valid and matched the expected type.
216 /// - `None`: If the handle was invalid, outdated (version mismatch), or mismatched in type.
217 ///
218 /// # Notes
219 /// - This function also increments the resource's version, invalidating all existing handles.
220 /// - If the removed element is not at the end of its internal storage, the moved resource’s
221 /// index is updated, and its handle remains valid.
222 pub fn remove_resource<T: 'static>(&mut self, handle: ResourceHandle) -> Option<T> {
223 let tid = TypeId::of::<T>();
224
225 let (stored_tid, index) = self.ids.remove(&handle.id)?;
226 if stored_tid != tid {
227 return None;
228 }
229
230 let version = self.versions.get_mut(&handle.id)?;
231 if *version != handle.version {
232 return None;
233 }
234
235 // Invalidate the handle by bumping its version
236 *version += 1;
237
238 let vec = self.stores.get_mut(&tid)?.downcast_mut::<Vec<T>>()?;
239
240 if index >= vec.len() {
241 return None;
242 }
243
244 // Remove resource from vec by swapping with last, then popping
245 let removed = vec.swap_remove(index);
246
247 // Clean up reverse ID map
248 self.reverse_ids.remove(&(tid, index));
249
250 // If we swapped with the last element, update the index of the moved one
251 if index < vec.len() {
252 let moved_id = self.reverse_ids.remove(&(tid, vec.len()))?;
253 self.ids.insert(moved_id, (tid, index));
254 self.reverse_ids.insert((tid, index), moved_id);
255 }
256
257 Some(removed)
258 }
259}