viewport_lib/resources/handle.rs
1//! Shared generational handle primitive.
2//!
3//! Most GPU content the library stores (meshes, textures, splat sets, volumes,
4//! curves) is keyed by a handle into a slotted store. A removed entry leaves an
5//! empty slot that a later insert reuses; the handle carries the generation its
6//! slot had when it was issued, and the store bumps that generation on removal.
7//! A stale handle then resolves to nothing rather than aliasing whatever now
8//! occupies the slot.
9//!
10//! [`slot_handle!`] generates one of these handle types with the standard
11//! surface (an `INVALID` sentinel, `index`, and a private `new`), so every
12//! content handle looks and behaves the same. [`ContentHandle`] is the common
13//! interface over them.
14
15/// Common interface implemented by every slotted content handle.
16///
17/// Lets code that does not care which resource a handle names (residency
18/// accounting, generic validity checks) work over any of them.
19pub trait ContentHandle: Copy + Eq {
20 /// The handle value that refers to nothing. Store lookups always return
21 /// `None` for it.
22 const INVALID: Self;
23
24 /// The raw slot index this handle points at.
25 fn index(&self) -> usize;
26
27 /// The generation this handle was issued against.
28 fn generation(&self) -> u32;
29
30 /// Build a handle from a raw slot index and generation. The store mints a
31 /// handle this way on insert; outside code obtains handles from upload calls
32 /// and treats them as opaque.
33 fn from_parts(index: u32, generation: u32) -> Self;
34
35 /// Whether this handle is anything other than [`INVALID`](Self::INVALID).
36 /// A valid handle may still fail to resolve if its slot was freed.
37 fn is_valid(&self) -> bool {
38 *self != Self::INVALID
39 }
40}
41
42/// Define a generational handle newtype with the standard surface.
43///
44/// Generates a `{ index: u32, generation: u32 }` struct plus its `INVALID`
45/// sentinel, `index`, crate-internal `new`, and a [`ContentHandle`] impl. Pass
46/// the doc comment and visibility; the derives and methods are fixed so every
47/// handle matches.
48macro_rules! slot_handle {
49 ($(#[$meta:meta])* $vis:vis struct $name:ident;) => {
50 $(#[$meta])*
51 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52 $vis struct $name {
53 pub(crate) index: u32,
54 pub(crate) generation: u32,
55 }
56
57 impl $name {
58 /// A handle that refers to nothing. Store lookups always return
59 /// `None` for it. Use it as the default / placeholder value where a
60 /// real resource has not been assigned yet.
61 pub const INVALID: $name = $name {
62 index: u32::MAX,
63 generation: u32::MAX,
64 };
65
66 /// The raw slot index this handle points at. Used to index parallel
67 /// per-slot arrays; not meaningful for [`INVALID`](Self::INVALID).
68 pub fn index(&self) -> usize {
69 self.index as usize
70 }
71
72 /// Build a handle from raw parts. Crate-internal: outside code
73 /// obtains a handle from an upload call and treats it as opaque.
74 pub(crate) fn new(index: u32, generation: u32) -> Self {
75 Self { index, generation }
76 }
77 }
78
79 impl $crate::resources::handle::ContentHandle for $name {
80 const INVALID: Self = <$name>::INVALID;
81
82 fn index(&self) -> usize {
83 self.index as usize
84 }
85
86 fn generation(&self) -> u32 {
87 self.generation
88 }
89
90 fn from_parts(index: u32, generation: u32) -> Self {
91 <$name>::new(index, generation)
92 }
93 }
94 };
95}
96
97pub(crate) use slot_handle;
98
99/// Define an append-only registry handle: a stable index into a grow-only store
100/// that never frees or reuses slots, so it needs no generation.
101///
102/// These name resources created once and kept for the session (density volumes,
103/// projected-tet meshes, GPU particle systems). The handle is opaque: it is
104/// obtained from a create / upload call, and its inner index is crate-private so
105/// it cannot be synthesised by hand. If a resource class later becomes
106/// evictable, its handle graduates to [`slot_handle!`] and gains a generation;
107/// because it is already opaque, that is an additive change for consumers.
108macro_rules! registry_handle {
109 ($(#[$meta:meta])* $vis:vis struct $name:ident;) => {
110 $(#[$meta])*
111 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
112 $vis struct $name(pub(crate) usize);
113
114 impl $name {
115 /// The raw registry index this handle points at. Stable for the
116 /// session; useful for debug overlays. Do not synthesise handles by
117 /// hand.
118 pub fn index(&self) -> usize {
119 self.0
120 }
121 }
122 };
123}
124
125pub(crate) use registry_handle;
126
127/// Append-only store keyed by a plain index.
128///
129/// Backs the grow-only content classes named by [`registry_handle!`] handles
130/// (volume textures, projected-tet meshes, overlay textures): slots are never
131/// freed or reused, so an index stays valid for the session. Compared to a raw
132/// `Vec`, this centralises the push-returns-index bookkeeping and routes reads
133/// through [`get`](Registry::get), which bounds-checks instead of panicking on
134/// an out-of-range index.
135pub(crate) struct Registry<T> {
136 items: Vec<T>,
137}
138
139impl<T> Registry<T> {
140 /// Append a value and return the index it was stored at.
141 pub(crate) fn push(&mut self, value: T) -> usize {
142 let index = self.items.len();
143 self.items.push(value);
144 index
145 }
146
147 /// Borrow the value at `index`, or `None` if it is out of range.
148 pub(crate) fn get(&self, index: usize) -> Option<&T> {
149 self.items.get(index)
150 }
151
152 /// Mutably borrow the value at `index`, or `None` if it is out of range.
153 pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut T> {
154 self.items.get_mut(index)
155 }
156
157 /// Number of stored values. Also the index the next [`push`](Registry::push)
158 /// will return.
159 pub(crate) fn len(&self) -> usize {
160 self.items.len()
161 }
162}
163
164impl<T> Default for Registry<T> {
165 fn default() -> Self {
166 Self { items: Vec::new() }
167 }
168}
169
170/// One slot in a [`SlotStore`]: the value when occupied, the slot's current
171/// generation, and the GPU byte size charged for it.
172struct Slot<T> {
173 value: Option<T>,
174 generation: u32,
175 bytes: u64,
176}
177
178/// Slotted store with generational handles, a free list, and maintained
179/// resident-byte and live-count totals.
180///
181/// A removed entry leaves an empty slot that a later insert reuses. Each slot
182/// carries a generation bumped on removal, and the handle captures the
183/// generation its slot had when it was issued. A handle whose generation no
184/// longer matches its slot resolves to `None`, so a stale handle held across a
185/// remove-then-reinsert cannot alias the value now in the slot.
186///
187/// This is the shared core behind the freeable content stores (meshes,
188/// textures, LOD groups, Gaussian splat sets). Each of those wraps a
189/// `SlotStore` and adds only what differs: how it measures an entry's GPU byte
190/// size, and any lookups specific to that resource. Byte accounting is opt-in,
191/// a store whose entries carry no measured size passes `0` for `bytes`.
192pub(crate) struct SlotStore<T, H: ContentHandle> {
193 slots: Vec<Slot<T>>,
194 free_list: Vec<usize>,
195 allocated_bytes: u64,
196 live_count: usize,
197 _handle: std::marker::PhantomData<H>,
198}
199
200impl<T, H: ContentHandle> SlotStore<T, H> {
201 /// Insert a value charging `bytes` against it, reusing a free slot if one is
202 /// available. Returns the handle carrying the slot's current generation.
203 pub(crate) fn insert(&mut self, value: T, bytes: u64) -> H {
204 self.allocated_bytes += bytes;
205 self.live_count += 1;
206 if let Some(idx) = self.free_list.pop() {
207 let slot = &mut self.slots[idx];
208 slot.value = Some(value);
209 slot.bytes = bytes;
210 H::from_parts(idx as u32, slot.generation)
211 } else {
212 let idx = self.slots.len();
213 self.slots.push(Slot {
214 value: Some(value),
215 generation: 0,
216 bytes,
217 });
218 H::from_parts(idx as u32, 0)
219 }
220 }
221
222 /// The live slot for `id`, or `None` if the index is out of range, the slot
223 /// is empty, or the handle's generation is stale.
224 fn live_slot(&self, id: H) -> Option<&Slot<T>> {
225 let slot = self.slots.get(id.index())?;
226 if slot.generation != id.generation() {
227 return None;
228 }
229 slot.value.is_some().then_some(slot)
230 }
231
232 /// Borrow the value for `id`, validating the generation. `None` for a stale
233 /// handle, an empty slot, or an out-of-range index.
234 pub(crate) fn get(&self, id: H) -> Option<&T> {
235 self.live_slot(id)?.value.as_ref()
236 }
237
238 /// Mutably borrow the value for `id`, with the same generation check as
239 /// [`get`](Self::get).
240 pub(crate) fn get_mut(&mut self, id: H) -> Option<&mut T> {
241 let slot = self.slots.get_mut(id.index())?;
242 if slot.generation != id.generation() {
243 return None;
244 }
245 slot.value.as_mut()
246 }
247
248 /// Borrow the value in a raw slot index without a generation check. For the
249 /// per-frame draw path, where the index was already validated through
250 /// [`get`](Self::get) earlier in the same frame.
251 pub(crate) fn get_by_index(&self, index: usize) -> Option<&T> {
252 self.slots.get(index)?.value.as_ref()
253 }
254
255 /// Mutable raw-index lookup, same contract as
256 /// [`get_by_index`](Self::get_by_index).
257 pub(crate) fn get_mut_by_index(&mut self, index: usize) -> Option<&mut T> {
258 self.slots.get_mut(index)?.value.as_mut()
259 }
260
261 /// Swap the value in `id`'s slot, charging `bytes` in place of the old size
262 /// and keeping the slot generation so `id` stays valid. Returns the old
263 /// value, or `None` for a stale handle or an empty slot.
264 ///
265 /// The generation check is the in-flight guard: a stale `id` does not
266 /// resolve, so it cannot overwrite whatever now occupies the slot.
267 pub(crate) fn replace(&mut self, id: H, value: T, bytes: u64) -> Option<T> {
268 let slot = self.slots.get_mut(id.index())?;
269 if slot.generation != id.generation() || slot.value.is_none() {
270 return None;
271 }
272 let old = slot.value.replace(value);
273 self.allocated_bytes = self.allocated_bytes.saturating_sub(slot.bytes) + bytes;
274 slot.bytes = bytes;
275 old
276 }
277
278 /// Remove the value for `id`, bump the slot generation, free the slot, and
279 /// drop its byte charge. Returns the removed value, or `None` for a stale
280 /// handle or an empty slot.
281 pub(crate) fn remove(&mut self, id: H) -> Option<T> {
282 let slot = self.slots.get_mut(id.index())?;
283 if slot.generation != id.generation() {
284 return None;
285 }
286 let value = slot.value.take()?;
287 self.allocated_bytes = self.allocated_bytes.saturating_sub(slot.bytes);
288 slot.bytes = 0;
289 // Bump so the just-removed handle no longer matches this slot.
290 slot.generation = slot.generation.wrapping_add(1);
291 self.free_list.push(id.index());
292 self.live_count -= 1;
293 Some(value)
294 }
295
296 /// Whether the slot for `id` holds a live value.
297 pub(crate) fn contains(&self, id: H) -> bool {
298 self.live_slot(id).is_some()
299 }
300
301 /// Number of occupied (non-empty) slots.
302 pub(crate) fn len(&self) -> usize {
303 self.live_count
304 }
305
306 /// Total number of slots (occupied plus free).
307 pub(crate) fn slot_count(&self) -> usize {
308 self.slots.len()
309 }
310
311 /// Total GPU bytes charged across every resident value.
312 pub(crate) fn allocated_bytes(&self) -> u64 {
313 self.allocated_bytes
314 }
315
316 /// Iterate every live value with its handle.
317 pub(crate) fn iter(&self) -> impl Iterator<Item = (H, &T)> {
318 self.slots.iter().enumerate().filter_map(|(idx, slot)| {
319 slot.value
320 .as_ref()
321 .map(|v| (H::from_parts(idx as u32, slot.generation), v))
322 })
323 }
324
325 /// Mutably iterate every live value with its handle.
326 pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = (H, &mut T)> {
327 self.slots.iter_mut().enumerate().filter_map(|(idx, slot)| {
328 let generation = slot.generation;
329 slot.value
330 .as_mut()
331 .map(|v| (H::from_parts(idx as u32, generation), v))
332 })
333 }
334}
335
336impl<T, H: ContentHandle> Default for SlotStore<T, H> {
337 fn default() -> Self {
338 Self {
339 slots: Vec::new(),
340 free_list: Vec::new(),
341 allocated_bytes: 0,
342 live_count: 0,
343 _handle: std::marker::PhantomData,
344 }
345 }
346}