Skip to main content

oxiui_render_wgpu/
resource.rs

1//! Generation-checked resource handles and reference-counted registry.
2//!
3//! Resources (textures, shaders) are identified by [`TextureHandle`] and
4//! [`ShaderHandle`] — lightweight generation-checked tokens.  The
5//! [`ResourceRegistry`] tracks reference counts and recycles slots so the
6//! same index can be safely reused without stale-handle confusion.
7//!
8//! RAII wrappers ([`TextureGuard`], [`ShaderGuard`]) automatically decrement
9//! the reference count when dropped.
10
11use std::cell::RefCell;
12use std::rc::Rc;
13
14// ── ResourceId ────────────────────────────────────────────────────────────────
15
16/// A generation-checked resource identifier.
17///
18/// The `gen` field is bumped each time a slot is recycled so that a stale
19/// handle cannot accidentally alias a newly allocated resource.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21pub struct ResourceId {
22    /// Generation counter for this slot.
23    pub gen: u32,
24    /// Index into the backing store.
25    pub idx: u32,
26}
27
28// ── Handle newtypes ───────────────────────────────────────────────────────────
29
30/// An opaque handle to a GPU texture resource.
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
32pub struct TextureHandle(
33    /// The underlying resource identifier.
34    pub ResourceId,
35);
36
37/// An opaque handle to a GPU shader resource.
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39pub struct ShaderHandle(
40    /// The underlying resource identifier.
41    pub ResourceId,
42);
43
44// ── ResourceEntry ─────────────────────────────────────────────────────────────
45
46/// A slot in the registry backing store.
47struct ResourceEntry {
48    /// Stored value (e.g. an asset name or path).
49    value: String,
50    /// Live reference count.  Zero means the slot is free.
51    ref_count: u32,
52    /// Current slot generation — incremented when the slot is recycled.
53    gen: u32,
54}
55
56// ── ResourceRegistry ──────────────────────────────────────────────────────────
57
58/// Shared registry of GPU textures and shaders, with generation-checked handles
59/// and reference counting.
60///
61/// Slots are recycled via a free list when their reference count reaches zero.
62/// The generation counter prevents stale handles from aliasing new allocations.
63pub struct ResourceRegistry {
64    /// Storage for texture entries.
65    texture_store: Vec<ResourceEntry>,
66    /// Storage for shader entries.
67    shader_store: Vec<ResourceEntry>,
68    /// Indices of free texture slots (ref_count == 0).
69    free_texture: Vec<usize>,
70    /// Indices of free shader slots (ref_count == 0).
71    free_shader: Vec<usize>,
72}
73
74impl ResourceRegistry {
75    /// Construct an empty registry.
76    pub fn new() -> Self {
77        Self {
78            texture_store: Vec::new(),
79            shader_store: Vec::new(),
80            free_texture: Vec::new(),
81            free_shader: Vec::new(),
82        }
83    }
84
85    // ── Texture API ──────────────────────────────────────────────────────────
86
87    /// Allocate a texture slot with an initial reference count of 1.
88    pub fn alloc_texture(&mut self, name: String) -> TextureHandle {
89        let id = if let Some(idx) = self.free_texture.pop() {
90            let entry = &mut self.texture_store[idx];
91            entry.value = name;
92            entry.ref_count = 1;
93            ResourceId {
94                gen: entry.gen,
95                idx: idx as u32,
96            }
97        } else {
98            let idx = self.texture_store.len();
99            self.texture_store.push(ResourceEntry {
100                value: name,
101                ref_count: 1,
102                gen: 0,
103            });
104            ResourceId {
105                gen: 0,
106                idx: idx as u32,
107            }
108        };
109        TextureHandle(id)
110    }
111
112    /// Increment the reference count of `h`.
113    ///
114    /// Returns `false` if the handle is stale (generation mismatch or
115    /// out-of-bounds); returns `true` on success.
116    pub fn retain_texture(&mut self, h: TextureHandle) -> bool {
117        let id = h.0;
118        let idx = id.idx as usize;
119        match self.texture_store.get_mut(idx) {
120            Some(entry) if entry.gen == id.gen && entry.ref_count > 0 => {
121                entry.ref_count += 1;
122                true
123            }
124            _ => false,
125        }
126    }
127
128    /// Decrement the reference count of `h`.
129    ///
130    /// When the count reaches zero the slot's generation is bumped and the
131    /// slot is pushed onto the free list for reuse.  Stale or already-freed
132    /// handles are silently ignored.
133    pub fn release_texture(&mut self, h: TextureHandle) {
134        let id = h.0;
135        let idx = id.idx as usize;
136        if let Some(entry) = self.texture_store.get_mut(idx) {
137            if entry.gen == id.gen && entry.ref_count > 0 {
138                entry.ref_count -= 1;
139                if entry.ref_count == 0 {
140                    entry.gen = entry.gen.wrapping_add(1);
141                    self.free_texture.push(idx);
142                }
143            }
144        }
145    }
146
147    /// Return the name associated with `h`, or `None` if stale.
148    pub fn get_texture(&self, h: TextureHandle) -> Option<&str> {
149        let id = h.0;
150        let idx = id.idx as usize;
151        let entry = self.texture_store.get(idx)?;
152        if entry.gen == id.gen && entry.ref_count > 0 {
153            Some(&entry.value)
154        } else {
155            None
156        }
157    }
158
159    // ── Shader API ───────────────────────────────────────────────────────────
160
161    /// Allocate a shader slot with an initial reference count of 1.
162    pub fn alloc_shader(&mut self, name: String) -> ShaderHandle {
163        let id = if let Some(idx) = self.free_shader.pop() {
164            let entry = &mut self.shader_store[idx];
165            entry.value = name;
166            entry.ref_count = 1;
167            ResourceId {
168                gen: entry.gen,
169                idx: idx as u32,
170            }
171        } else {
172            let idx = self.shader_store.len();
173            self.shader_store.push(ResourceEntry {
174                value: name,
175                ref_count: 1,
176                gen: 0,
177            });
178            ResourceId {
179                gen: 0,
180                idx: idx as u32,
181            }
182        };
183        ShaderHandle(id)
184    }
185
186    /// Increment the reference count of `h`.
187    ///
188    /// Returns `false` if the handle is stale; returns `true` on success.
189    pub fn retain_shader(&mut self, h: ShaderHandle) -> bool {
190        let id = h.0;
191        let idx = id.idx as usize;
192        match self.shader_store.get_mut(idx) {
193            Some(entry) if entry.gen == id.gen && entry.ref_count > 0 => {
194                entry.ref_count += 1;
195                true
196            }
197            _ => false,
198        }
199    }
200
201    /// Decrement the reference count of `h`.
202    ///
203    /// When the count reaches zero the slot is freed and the generation is
204    /// bumped.  Stale handles are silently ignored.
205    pub fn release_shader(&mut self, h: ShaderHandle) {
206        let id = h.0;
207        let idx = id.idx as usize;
208        if let Some(entry) = self.shader_store.get_mut(idx) {
209            if entry.gen == id.gen && entry.ref_count > 0 {
210                entry.ref_count -= 1;
211                if entry.ref_count == 0 {
212                    entry.gen = entry.gen.wrapping_add(1);
213                    self.free_shader.push(idx);
214                }
215            }
216        }
217    }
218
219    /// Return the name associated with `h`, or `None` if stale.
220    pub fn get_shader(&self, h: ShaderHandle) -> Option<&str> {
221        let id = h.0;
222        let idx = id.idx as usize;
223        let entry = self.shader_store.get(idx)?;
224        if entry.gen == id.gen && entry.ref_count > 0 {
225            Some(&entry.value)
226        } else {
227            None
228        }
229    }
230}
231
232impl Default for ResourceRegistry {
233    fn default() -> Self {
234        Self::new()
235    }
236}
237
238// ── RAII guards ───────────────────────────────────────────────────────────────
239
240/// RAII wrapper that releases a [`TextureHandle`] when dropped.
241pub struct TextureGuard {
242    /// The guarded texture handle.
243    pub handle: TextureHandle,
244    /// Shared access to the registry that owns `handle`.
245    registry: Rc<RefCell<ResourceRegistry>>,
246}
247
248impl TextureGuard {
249    /// Construct a guard that will release `handle` on drop.
250    pub fn new(handle: TextureHandle, registry: Rc<RefCell<ResourceRegistry>>) -> Self {
251        Self { handle, registry }
252    }
253}
254
255impl Drop for TextureGuard {
256    fn drop(&mut self) {
257        self.registry.borrow_mut().release_texture(self.handle);
258    }
259}
260
261/// RAII wrapper that releases a [`ShaderHandle`] when dropped.
262pub struct ShaderGuard {
263    /// The guarded shader handle.
264    pub handle: ShaderHandle,
265    /// Shared access to the registry that owns `handle`.
266    registry: Rc<RefCell<ResourceRegistry>>,
267}
268
269impl ShaderGuard {
270    /// Construct a guard that will release `handle` on drop.
271    pub fn new(handle: ShaderHandle, registry: Rc<RefCell<ResourceRegistry>>) -> Self {
272        Self { handle, registry }
273    }
274}
275
276impl Drop for ShaderGuard {
277    fn drop(&mut self) {
278        self.registry.borrow_mut().release_shader(self.handle);
279    }
280}
281
282// ── Tests ─────────────────────────────────────────────────────────────────────
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287
288    #[test]
289    fn resource_alloc_retain_release_raii() {
290        let registry = Rc::new(RefCell::new(ResourceRegistry::new()));
291
292        // Allocate with ref_count = 1.
293        let handle = registry.borrow_mut().alloc_texture("tex_a".to_string());
294        assert_eq!(registry.borrow().get_texture(handle), Some("tex_a"));
295
296        // retain bumps to 2.
297        assert!(registry.borrow_mut().retain_texture(handle));
298
299        // First release → count 1, still live.
300        registry.borrow_mut().release_texture(handle);
301        assert_eq!(registry.borrow().get_texture(handle), Some("tex_a"));
302
303        // RAII guard releases the second ref on drop.
304        {
305            let _guard = TextureGuard::new(handle, Rc::clone(&registry));
306        }
307        // After guard drop, count == 0 → slot freed, handle stale.
308        assert_eq!(registry.borrow().get_texture(handle), None);
309    }
310
311    #[test]
312    fn resource_double_release_is_safe() {
313        let mut reg = ResourceRegistry::new();
314        let h = reg.alloc_texture("tex_b".to_string());
315        reg.release_texture(h);
316        // Second release on the now-stale handle must not panic.
317        reg.release_texture(h);
318        // Slot is freed; a new alloc should reuse it.
319        let h2 = reg.alloc_texture("tex_c".to_string());
320        assert_eq!(reg.get_texture(h2), Some("tex_c"));
321        // The old handle is still stale.
322        assert_eq!(reg.get_texture(h), None);
323    }
324}