1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
//! The `ResourceMetadata` type.
use bit_vec::BitVec;
use wgt::strict_assert;
/// A set of resources, holding a `Arc<T>` and epoch for each member.
///
/// Testing for membership is fast, and iterating over members is
/// reasonably fast in practice. Storage consumption is proportional
/// to the largest id index of any member, not to the number of
/// members, but a bit vector tracks occupancy, so iteration touches
/// only occupied elements.
#[derive(Debug)]
pub(super) struct ResourceMetadata<T: Clone> {
/// If the resource with index `i` is a member, `owned[i]` is `true`.
owned: BitVec<usize>,
/// A vector holding clones of members' `T`s.
resources: Vec<Option<T>>,
}
impl<T: Clone> ResourceMetadata<T> {
pub(super) fn new() -> Self {
Self {
owned: BitVec::default(),
resources: Vec::new(),
}
}
pub(super) fn set_size(&mut self, size: usize) {
self.resources.resize(size, None);
resize_bitvec(&mut self.owned, size);
}
pub(super) fn clear(&mut self) {
self.resources.clear();
self.owned.clear();
}
/// Ensures a given index is in bounds for all arrays and does
/// sanity checks of the presence of a refcount.
///
/// In release mode this function is completely empty and is removed.
#[cfg_attr(not(feature = "strict_asserts"), allow(unused_variables))]
pub(super) fn tracker_assert_in_bounds(&self, index: usize) {
strict_assert!(index < self.owned.len());
strict_assert!(index < self.resources.len());
strict_assert!(if self.contains(index) {
self.resources[index].is_some()
} else {
true
});
}
/// Returns true if the tracker owns no resources.
///
/// This is a O(n) operation.
pub(super) fn is_empty(&self) -> bool {
!self.owned.any()
}
/// Returns true if the set contains the resource with the given index.
pub(super) fn contains(&self, index: usize) -> bool {
self.owned.get(index).unwrap_or(false)
}
/// Returns true if the set contains the resource with the given index.
///
/// # Safety
///
/// The given `index` must be in bounds for this `ResourceMetadata`'s
/// existing tables. See `tracker_assert_in_bounds`.
#[inline(always)]
pub(super) unsafe fn contains_unchecked(&self, index: usize) -> bool {
unsafe { self.owned.get(index).unwrap_unchecked() }
}
/// Insert a resource into the set.
///
/// Add the resource with the given index, epoch, and reference count to the
/// set.
///
/// Returns a reference to the newly inserted resource.
/// (This allows avoiding a clone/reference count increase in many cases.)
///
/// # Safety
///
/// The given `index` must be in bounds for this `ResourceMetadata`'s
/// existing tables. See `tracker_assert_in_bounds`.
#[inline(always)]
pub(super) unsafe fn insert(&mut self, index: usize, resource: T) -> &T {
self.owned.set(index, true);
let resource_dst = unsafe { self.resources.get_unchecked_mut(index) };
resource_dst.insert(resource)
}
/// Get the resource with the given index.
///
/// # Safety
///
/// The given `index` must be in bounds for this `ResourceMetadata`'s
/// existing tables. See `tracker_assert_in_bounds`.
#[inline(always)]
pub(super) unsafe fn get_resource_unchecked(&self, index: usize) -> &T {
unsafe {
self.resources
.get_unchecked(index)
.as_ref()
.unwrap_unchecked()
}
}
/// Returns an iterator over the resources owned by `self`.
pub(super) fn owned_resources(&self) -> impl Iterator<Item = T> + '_ {
if !self.owned.is_empty() {
self.tracker_assert_in_bounds(self.owned.len() - 1)
};
iterate_bitvec_indices(&self.owned).map(move |index| {
let resource = unsafe { self.resources.get_unchecked(index) };
resource.as_ref().unwrap().clone()
})
}
/// Returns an iterator over the indices of all resources owned by `self`.
pub(super) fn owned_indices(&self) -> impl Iterator<Item = usize> + '_ {
if !self.owned.is_empty() {
self.tracker_assert_in_bounds(self.owned.len() - 1)
};
iterate_bitvec_indices(&self.owned)
}
/// Remove the resource with the given index from the set.
pub(super) unsafe fn remove(&mut self, index: usize) {
unsafe {
*self.resources.get_unchecked_mut(index) = None;
}
self.owned.set(index, false);
}
}
/// A source of resource metadata.
///
/// This is used to abstract over the various places
/// trackers can get new resource metadata from.
pub(super) enum ResourceMetadataProvider<'a, T: Clone> {
/// Comes directly from explicit values.
Direct { resource: &'a T },
/// Comes from another metadata tracker.
Indirect { metadata: &'a ResourceMetadata<T> },
}
impl<T: Clone> ResourceMetadataProvider<'_, T> {
/// Get a reference to the resource from this.
///
/// # Safety
///
/// - The index must be in bounds of the metadata tracker if this uses an indirect source.
#[inline(always)]
pub(super) unsafe fn get(&self, index: usize) -> &T {
match self {
ResourceMetadataProvider::Direct { resource } => resource,
ResourceMetadataProvider::Indirect { metadata } => {
metadata.tracker_assert_in_bounds(index);
{
let resource = unsafe { metadata.resources.get_unchecked(index) }.as_ref();
unsafe { resource.unwrap_unchecked() }
}
}
}
}
}
/// Resizes the given bitvec to the given size. I'm not sure why this is hard to do but it is.
fn resize_bitvec<B: bit_vec::BitBlock>(vec: &mut BitVec<B>, size: usize) {
let owned_size_to_grow = size.checked_sub(vec.len());
if let Some(delta) = owned_size_to_grow {
if delta != 0 {
vec.grow(delta, false);
}
} else {
vec.truncate(size);
}
}
/// Produces an iterator that yields the indexes of all bits that are set in the bitvec.
///
/// Will skip entire usize's worth of bits if they are all false.
fn iterate_bitvec_indices(ownership: &BitVec<usize>) -> impl Iterator<Item = usize> + '_ {
const BITS_PER_BLOCK: usize = usize::BITS as usize;
let size = ownership.len();
ownership
.blocks()
.enumerate()
.filter(|&(_, word)| word != 0)
.flat_map(move |(word_index, mut word)| {
let bit_start = word_index * BITS_PER_BLOCK;
let bit_end = (bit_start + BITS_PER_BLOCK).min(size);
(bit_start..bit_end).filter(move |_| {
let active = word & 0b1 != 0;
word >>= 1;
active
})
})
}