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/// - The non-reuse property is achieved by storing an `epoch` alongside the
36/// index in an `Id`. Index values are reused, but only with a different
37/// epoch.
38///
39/// `IdentityValues` can also be used to track the count of IDs allocated by
40/// some external allocator. Combining internal and external allocation is not
41/// allowed; calling both `alloc` and `mark_as_used` on the same
42/// `IdentityValues` will result in a panic. The external mode is used when
43/// [playing back a trace of wgpu operations][player].
44///
45/// [`Id`]: crate::id::Id
46/// [`alloc`]: IdentityValues::alloc
47/// [`release`]: IdentityValues::release
48/// [player]: https://github.com/gfx-rs/wgpu/tree/trunk/player/
49#[derive(Debug)]
50pub(super) struct IdentityValues {
51 free: Vec<(Index, Epoch)>,
52 next_index: Index,
53 count: usize,
54 // Sanity check: The allocation logic works under the assumption that we don't
55 // do a mix of allocating ids from here and providing ids manually for the same
56 // storage container.
57 id_source: IdSource,
58}
59
60impl IdentityValues {
61 /// Allocate a fresh, never-before-seen ID.
62 ///
63 /// # Panics
64 ///
65 /// If `mark_as_used` has previously been called on this `IdentityValues`.
66 pub fn alloc<T: Marker>(&mut self) -> Id<T> {
67 assert!(
68 self.id_source != IdSource::External,
69 "Mix of internally allocated and externally provided IDs"
70 );
71 self.id_source = IdSource::Allocated;
72
73 self.count += 1;
74 match self.free.pop() {
75 Some((index, epoch)) => Id::zip(index, epoch + 1),
76 None => {
77 let index = self.next_index;
78 self.next_index += 1;
79 let epoch = 1;
80 Id::zip(index, epoch)
81 }
82 }
83 }
84
85 /// Increment the count of used IDs.
86 ///
87 /// # Panics
88 ///
89 /// If `alloc` has previously been called on this `IdentityValues`.
90 pub fn mark_as_used<T: Marker>(&mut self, id: Id<T>) -> Id<T> {
91 assert!(
92 self.id_source != IdSource::Allocated,
93 "Mix of internally allocated and externally provided IDs"
94 );
95 self.id_source = IdSource::External;
96
97 self.count += 1;
98 id
99 }
100
101 /// Free `id` and/or decrement the count of used IDs.
102 ///
103 /// Freed IDs will never be returned from `alloc` again.
104 pub fn release<T: Marker>(&mut self, id: Id<T>) {
105 if let IdSource::Allocated = self.id_source {
106 let (index, epoch) = id.unzip();
107 self.free.push((index, epoch));
108 }
109 self.count -= 1;
110 }
111
112 pub fn count(&self) -> usize {
113 self.count
114 }
115}
116
117#[derive(Debug)]
118pub struct IdentityManager<T: Marker> {
119 pub(super) values: Mutex<IdentityValues>,
120 _phantom: PhantomData<T>,
121}
122
123impl<T: Marker> IdentityManager<T> {
124 pub fn process(&self) -> Id<T> {
125 self.values.lock().alloc()
126 }
127 pub fn mark_as_used(&self, id: Id<T>) -> Id<T> {
128 self.values.lock().mark_as_used(id)
129 }
130 pub fn free(&self, id: Id<T>) {
131 self.values.lock().release(id)
132 }
133}
134
135impl<T: Marker> IdentityManager<T> {
136 pub fn new() -> Self {
137 Self {
138 values: Mutex::new(
139 rank::IDENTITY_MANAGER_VALUES,
140 IdentityValues {
141 free: Vec::new(),
142 next_index: 0,
143 count: 0,
144 id_source: IdSource::None,
145 },
146 ),
147 _phantom: PhantomData,
148 }
149 }
150}
151
152#[test]
153fn test_epoch_end_of_life() {
154 use crate::id;
155 let man = IdentityManager::<id::markers::Buffer>::new();
156 let id1 = man.process();
157 assert_eq!(id1.unzip(), (0, 1));
158 man.free(id1);
159 let id2 = man.process();
160 // confirm that the epoch 1 is no longer re-used
161 assert_eq!(id2.unzip(), (0, 2));
162}