Merge branch 'sparse-set-docs' (#18)
All checks were successful
Build Zenyx ⚡ / 🧪 Run Cargo Tests (push) Successful in 3m0s
Build Zenyx ⚡ / 🏗️ Build aarch64-unknown-linux-gnu (push) Successful in 9m51s
Build Zenyx ⚡ / 🏗️ Build x86_64-unknown-linux-gnu (push) Successful in 9m52s
Build Zenyx ⚡ / 🏗️ Build x86_64-pc-windows-msvc (push) Successful in 9m58s
Build Zenyx ⚡ / 🏗️ Build aarch64-pc-windows-msvc (push) Successful in 10m21s

Reviewed-on: #18
This commit is contained in:
BitSyndicate 2025-05-06 20:28:05 +02:00
commit 010ee908b0
Signed by: bitsyndicate
GPG key ID: 443E4198D6BBA6DE
2 changed files with 242 additions and 6 deletions

View file

@ -1,3 +1,6 @@
/// Collections types for Zenyx
///
/// - [`SparseSet`]
mod sparse_set;
pub use sparse_set::SparseSet;

View file

@ -10,17 +10,36 @@ use bytemuck::Contiguous;
const SPARSE_PAGESIZE: usize = (1 << 10) * 4;
type SparsePage<A> = Option<(Box<[Option<NonZeroUsize>; SPARSE_PAGESIZE], A>, usize)>;
/// A sparse set for fast lookup of large indices.
///
/// The sparse allocator is mainly used for bulk allocations in the system's page size
/// for the lookup array. It will also be used for the array of pointers into those
/// bulk allocations. Additionally it will be used for the reverse map that generates keys
/// from the value in the internal packed array.
///
/// The packed allocator will exclusively be used to store the values of type `T`.
///
/// All operations on this datastructure, meaning insertion, lookup, and deletion, are `O(1)`.
///
/// This data structure does not in any way guarantee ordering of the values on
/// its own.
#[derive(Hash)]
pub struct SparseSet<T, PackedAlloc = Global, SparseAlloc = Global>
where
PackedAlloc: Allocator,
SparseAlloc: Allocator,
{
/// The paginated array of keys. The value at the key is an index into the dense array minus
/// one where the value corresponding to that key is stored.
sparse: Vec<SparsePage<SparseAlloc>, SparseAlloc>,
/// The dense array where the values corresponding to the keys are stored.
dense: Vec<T, PackedAlloc>,
/// The reverse map to get the index in the sparse array from the index in the dense array.
dense_to_id: Vec<usize, SparseAlloc>,
}
impl<T> SparseSet<T> {
/// Creates a new [`SparseSet`] with the global allocator.
pub const fn new() -> Self {
Self {
sparse: Vec::new(),
@ -30,11 +49,45 @@ impl<T> SparseSet<T> {
}
}
impl<T> Default for SparseSet<T> {
fn default() -> Self {
Self::new()
}
}
impl<T, PackedAlloc, SparseAlloc> core::fmt::Debug for SparseSet<T, PackedAlloc, SparseAlloc>
where
T: core::fmt::Debug,
PackedAlloc: Allocator,
SparseAlloc: Allocator,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_map()
.entries(self.dense_to_id.iter().zip(self.dense.iter()))
.finish()
}
}
impl<T, PackedAlloc, SparseAlloc> SparseSet<T, PackedAlloc, SparseAlloc>
where
PackedAlloc: Allocator,
SparseAlloc: Allocator + Clone,
{
/// Inserts an element into the sparse set with the key `id`. This will
/// return the previous value if it already exists.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
///
/// sparse_set.insert(10, 123);
/// assert_eq!(sparse_set.get(10), Some(&123));
///
/// let prev = sparse_set.insert(10, 9);
/// assert_eq!(prev, Some(123));
/// assert_eq!(sparse_set.get(10), Some(&9));
/// ```
pub fn insert(&mut self, id: usize, value: T) -> Option<T> {
match self.get_dense_idx(id) {
Some(idx) => {
@ -52,15 +105,39 @@ where
}
}
/// Gets the value with the key `id`.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
///
/// sparse_set.insert(10, 123);
/// assert_eq!(sparse_set.get(10), Some(&123));
/// ```
pub fn get(&self, id: usize) -> Option<&T> {
self.dense.get(self.get_dense_idx(id)?)
}
/// Gets the value with the key `id` mutably.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
///
/// sparse_set.insert(10, 123);
/// let value = sparse_set.get_mut(10).unwrap();
/// *value = 0;
/// assert_eq!(sparse_set.get(10), Some(&0));
/// ```
pub fn get_mut(&mut self, id: usize) -> Option<&mut T> {
let idx = self.get_dense_idx(id)?;
self.dense.get_mut(idx)
}
/// Sets the dense index of an `key` to `idx`. This will remove said index
/// if it is [`None`].
fn set_dense_idx(&mut self, id: usize, idx: Option<usize>) {
let page = id / SPARSE_PAGESIZE;
let sparse_index = id % SPARSE_PAGESIZE;
@ -84,6 +161,15 @@ where
}
}
/// Gets the index in the dense array for a key `id`.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
/// sparse_set.insert(10, 123);
/// assert_eq!(sparse_set.values()[sparse_set.get_dense_idx(10).unwrap()], 123);
/// ```
pub fn get_dense_idx(&self, id: usize) -> Option<usize> {
let page = id / SPARSE_PAGESIZE;
let sparse_index = id % SPARSE_PAGESIZE;
@ -91,6 +177,8 @@ where
page.0[sparse_index].map(|idx| idx.into_integer() - 1)
}
/// This reduces the usage count for a page in the sparse array, deallocating
/// it if it is not used anymore.
fn reduce_page_usage_count(&mut self, id: usize) {
let page = id / SPARSE_PAGESIZE;
let Some(usage) = &mut self.sparse[page] else {
@ -103,6 +191,7 @@ where
}
}
/// Increase the page usage count for a page in the sparse array.
fn increase_page_usage_count(&mut self, id: usize) {
let page = id / SPARSE_PAGESIZE;
if page >= self.sparse.len() {
@ -114,6 +203,18 @@ where
usage.1 += 1;
}
/// Removes the value with the key `id` from the sparse set, returning the
/// value if it existed.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
///
/// sparse_set.insert(10, 123);
/// assert_eq!(sparse_set.remove(10), Some(123));
/// assert_eq!(sparse_set.remove(10), None);
/// ```
pub fn remove(&mut self, id: usize) -> Option<T> {
let index = self.get_dense_idx(id)?;
if self.dense.is_empty() {
@ -129,26 +230,126 @@ where
Some(previous)
}
/// Returns if there are values in this sparse set.
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
/// assert!(sparse_set.is_empty());
/// sparse_set.insert(10, 123);
/// assert!(!sparse_set.is_empty());
/// ```
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns the number of values in this sparse set.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
/// sparse_set.insert(10, 123);
/// sparse_set.insert(10, 9);
/// sparse_set.insert(11, 10);
/// assert_eq!(sparse_set.len(), 2);
/// ```
pub fn len(&self) -> usize {
self.dense.len()
}
/// Checks if the sparse set contains a value with key `id`.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
/// assert!(!sparse_set.contains(10));
/// sparse_set.insert(10, 123);
/// assert!(sparse_set.contains(10));
/// ```
pub fn contains(&self, id: usize) -> bool {
self.get_dense_idx(id).is_some()
}
/// Gets the keys of all values in the sparse set. This method does not provide
/// any ordering guarantees other than the keys contained corresponding to
/// the values with the same index returned by [`Self::values`].
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
/// assert!(sparse_set.keys().is_empty());
/// sparse_set.insert(10, 10);
/// sparse_set.insert(9, 10);
/// sparse_set.insert(11, 10);
///
/// assert_eq!(sparse_set.keys(), &[10, 9, 11]);
/// sparse_set.remove(10);
/// assert_eq!(sparse_set.keys(), &[11, 9]);
/// ```
pub fn keys(&self) -> &[usize] {
&self.dense_to_id
}
/// Gets all values in the sparse set, the corresponding `key` is at the same
/// position in the slice returned by [`Self::keys`].
///
/// Otherwise there are no ordering guarantees.
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
/// assert!(sparse_set.values().is_empty());
/// sparse_set.insert(10, 10);
/// sparse_set.insert(9, 9);
/// sparse_set.insert(11, 11);
///
/// assert_eq!(sparse_set.values(), &[10, 9, 11]);
/// sparse_set.remove(10);
/// assert_eq!(sparse_set.values(), &[11, 9]);
/// ```
pub fn values(&self) -> &[T] {
&self.dense
}
/// Mutable version of [`Self::keys`].
///
/// ```
/// use zenyx::collections::SparseSet;
///
/// let mut sparse_set: SparseSet<u32> = SparseSet::new();
/// assert!(sparse_set.values().is_empty());
/// sparse_set.insert(10, 10);
/// sparse_set.insert(9, 9);
/// sparse_set.insert(11, 11);
///
/// let dense_of_9 = sparse_set.get_dense_idx(9).unwrap();
/// let dense_of_10 = sparse_set.get_dense_idx(10).unwrap();
///
/// let values = sparse_set.values_mut();
/// values[dense_of_10] = 9;
/// values[dense_of_9] = 10;
///
/// assert_eq!(sparse_set.get(10), Some(&9));
/// assert_eq!(sparse_set.get(9), Some(&10));
/// ```
pub fn values_mut(&mut self) -> &mut [T] {
&mut self.dense
}
/// Creates a new [`SparseSet`] with the values allocated by the `packed_alloc`
/// and everything else, as described in the top level documentation for [`SparseSet`]
/// in the `sparse_alloc`.
///
/// ```
/// use zenyx::collections::SparseSet;
/// use allocator_api2::alloc::Global;
///
/// let sparse_set = SparseSet::<u32>::new_in(Global, Global);
/// ```
pub fn new_in(packed_alloc: PackedAlloc, sparse_alloc: SparseAlloc) -> Self {
Self {
dense: Vec::new_in(packed_alloc),
@ -162,6 +363,16 @@ impl<T, PackedAlloc> SparseSet<T, PackedAlloc>
where
PackedAlloc: Allocator,
{
/// Creates a new [`SparseSet`] with the values allocated by the `packed_alloc`
/// Everything else, as described in the top level documentation for [`SparseSet`]
/// is allocated using the global allocator.
///
/// ```
/// use zenyx::collections::SparseSet;
/// use allocator_api2::alloc::Global;
///
/// let sparse_set = SparseSet::<u32>::new_in_packed(Global);
/// ```
pub const fn new_in_packed(packed_alloc: PackedAlloc) -> Self {
Self {
sparse: Vec::new(),
@ -236,7 +447,10 @@ mod tests {
assert_eq!(sparse_set.remove(SPARSE_PAGESIZE + 2).unwrap(), 3);
assert_eq!(sparse_set.sparse[1].as_ref().unwrap().1, 2);
assert_eq!(sparse_set.keys(), [10, 11, 12, SPARSE_PAGESIZE, SPARSE_PAGESIZE + 1]);
assert_eq!(
sparse_set.keys(),
[10, 11, 12, SPARSE_PAGESIZE, SPARSE_PAGESIZE + 1]
);
assert_eq!(sparse_set.values(), [1, 2, 2, 1, 2]);
assert_eq!(sparse_set.remove(SPARSE_PAGESIZE + 1).unwrap(), 2);
@ -249,25 +463,44 @@ mod tests {
assert_eq!(sparse_set.keys(), [10, 11, 12]);
assert_eq!(sparse_set.values(), [1, 2, 2]);
sparse_set.insert(SPARSE_PAGESIZE, 1);
sparse_set.insert(SPARSE_PAGESIZE + 1, 2);
sparse_set.insert(SPARSE_PAGESIZE + 2, 3);
assert_eq!(sparse_set.remove(10).unwrap(), 1);
assert_eq!(sparse_set.sparse[0].as_ref().unwrap().1, 2);
// swap-remove
assert_eq!(sparse_set.keys(), [SPARSE_PAGESIZE + 2, 11, 12, SPARSE_PAGESIZE, SPARSE_PAGESIZE + 1]);
// swap-remove
assert_eq!(
sparse_set.keys(),
[
SPARSE_PAGESIZE + 2,
11,
12,
SPARSE_PAGESIZE,
SPARSE_PAGESIZE + 1
]
);
assert_eq!(sparse_set.values(), [3, 2, 2, 1, 2]);
assert_eq!(sparse_set.remove(11).unwrap(), 2);
assert_eq!(sparse_set.sparse[0].as_ref().unwrap().1, 1);
assert_eq!(sparse_set.keys(), [SPARSE_PAGESIZE + 2, SPARSE_PAGESIZE + 1, 12, SPARSE_PAGESIZE]);
assert_eq!(
sparse_set.keys(),
[
SPARSE_PAGESIZE + 2,
SPARSE_PAGESIZE + 1,
12,
SPARSE_PAGESIZE
]
);
assert_eq!(sparse_set.values(), [3, 2, 2, 1]);
assert_eq!(sparse_set.remove(12).unwrap(), 2);
assert!(sparse_set.sparse[0].is_none());
assert_eq!(sparse_set.keys(), [SPARSE_PAGESIZE + 2, SPARSE_PAGESIZE + 1, SPARSE_PAGESIZE]);
assert_eq!(
sparse_set.keys(),
[SPARSE_PAGESIZE + 2, SPARSE_PAGESIZE + 1, SPARSE_PAGESIZE]
);
assert_eq!(sparse_set.values(), [3, 2, 1]);
}
}