use std::cell::RefCell;
use std::rc::Rc;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ResourceId {
pub gen: u32,
pub idx: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct TextureHandle(
pub ResourceId,
);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ShaderHandle(
pub ResourceId,
);
struct ResourceEntry {
value: String,
ref_count: u32,
gen: u32,
}
pub struct ResourceRegistry {
texture_store: Vec<ResourceEntry>,
shader_store: Vec<ResourceEntry>,
free_texture: Vec<usize>,
free_shader: Vec<usize>,
}
impl ResourceRegistry {
pub fn new() -> Self {
Self {
texture_store: Vec::new(),
shader_store: Vec::new(),
free_texture: Vec::new(),
free_shader: Vec::new(),
}
}
pub fn alloc_texture(&mut self, name: String) -> TextureHandle {
let id = if let Some(idx) = self.free_texture.pop() {
let entry = &mut self.texture_store[idx];
entry.value = name;
entry.ref_count = 1;
ResourceId {
gen: entry.gen,
idx: idx as u32,
}
} else {
let idx = self.texture_store.len();
self.texture_store.push(ResourceEntry {
value: name,
ref_count: 1,
gen: 0,
});
ResourceId {
gen: 0,
idx: idx as u32,
}
};
TextureHandle(id)
}
pub fn retain_texture(&mut self, h: TextureHandle) -> bool {
let id = h.0;
let idx = id.idx as usize;
match self.texture_store.get_mut(idx) {
Some(entry) if entry.gen == id.gen && entry.ref_count > 0 => {
entry.ref_count += 1;
true
}
_ => false,
}
}
pub fn release_texture(&mut self, h: TextureHandle) {
let id = h.0;
let idx = id.idx as usize;
if let Some(entry) = self.texture_store.get_mut(idx) {
if entry.gen == id.gen && entry.ref_count > 0 {
entry.ref_count -= 1;
if entry.ref_count == 0 {
entry.gen = entry.gen.wrapping_add(1);
self.free_texture.push(idx);
}
}
}
}
pub fn get_texture(&self, h: TextureHandle) -> Option<&str> {
let id = h.0;
let idx = id.idx as usize;
let entry = self.texture_store.get(idx)?;
if entry.gen == id.gen && entry.ref_count > 0 {
Some(&entry.value)
} else {
None
}
}
pub fn alloc_shader(&mut self, name: String) -> ShaderHandle {
let id = if let Some(idx) = self.free_shader.pop() {
let entry = &mut self.shader_store[idx];
entry.value = name;
entry.ref_count = 1;
ResourceId {
gen: entry.gen,
idx: idx as u32,
}
} else {
let idx = self.shader_store.len();
self.shader_store.push(ResourceEntry {
value: name,
ref_count: 1,
gen: 0,
});
ResourceId {
gen: 0,
idx: idx as u32,
}
};
ShaderHandle(id)
}
pub fn retain_shader(&mut self, h: ShaderHandle) -> bool {
let id = h.0;
let idx = id.idx as usize;
match self.shader_store.get_mut(idx) {
Some(entry) if entry.gen == id.gen && entry.ref_count > 0 => {
entry.ref_count += 1;
true
}
_ => false,
}
}
pub fn release_shader(&mut self, h: ShaderHandle) {
let id = h.0;
let idx = id.idx as usize;
if let Some(entry) = self.shader_store.get_mut(idx) {
if entry.gen == id.gen && entry.ref_count > 0 {
entry.ref_count -= 1;
if entry.ref_count == 0 {
entry.gen = entry.gen.wrapping_add(1);
self.free_shader.push(idx);
}
}
}
}
pub fn get_shader(&self, h: ShaderHandle) -> Option<&str> {
let id = h.0;
let idx = id.idx as usize;
let entry = self.shader_store.get(idx)?;
if entry.gen == id.gen && entry.ref_count > 0 {
Some(&entry.value)
} else {
None
}
}
}
impl Default for ResourceRegistry {
fn default() -> Self {
Self::new()
}
}
pub struct TextureGuard {
pub handle: TextureHandle,
registry: Rc<RefCell<ResourceRegistry>>,
}
impl TextureGuard {
pub fn new(handle: TextureHandle, registry: Rc<RefCell<ResourceRegistry>>) -> Self {
Self { handle, registry }
}
}
impl Drop for TextureGuard {
fn drop(&mut self) {
self.registry.borrow_mut().release_texture(self.handle);
}
}
pub struct ShaderGuard {
pub handle: ShaderHandle,
registry: Rc<RefCell<ResourceRegistry>>,
}
impl ShaderGuard {
pub fn new(handle: ShaderHandle, registry: Rc<RefCell<ResourceRegistry>>) -> Self {
Self { handle, registry }
}
}
impl Drop for ShaderGuard {
fn drop(&mut self) {
self.registry.borrow_mut().release_shader(self.handle);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resource_alloc_retain_release_raii() {
let registry = Rc::new(RefCell::new(ResourceRegistry::new()));
let handle = registry.borrow_mut().alloc_texture("tex_a".to_string());
assert_eq!(registry.borrow().get_texture(handle), Some("tex_a"));
assert!(registry.borrow_mut().retain_texture(handle));
registry.borrow_mut().release_texture(handle);
assert_eq!(registry.borrow().get_texture(handle), Some("tex_a"));
{
let _guard = TextureGuard::new(handle, Rc::clone(®istry));
}
assert_eq!(registry.borrow().get_texture(handle), None);
}
#[test]
fn resource_double_release_is_safe() {
let mut reg = ResourceRegistry::new();
let h = reg.alloc_texture("tex_b".to_string());
reg.release_texture(h);
reg.release_texture(h);
let h2 = reg.alloc_texture("tex_c".to_string());
assert_eq!(reg.get_texture(h2), Some("tex_c"));
assert_eq!(reg.get_texture(h), None);
}
}