Skip to main content

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}