wgpu_core/
storage.rs

1use alloc::{sync::Arc, vec::Vec};
2use core::mem;
3
4use crate::id::{Id, Marker};
5use crate::resource::ResourceType;
6use crate::{Epoch, Index};
7use parking_lot::Mutex;
8
9/// An entry in a `Storage::map` table.
10#[derive(Debug)]
11pub(crate) enum Element<T>
12where
13    T: StorageItem,
14{
15    /// There are no live ids with this index.
16    Vacant,
17
18    /// There is one live id with this index, allocated at the given
19    /// epoch.
20    Occupied(T, Epoch),
21}
22
23/// Not a public API. For use only by `player`.
24#[doc(hidden)]
25pub trait StorageItem: ResourceType {
26    type Marker: Marker;
27}
28
29impl<T: ResourceType> ResourceType for Arc<T> {
30    const TYPE: &'static str = T::TYPE;
31}
32
33impl<T: StorageItem> StorageItem for Arc<T> {
34    type Marker = T::Marker;
35}
36
37impl<T: ResourceType> ResourceType for Mutex<T> {
38    const TYPE: &'static str = T::TYPE;
39}
40
41impl<T: StorageItem> StorageItem for Mutex<T> {
42    type Marker = T::Marker;
43}
44
45#[macro_export]
46macro_rules! impl_storage_item {
47    ($ty:ident) => {
48        impl $crate::storage::StorageItem for $ty {
49            type Marker = $crate::id::markers::$ty;
50        }
51    };
52}
53
54/// A table of `T` values indexed by the id type `I`.
55///
56/// `Storage` implements [`core::ops::Index`], accepting `Id` values as
57/// indices.
58///
59/// The table is represented as a vector indexed by the ids' index
60/// values, so you should use an id allocator like `IdentityManager`
61/// that keeps the index values dense and close to zero.
62#[derive(Debug)]
63pub(crate) struct Storage<T>
64where
65    T: StorageItem,
66{
67    pub(crate) map: Vec<Element<T>>,
68}
69
70impl<T> Storage<T>
71where
72    T: StorageItem,
73{
74    pub(crate) fn new() -> Self {
75        Self { map: Vec::new() }
76    }
77}
78
79impl<T> Storage<T>
80where
81    T: StorageItem,
82{
83    pub(crate) fn insert(&mut self, id: Id<T::Marker>, value: T) {
84        let (index, epoch) = id.unzip();
85        let index = index as usize;
86        if index >= self.map.len() {
87            self.map.resize_with(index + 1, || Element::Vacant);
88        }
89        match mem::replace(&mut self.map[index], Element::Occupied(value, epoch)) {
90            Element::Vacant => {}
91            Element::Occupied(_, storage_epoch) => {
92                panic!(
93                    "Cannot insert {id:?}, found existing resource {other:?}",
94                    other = Id::<T::Marker>::zip(index as Index, storage_epoch),
95                );
96            }
97        }
98    }
99
100    pub(crate) fn remove(&mut self, id: Id<T::Marker>) -> T {
101        let (index, epoch) = id.unzip();
102        let stored = self.map.get_mut(index as usize);
103        match stored.map(|stored| mem::replace(stored, Element::Vacant)) {
104            Some(Element::Occupied(value, storage_epoch)) => {
105                assert_eq!(
106                    epoch,
107                    storage_epoch,
108                    "Cannot remove {id:?}, found other resource {other:?}",
109                    other = Id::<T::Marker>::zip(index, storage_epoch),
110                );
111                value
112            }
113            None | Some(Element::Vacant) => {
114                panic!("Cannot remove non-existent resource {id:?}");
115            }
116        }
117    }
118
119    #[allow(dead_code)]
120    pub(crate) fn iter(&self) -> impl Iterator<Item = (Id<T::Marker>, &T)> {
121        self.map
122            .iter()
123            .enumerate()
124            .filter_map(move |(index, x)| match *x {
125                Element::Occupied(ref value, storage_epoch) => {
126                    Some((Id::zip(index as Index, storage_epoch), value))
127                }
128                _ => None,
129            })
130    }
131}
132
133impl<T> Storage<T>
134where
135    T: StorageItem + Clone,
136{
137    /// Get an owned reference to an item.
138    /// Panics if there is an epoch mismatch, the entry is empty or in error.
139    pub(crate) fn get(&self, id: Id<T::Marker>) -> T {
140        let (index, epoch) = id.unzip();
141        let (result, storage_epoch) = match self.map.get(index as usize) {
142            Some(&Element::Occupied(ref v, epoch)) => (v.clone(), epoch),
143            None | Some(&Element::Vacant) => {
144                panic!("Cannot get non-existent resource {id:?}");
145            }
146        };
147        assert_eq!(
148            epoch,
149            storage_epoch,
150            "Cannot get {id:?}, found other resource {other:?}",
151            other = Id::<T::Marker>::zip(index, storage_epoch),
152        );
153        result
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[derive(Clone, Debug)]
162    struct TestItem;
163
164    impl ResourceType for TestItem {
165        const TYPE: &'static str = "TestItem";
166    }
167
168    impl StorageItem for TestItem {
169        type Marker = ();
170    }
171
172    fn id(index: Index, epoch: Epoch) -> Id<()> {
173        Id::zip(index, epoch)
174    }
175
176    #[test]
177    #[should_panic(
178        expected = "Cannot insert UntypedId(0,1), found existing resource UntypedId(0,1)"
179    )]
180    fn insert_occupied_same_epoch() {
181        let mut storage = Storage::new();
182        storage.insert(id(0, 1), TestItem);
183        storage.insert(id(0, 1), TestItem);
184    }
185
186    #[test]
187    #[should_panic(
188        expected = "Cannot insert UntypedId(0,2), found existing resource UntypedId(0,1)"
189    )]
190    fn insert_occupied_different_epoch() {
191        let mut storage = Storage::new();
192        storage.insert(id(0, 1), TestItem);
193        storage.insert(id(0, 2), TestItem);
194    }
195
196    #[test]
197    #[should_panic(expected = "Cannot remove UntypedId(0,2), found other resource UntypedId(0,1)")]
198    fn remove_epoch_mismatch() {
199        let mut storage = Storage::new();
200        storage.insert(id(0, 1), TestItem);
201        storage.remove(id(0, 2));
202    }
203
204    #[test]
205    #[should_panic(expected = "Cannot remove non-existent resource UntypedId(0,1)")]
206    fn remove_vacant() {
207        let mut storage = Storage::<TestItem>::new();
208        storage.remove(id(0, 1));
209    }
210
211    #[test]
212    #[should_panic(expected = "Cannot get non-existent resource UntypedId(0,1)")]
213    fn get_vacant() {
214        let storage = Storage::<TestItem>::new();
215        storage.get(id(0, 1));
216    }
217
218    #[test]
219    #[should_panic(expected = "Cannot get UntypedId(0,2), found other resource UntypedId(0,1)")]
220    fn get_epoch_mismatch() {
221        let mut storage = Storage::new();
222        storage.insert(id(0, 1), TestItem);
223        storage.get(id(0, 2));
224    }
225}