1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! Slotted mesh storage with free-list removal.
//!
//! `MeshStore` manages GPU mesh lifetimes using a slot-based approach:
//! removed meshes leave `None` slots that are reused by subsequent inserts.
//! This avoids invalidating existing `MeshId` handles when meshes are removed.
use crate::resources::GpuMesh;
/// Opaque handle to a mesh in the store.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MeshId(pub(crate) usize);
impl MeshId {
/// Create a `MeshId` from a raw index.
pub fn from_index(index: usize) -> Self {
Self(index)
}
/// The raw index into the slot array.
pub fn index(&self) -> usize {
self.0
}
}
/// Slotted storage for GPU meshes with a free list for slot reuse.
pub(crate) struct MeshStore {
slots: Vec<Option<GpuMesh>>,
free_list: Vec<usize>,
}
impl MeshStore {
/// Create an empty mesh store.
pub fn new() -> Self {
Self {
slots: Vec::new(),
free_list: Vec::new(),
}
}
/// Insert a mesh, reusing a free slot if available. Returns the assigned `MeshId`.
pub fn insert(&mut self, mesh: GpuMesh) -> MeshId {
if let Some(idx) = self.free_list.pop() {
self.slots[idx] = Some(mesh);
MeshId(idx)
} else {
let idx = self.slots.len();
self.slots.push(Some(mesh));
MeshId(idx)
}
}
/// Get a reference to the mesh at the given ID, or `None` if the slot is empty/invalid.
pub fn get(&self, id: MeshId) -> Option<&GpuMesh> {
self.slots.get(id.0)?.as_ref()
}
/// Get a mutable reference to the mesh at the given ID.
pub fn get_mut(&mut self, id: MeshId) -> Option<&mut GpuMesh> {
self.slots.get_mut(id.0)?.as_mut()
}
/// Replace the mesh at the given ID with a new one.
///
/// # Errors
///
/// Returns [`ViewportError::MeshSlotEmpty`] if the slot is empty or out of bounds.
pub fn replace(&mut self, id: MeshId, mesh: GpuMesh) -> crate::error::ViewportResult<()> {
match self.slots.get_mut(id.0) {
Some(slot) if slot.is_some() => {
*slot = Some(mesh);
Ok(())
}
_ => Err(crate::error::ViewportError::MeshSlotEmpty { index: id.0 }),
}
}
/// Remove a mesh, dropping its GPU buffers and pushing the slot to the free list.
///
/// Returns `true` if a mesh was actually removed, `false` if the slot was already empty.
pub fn remove(&mut self, id: MeshId) -> bool {
if let Some(slot) = self.slots.get_mut(id.0) {
if slot.is_some() {
*slot = None;
self.free_list.push(id.0);
return true;
}
}
false
}
/// Number of occupied (non-empty) slots.
pub fn len(&self) -> usize {
self.slots.iter().filter(|s| s.is_some()).count()
}
/// Total number of slots (occupied + free).
pub fn slot_count(&self) -> usize {
self.slots.len()
}
/// Whether the slot for the given ID contains a mesh.
pub fn contains(&self, id: MeshId) -> bool {
self.get(id).is_some()
}
}