wgpu_core/
identity.rs

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