wgpu_core/
registry.rs

1use alloc::sync::Arc;
2
3use crate::{
4    id::Id,
5    identity::IdentityManager,
6    lock::{rank, RwLock, RwLockReadGuard},
7    storage::{Element, Storage, StorageItem},
8};
9
10#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
11pub struct RegistryReport {
12    pub num_allocated: usize,
13    pub num_kept_from_user: usize,
14    pub num_released_from_user: usize,
15    pub element_size: usize,
16}
17
18impl RegistryReport {
19    pub fn is_empty(&self) -> bool {
20        self.num_allocated + self.num_kept_from_user == 0
21    }
22}
23
24/// Registry is the primary holder of each resource type
25/// Every resource is now arcanized so the last arc released
26/// will in the end free the memory and release the inner raw resource
27///
28/// Registry act as the main entry point to keep resource alive
29/// when created and released from user land code
30///
31/// A resource may still be alive when released from user land code
32/// if it's used in active submission or anyway kept alive from
33/// any other dependent resource
34///
35#[derive(Debug)]
36pub(crate) struct Registry<T: StorageItem> {
37    // Must only contain an id which has either never been used or has been released from `storage`
38    identity: Arc<IdentityManager<T::Marker>>,
39    storage: RwLock<Storage<T>>,
40}
41
42impl<T: StorageItem> Registry<T> {
43    pub(crate) fn new() -> Self {
44        Self {
45            identity: Arc::new(IdentityManager::new()),
46            storage: RwLock::new(rank::REGISTRY_STORAGE, Storage::new()),
47        }
48    }
49}
50
51#[must_use]
52pub(crate) struct FutureId<'a, T: StorageItem> {
53    id: Id<T::Marker>,
54    data: &'a RwLock<Storage<T>>,
55}
56
57impl<T: StorageItem> FutureId<'_, T> {
58    #[cfg(feature = "trace")]
59    pub fn id(&self) -> Id<T::Marker> {
60        self.id
61    }
62
63    /// Assign a new resource to this ID.
64    ///
65    /// Registers it with the registry.
66    pub fn assign(self, value: T) -> Id<T::Marker> {
67        let mut data = self.data.write();
68        data.insert(self.id, value);
69        self.id
70    }
71}
72
73impl<T: StorageItem> Registry<T> {
74    pub(crate) fn prepare(&self, id_in: Option<Id<T::Marker>>) -> FutureId<'_, T> {
75        FutureId {
76            id: match id_in {
77                Some(id_in) => {
78                    self.identity.mark_as_used(id_in);
79                    id_in
80                }
81                None => self.identity.process(),
82            },
83            data: &self.storage,
84        }
85    }
86
87    #[track_caller]
88    pub(crate) fn read<'a>(&'a self) -> RwLockReadGuard<'a, Storage<T>> {
89        self.storage.read()
90    }
91    pub(crate) fn remove(&self, id: Id<T::Marker>) -> T {
92        let value = self.storage.write().remove(id);
93        // This needs to happen *after* removing it from the storage, to maintain the
94        // invariant that `self.identity` only contains ids which are actually available
95        // See https://github.com/gfx-rs/wgpu/issues/5372
96        self.identity.free(id);
97        //Returning None is legal if it's an error ID
98        value
99    }
100
101    pub(crate) fn generate_report(&self) -> RegistryReport {
102        let storage = self.storage.read();
103        let mut report = RegistryReport {
104            element_size: size_of::<T>(),
105            ..Default::default()
106        };
107        report.num_allocated = self.identity.values.lock().count();
108        for element in storage.map.iter() {
109            match *element {
110                Element::Occupied(..) => report.num_kept_from_user += 1,
111                Element::Vacant => report.num_released_from_user += 1,
112            }
113        }
114        report
115    }
116}
117
118impl<T: StorageItem + Clone> Registry<T> {
119    pub(crate) fn get(&self, id: Id<T::Marker>) -> T {
120        self.read().get(id)
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::Registry;
127    use crate::{id::Marker, resource::ResourceType, storage::StorageItem};
128    use alloc::sync::Arc;
129
130    struct TestData;
131    struct TestDataId;
132    impl Marker for TestDataId {}
133
134    impl ResourceType for TestData {
135        const TYPE: &'static str = "TestData";
136    }
137    impl StorageItem for TestData {
138        type Marker = TestDataId;
139    }
140
141    #[test]
142    fn simultaneous_registration() {
143        let registry = Registry::new();
144        std::thread::scope(|s| {
145            for _ in 0..5 {
146                s.spawn(|| {
147                    for _ in 0..1000 {
148                        let value = Arc::new(TestData);
149                        let new_id = registry.prepare(None);
150                        let id = new_id.assign(value);
151                        registry.remove(id);
152                    }
153                });
154            }
155        })
156    }
157}