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    pub(crate) fn iter(&self) -> impl Iterator<Item = (Id<T::Marker>, &T)> {
120        self.map
121            .iter()
122            .enumerate()
123            .filter_map(move |(index, x)| match *x {
124                Element::Occupied(ref value, storage_epoch) => {
125                    Some((Id::zip(index as Index, storage_epoch), value))
126                }
127                _ => None,
128            })
129    }
130}
131
132impl<T> Storage<T>
133where
134    T: StorageItem + Clone,
135{
136    /// Get an owned reference to an item.
137    /// Panics if there is an epoch mismatch, the entry is empty or in error.
138    pub(crate) fn get(&self, id: Id<T::Marker>) -> T {
139        let (index, epoch) = id.unzip();
140        let (result, storage_epoch) = match self.map.get(index as usize) {
141            Some(&Element::Occupied(ref v, epoch)) => (v.clone(), epoch),
142            None | Some(&Element::Vacant) => {
143                panic!("Cannot get non-existent resource {id:?}");
144            }
145        };
146        assert_eq!(
147            epoch,
148            storage_epoch,
149            "Cannot get {id:?}, found other resource {other:?}",
150            other = Id::<T::Marker>::zip(index, storage_epoch),
151        );
152        result
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[derive(Clone, Debug)]
161    struct TestItem;
162
163    impl ResourceType for TestItem {
164        const TYPE: &'static str = "TestItem";
165    }
166
167    impl StorageItem for TestItem {
168        type Marker = ();
169    }
170
171    fn id(index: Index, epoch: Epoch) -> Id<()> {
172        Id::zip(index, epoch)
173    }
174
175    #[test]
176    #[should_panic(
177        expected = "Cannot insert UntypedId(0,1), found existing resource UntypedId(0,1)"
178    )]
179    fn insert_occupied_same_epoch() {
180        let mut storage = Storage::new();
181        storage.insert(id(0, 1), TestItem);
182        storage.insert(id(0, 1), TestItem);
183    }
184
185    #[test]
186    #[should_panic(
187        expected = "Cannot insert UntypedId(0,2), found existing resource UntypedId(0,1)"
188    )]
189    fn insert_occupied_different_epoch() {
190        let mut storage = Storage::new();
191        storage.insert(id(0, 1), TestItem);
192        storage.insert(id(0, 2), TestItem);
193    }
194
195    #[test]
196    #[should_panic(expected = "Cannot remove UntypedId(0,2), found other resource UntypedId(0,1)")]
197    fn remove_epoch_mismatch() {
198        let mut storage = Storage::new();
199        storage.insert(id(0, 1), TestItem);
200        storage.remove(id(0, 2));
201    }
202
203    #[test]
204    #[should_panic(expected = "Cannot remove non-existent resource UntypedId(0,1)")]
205    fn remove_vacant() {
206        let mut storage = Storage::<TestItem>::new();
207        storage.remove(id(0, 1));
208    }
209
210    #[test]
211    #[should_panic(expected = "Cannot get non-existent resource UntypedId(0,1)")]
212    fn get_vacant() {
213        let storage = Storage::<TestItem>::new();
214        storage.get(id(0, 1));
215    }
216
217    #[test]
218    #[should_panic(expected = "Cannot get UntypedId(0,2), found other resource UntypedId(0,1)")]
219    fn get_epoch_mismatch() {
220        let mut storage = Storage::new();
221        storage.insert(id(0, 1), TestItem);
222        storage.get(id(0, 2));
223    }
224}