wgpu_core/
identity.rs

1use crate::{
2    id::{Id, Marker},
3    lock::{rank, Mutex},
4    Epoch, Index,
5};
6use std::{fmt::Debug, marker::PhantomData};
7
8#[derive(Copy, Clone, Debug, PartialEq)]
9enum IdSource {
10    External,
11    Allocated,
12    None,
13}
14
15/// A simple structure to allocate [`Id`] identifiers.
16///
17/// Calling [`alloc`] returns a fresh, never-before-seen id. Calling [`release`]
18/// marks an id as dead; it will never be returned again by `alloc`.
19///
20/// `IdentityValues` returns `Id`s whose index values are suitable for use as
21/// indices into a `Vec<T>` that holds those ids' referents:
22///
23/// - Every live id has a distinct index value. Every live id's index
24///   selects a distinct element in the vector.
25///
26/// - `IdentityValues` prefers low index numbers. If you size your vector to
27///   accommodate the indices produced here, the vector's length will reflect
28///   the highwater mark of actual occupancy.
29///
30/// - `IdentityValues` reuses the index values of freed ids before returning
31///   ids with new index values. Freed vector entries get reused.
32///
33/// [`Id`]: crate::id::Id
34/// [`Backend`]: wgt::Backend;
35/// [`alloc`]: IdentityValues::alloc
36/// [`release`]: IdentityValues::release
37#[derive(Debug)]
38pub(super) struct IdentityValues {
39    free: Vec<(Index, Epoch)>,
40    next_index: Index,
41    count: usize,
42    // Sanity check: The allocation logic works under the assumption that we don't
43    // do a mix of allocating ids from here and providing ids manually for the same
44    // storage container.
45    id_source: IdSource,
46}
47
48impl IdentityValues {
49    /// Allocate a fresh, never-before-seen id with the given `backend`.
50    ///
51    /// The backend is incorporated into the id, so that ids allocated with
52    /// different `backend` values are always distinct.
53    pub fn alloc<T: Marker>(&mut self) -> Id<T> {
54        assert!(
55            self.id_source != IdSource::External,
56            "Mix of internally allocated and externally provided IDs"
57        );
58        self.id_source = IdSource::Allocated;
59
60        self.count += 1;
61        match self.free.pop() {
62            Some((index, epoch)) => Id::zip(index, epoch + 1),
63            None => {
64                let index = self.next_index;
65                self.next_index += 1;
66                let epoch = 1;
67                Id::zip(index, epoch)
68            }
69        }
70    }
71
72    pub fn mark_as_used<T: Marker>(&mut self, id: Id<T>) -> Id<T> {
73        assert!(
74            self.id_source != IdSource::Allocated,
75            "Mix of internally allocated and externally provided IDs"
76        );
77        self.id_source = IdSource::External;
78
79        self.count += 1;
80        id
81    }
82
83    /// Free `id`. It will never be returned from `alloc` again.
84    pub fn release<T: Marker>(&mut self, id: Id<T>) {
85        if let IdSource::Allocated = self.id_source {
86            let (index, epoch) = id.unzip();
87            self.free.push((index, epoch));
88        }
89        self.count -= 1;
90    }
91
92    pub fn count(&self) -> usize {
93        self.count
94    }
95}
96
97#[derive(Debug)]
98pub struct IdentityManager<T: Marker> {
99    pub(super) values: Mutex<IdentityValues>,
100    _phantom: PhantomData<T>,
101}
102
103impl<T: Marker> IdentityManager<T> {
104    pub fn process(&self) -> Id<T> {
105        self.values.lock().alloc()
106    }
107    pub fn mark_as_used(&self, id: Id<T>) -> Id<T> {
108        self.values.lock().mark_as_used(id)
109    }
110    pub fn free(&self, id: Id<T>) {
111        self.values.lock().release(id)
112    }
113}
114
115impl<T: Marker> IdentityManager<T> {
116    pub fn new() -> Self {
117        Self {
118            values: Mutex::new(
119                rank::IDENTITY_MANAGER_VALUES,
120                IdentityValues {
121                    free: Vec::new(),
122                    next_index: 0,
123                    count: 0,
124                    id_source: IdSource::None,
125                },
126            ),
127            _phantom: PhantomData,
128        }
129    }
130}
131
132#[test]
133fn test_epoch_end_of_life() {
134    use crate::id;
135    let man = IdentityManager::<id::markers::Buffer>::new();
136    let id1 = man.process();
137    assert_eq!(id1.unzip(), (0, 1));
138    man.free(id1);
139    let id2 = man.process();
140    // confirm that the epoch 1 is no longer re-used
141    assert_eq!(id2.unzip(), (0, 2));
142}