diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/thunderdome/src | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/thunderdome/src')
-rw-r--r-- | third_party/rust/thunderdome/src/arena.rs | 478 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/drain.rs | 96 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/free_pointer.rs | 46 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/generation.rs | 61 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/into_iter.rs | 79 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/iter.rs | 82 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/iter_mut.rs | 82 | ||||
-rw-r--r-- | third_party/rust/thunderdome/src/lib.rs | 90 |
8 files changed, 1014 insertions, 0 deletions
diff --git a/third_party/rust/thunderdome/src/arena.rs b/third_party/rust/thunderdome/src/arena.rs new file mode 100644 index 0000000000..7125266071 --- /dev/null +++ b/third_party/rust/thunderdome/src/arena.rs @@ -0,0 +1,478 @@ +use std::convert::TryInto; +use std::mem::replace; +use std::ops; + +use crate::drain::Drain; +use crate::free_pointer::FreePointer; +use crate::generation::Generation; +use crate::into_iter::IntoIter; +use crate::iter::Iter; +use crate::iter_mut::IterMut; + +/// Container that can have elements inserted into it and removed from it. +/// +/// Indices use the [`Index`][Index] type, created by inserting values with +/// [`Arena::insert`][Arena::insert]. +#[derive(Debug, Clone)] +pub struct Arena<T> { + storage: Vec<Entry<T>>, + len: u32, + first_free: Option<FreePointer>, +} + +/// Index type for [`Arena`][Arena] that has a generation attached to it. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Index { + pub(crate) slot: u32, + pub(crate) generation: Generation, +} + +impl Index { + /// Convert this `Index` to an equivalent `u64` representation. Mostly + /// useful for passing to code outside of Rust. + #[allow(clippy::integer_arithmetic)] + pub fn to_bits(self) -> u64 { + // This is safe because a `u32` bit-shifted by 32 will still fit in a `u64`. + ((self.generation.to_u32() as u64) << 32) | (self.slot as u64) + } + + /// Convert back from a value generated with `Index::to_bits`. Don't call + /// this with arbitrary inputs; you'll almost certainly just get invalid + /// and/or malformed indices. + /// + /// If fed an index which was not generated by thunderdome or even just run + /// `Index::from_bits(0)`, this function may panic! + #[allow(clippy::integer_arithmetic)] + pub fn from_bits(bits: u64) -> Self { + // By bit-shifting right by 32, we're undoing the left-shift in `to_bits` + // thus this is okay by the same rationale. + let generation = Generation::from_u32((bits >> 32) as u32); + let slot = bits as u32; + + Self { generation, slot } + } +} + +#[derive(Debug, Clone)] +pub(crate) enum Entry<T> { + Occupied(OccupiedEntry<T>), + Empty(EmptyEntry), +} + +impl<T> Entry<T> { + /// Consume the entry, and if it's occupied, return the value. + fn into_value(self) -> Option<T> { + match self { + Entry::Occupied(occupied) => Some(occupied.value), + Entry::Empty(_) => None, + } + } + + /// If the entry is empty, return a copy of the emptiness data. + fn get_empty(&self) -> Option<EmptyEntry> { + match self { + Entry::Empty(empty) => Some(*empty), + Entry::Occupied(_) => None, + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct OccupiedEntry<T> { + pub(crate) generation: Generation, + pub(crate) value: T, +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct EmptyEntry { + pub(crate) generation: Generation, + pub(crate) next_free: Option<FreePointer>, +} + +impl<T> Arena<T> { + /// Construct an empty arena. + pub fn new() -> Self { + Self { + storage: Vec::new(), + len: 0, + first_free: None, + } + } + + /// Construct an empty arena with space to hold exactly `capacity` elements + /// without reallocating. + pub fn with_capacity(capacity: usize) -> Self { + Self { + storage: Vec::with_capacity(capacity), + len: 0, + first_free: None, + } + } + + /// Return the number of elements contained in the arena. + pub fn len(&self) -> usize { + self.len as usize + } + + /// Return the number of elements the arena can hold without allocating, + /// including the elements currently in the arena. + pub fn capacity(&self) -> usize { + self.storage.capacity() + } + + /// Returns whether the arena is empty. + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Insert a new value into the arena, returning an index that can be used + /// to later retrieve the value. + pub fn insert(&mut self, value: T) -> Index { + // This value will definitely be inserted, so we can update length now. + self.len = self + .len + .checked_add(1) + .unwrap_or_else(|| panic!("Cannot insert more than u32::MAX elements into Arena")); + + // If there was a previously free entry, we can re-use its slot as long + // as we increment its generation. + if let Some(free_pointer) = self.first_free { + let slot = free_pointer.slot(); + let entry = self.storage.get_mut(slot as usize).unwrap_or_else(|| { + unreachable!("first_free pointed past the end of the arena's storage") + }); + + let empty = entry + .get_empty() + .unwrap_or_else(|| unreachable!("first_free pointed to an occupied entry")); + + // If there is another empty entry after this one, we'll update the + // arena to point to it to use it on the next insertion. + self.first_free = empty.next_free; + + // Overwrite the entry directly using our mutable reference instead + // of indexing into our storage again. This should avoid an + // additional bounds check. + let generation = empty.generation.next(); + *entry = Entry::Occupied(OccupiedEntry { generation, value }); + + Index { slot, generation } + } else { + // There were no more empty entries left in our free list, so we'll + // create a new first-generation entry and push it into storage. + + let generation = Generation::first(); + let slot: u32 = self.storage.len().try_into().unwrap_or_else(|_| { + unreachable!("Arena storage exceeded what can be represented by a u32") + }); + + self.storage + .push(Entry::Occupied(OccupiedEntry { generation, value })); + + Index { slot, generation } + } + } + + /// Get an immutable reference to a value inside the arena by + /// [`Index`][Index], returning `None` if the index is not contained in the + /// arena. + pub fn get(&self, index: Index) -> Option<&T> { + match self.storage.get(index.slot as usize) { + Some(Entry::Occupied(occupied)) if occupied.generation == index.generation => { + Some(&occupied.value) + } + _ => None, + } + } + + /// Get a mutable reference to a value inside the arena by [`Index`][Index], + /// returning `None` if the index is not contained in the arena. + pub fn get_mut(&mut self, index: Index) -> Option<&mut T> { + match self.storage.get_mut(index.slot as usize) { + Some(Entry::Occupied(occupied)) if occupied.generation == index.generation => { + Some(&mut occupied.value) + } + _ => None, + } + } + + /// Remove the value contained at the given index from the arena, returning + /// it if it was present. + pub fn remove(&mut self, index: Index) -> Option<T> { + let entry = self.storage.get_mut(index.slot as usize)?; + + match entry { + Entry::Occupied(occupied) if occupied.generation == index.generation => { + // We can replace an occupied entry with an empty entry with the + // same generation. On next insertion, this generation will + // increment. + let new_entry = Entry::Empty(EmptyEntry { + generation: occupied.generation, + next_free: self.first_free, + }); + + // Swap our new entry into our storage and take ownership of the + // old entry. We'll consume it for its value so we can give that + // back to our caller. + let old_entry = replace(entry, new_entry); + let value = old_entry.into_value().unwrap_or_else(|| unreachable!()); + + // The next time we insert, we can re-use the empty entry we + // just created. If another removal happens before then, that + // entry will be used before this one (FILO). + self.first_free = Some(FreePointer::from_slot(index.slot)); + + self.len = self.len.checked_sub(1).unwrap_or_else(|| unreachable!()); + + Some(value) + } + _ => None, + } + } + + /// Invalidate the given index and return a new index to the same value. This + /// is roughly equivalent to `remove` followed by `insert`, but much faster. + /// If the old index is already invalid, this method returns `None`. + pub fn invalidate(&mut self, index: Index) -> Option<Index> { + let entry = self.storage.get_mut(index.slot as usize)?; + + match entry { + Entry::Occupied(occupied) if occupied.generation == index.generation => { + occupied.generation = occupied.generation.next(); + + Some(Index { + generation: occupied.generation, + ..index + }) + } + _ => None, + } + } + + /// Clear the arena and drop all elements. + pub fn clear(&mut self) { + self.drain().for_each(drop); + } + + /// Iterate over all of the indexes and values contained in the arena. + /// + /// Iteration order is not defined. + pub fn iter(&self) -> Iter<'_, T> { + Iter { + inner: self.storage.iter().enumerate(), + len: self.len, + } + } + + /// Iterate over all of the indexes and values contained in the arena, with + /// mutable access to each value. + /// + /// Iteration order is not defined. + pub fn iter_mut(&mut self) -> IterMut<'_, T> { + IterMut { + inner: self.storage.iter_mut().enumerate(), + len: self.len, + } + } + + /// Returns an iterator that removes each element from the arena. + /// + /// Iteration order is not defined. + /// + /// If the iterator is dropped before it is fully consumed, any uniterated + /// items will be dropped from the arena, and the arena will be empty. + /// The arena's capacity will not be changed. + pub fn drain(&mut self) -> Drain<'_, T> { + Drain { + arena: self, + slot: 0, + } + } +} + +/// Methods exposed only within the crate. +impl<T> Arena<T> { + /// This method is a lot like `remove`, but takes no generation. It's used + /// as part of `drain` and can likely be exposed as a public API eventually. + pub(crate) fn remove_entry_by_slot(&mut self, slot: u32) -> Option<(Index, T)> { + let entry = self.storage.get_mut(slot as usize)?; + + match entry { + Entry::Occupied(occupied) => { + // Construct the index that would be used to access this entry. + let index = Index { + generation: occupied.generation, + slot, + }; + + // This occupied entry will be replaced with an empty one of the + // same generation. Generation will be incremented on the next + // insert. + let next_entry = Entry::Empty(EmptyEntry { + generation: occupied.generation, + next_free: self.first_free, + }); + + // Swap new entry into place and consume the old one. + let old_entry = replace(entry, next_entry); + let value = old_entry.into_value().unwrap_or_else(|| unreachable!()); + + // Set this entry as the next one that should be inserted into, + // should an insertion happen. + self.first_free = Some(FreePointer::from_slot(slot)); + + self.len = self.len.checked_sub(1).unwrap_or_else(|| unreachable!()); + + Some((index, value)) + } + _ => None, + } + } +} + +impl<T> Default for Arena<T> { + fn default() -> Self { + Arena::new() + } +} + +impl<T> IntoIterator for Arena<T> { + type Item = (Index, T); + type IntoIter = IntoIter<T>; + + fn into_iter(self) -> Self::IntoIter { + IntoIter { + arena: self, + slot: 0, + } + } +} + +impl<T> ops::Index<Index> for Arena<T> { + type Output = T; + + fn index(&self, index: Index) -> &Self::Output { + self.get(index) + .unwrap_or_else(|| panic!("No entry at index {:?}", index)) + } +} + +impl<T> ops::IndexMut<Index> for Arena<T> { + fn index_mut(&mut self, index: Index) -> &mut Self::Output { + self.get_mut(index) + .unwrap_or_else(|| panic!("No entry at index {:?}", index)) + } +} + +#[cfg(test)] +mod test { + use super::{Arena, Index}; + + use std::mem::size_of; + + #[test] + fn size_of_index() { + assert_eq!(size_of::<Index>(), 8); + assert_eq!(size_of::<Option<Index>>(), 8); + } + + #[test] + fn new() { + let arena: Arena<u32> = Arena::new(); + assert_eq!(arena.len(), 0); + assert_eq!(arena.capacity(), 0); + } + + #[test] + fn with_capacity() { + let arena: Arena<u32> = Arena::with_capacity(8); + assert_eq!(arena.len(), 0); + assert_eq!(arena.capacity(), 8); + } + + #[test] + fn insert_and_get() { + let mut arena = Arena::new(); + + let one = arena.insert(1); + assert_eq!(arena.len(), 1); + assert_eq!(arena.get(one), Some(&1)); + + let two = arena.insert(2); + assert_eq!(arena.len(), 2); + assert_eq!(arena.get(one), Some(&1)); + assert_eq!(arena.get(two), Some(&2)); + } + + #[test] + fn insert_remove_get() { + let mut arena = Arena::new(); + let one = arena.insert(1); + + let two = arena.insert(2); + assert_eq!(arena.len(), 2); + assert_eq!(arena.remove(two), Some(2)); + + let three = arena.insert(3); + assert_eq!(arena.len(), 2); + assert_eq!(arena.get(one), Some(&1)); + assert_eq!(arena.get(three), Some(&3)); + assert_eq!(arena.get(two), None); + } + + #[test] + fn get_mut() { + let mut arena = Arena::new(); + let foo = arena.insert(5); + + let handle = arena.get_mut(foo).unwrap(); + *handle = 6; + + assert_eq!(arena.get(foo), Some(&6)); + } + + #[test] + fn insert_remove_insert_capacity() { + let mut arena = Arena::with_capacity(2); + assert_eq!(arena.capacity(), 2); + + let a = arena.insert("a"); + let b = arena.insert("b"); + assert_eq!(arena.len(), 2); + assert_eq!(arena.capacity(), 2); + + arena.remove(a); + arena.remove(b); + assert_eq!(arena.len(), 0); + assert_eq!(arena.capacity(), 2); + + let _a2 = arena.insert("a2"); + let _b2 = arena.insert("b2"); + assert_eq!(arena.len(), 2); + assert_eq!(arena.capacity(), 2); + } + + #[test] + fn invalidate() { + let mut arena = Arena::new(); + + let a = arena.insert("a"); + assert_eq!(arena.get(a), Some(&"a")); + + let new_a = arena.invalidate(a).unwrap(); + assert_eq!(arena.get(a), None); + assert_eq!(arena.get(new_a), Some(&"a")); + } + + #[test] + fn index_bits_roundtrip() { + let index = Index::from_bits(0x1BADCAFE_DEADBEEF); + assert_eq!(index.to_bits(), 0x1BADCAFE_DEADBEEF); + } + + #[test] + #[should_panic] + fn index_bits_panic_on_zero_generation() { + Index::from_bits(0x00000000_DEADBEEF); + } +} diff --git a/third_party/rust/thunderdome/src/drain.rs b/third_party/rust/thunderdome/src/drain.rs new file mode 100644 index 0000000000..930c0962fa --- /dev/null +++ b/third_party/rust/thunderdome/src/drain.rs @@ -0,0 +1,96 @@ +use std::iter::{ExactSizeIterator, FusedIterator}; + +use crate::arena::{Arena, Index}; + +/// See [`Arena::drain`][Arena::drain]. +pub struct Drain<'a, T> { + pub(crate) arena: &'a mut Arena<T>, + pub(crate) slot: u32, +} + +impl<'a, T> Iterator for Drain<'a, T> { + type Item = (Index, T); + + fn next(&mut self) -> Option<Self::Item> { + loop { + // If there are no entries remaining in the arena, we should always + // return None. Using this check instead of comparing with the + // arena's size allows us to skip any trailing empty entries. + if self.arena.is_empty() { + return None; + } + + // slot may overflow if the arena's underlying storage contains more + // than 2^32 elements, but its internal length value was not + // changed, as it overflowing would panic before reaching this code. + let slot = self.slot; + self.slot = self + .slot + .checked_add(1) + .unwrap_or_else(|| panic!("Overflowed u32 trying to drain Arena")); + + // If this entry is occupied, this method will mark it as an empty. + // Otherwise, we'll continue looping until we've drained all + // occupied entries from the arena. + if let Some((index, value)) = self.arena.remove_entry_by_slot(slot) { + return Some((index, value)); + } + } + } + + fn size_hint(&self) -> (usize, Option<usize>) { + (self.arena.len(), Some(self.arena.len())) + } +} + +impl<'a, T> FusedIterator for Drain<'a, T> {} +impl<'a, T> ExactSizeIterator for Drain<'a, T> {} + +impl<'a, T> Drop for Drain<'a, T> { + // Continue iterating/dropping if there are any elements left. + fn drop(&mut self) { + self.for_each(drop); + } +} + +#[cfg(test)] +mod test { + use crate::Arena; + + use std::collections::HashSet; + + #[test] + fn drain() { + let mut arena = Arena::with_capacity(2); + let one = arena.insert(1); + let two = arena.insert(2); + + let mut drained_pairs = HashSet::new(); + { + let mut drain = arena.drain(); + assert_eq!(drain.size_hint(), (2, Some(2))); + + drained_pairs.insert(drain.next().unwrap()); + assert_eq!(drain.size_hint(), (1, Some(1))); + + // Do not fully drain so we can ensure everything is dropped when the + // `Drain` is dropped. + assert_eq!(drain.size_hint(), (1, Some(1))); + } + + assert_eq!(arena.len(), 0); + assert_eq!(arena.capacity(), 2); + assert_eq!(drained_pairs.len(), 1); + + // We should still be able to use the arena after this. + let one_prime = arena.insert(1); + let two_prime = arena.insert(2); + + assert_eq!(arena.len(), 2); + assert_eq!(arena.capacity(), 2); + assert_eq!(arena.get(one_prime), Some(&1)); + assert_eq!(arena.get(two_prime), Some(&2)); + assert_eq!(arena.get(one), None); + assert_eq!(arena.get(two), None); + } +} diff --git a/third_party/rust/thunderdome/src/free_pointer.rs b/third_party/rust/thunderdome/src/free_pointer.rs new file mode 100644 index 0000000000..e0d5f4ae8a --- /dev/null +++ b/third_party/rust/thunderdome/src/free_pointer.rs @@ -0,0 +1,46 @@ +use std::num::NonZeroU32;
+
+/// Contains a reference to a free slot in an arena, encapsulating NonZeroU32
+/// to prevent off-by-one errors and leaking unsafety.
+///
+/// Uses NonZeroU32 to stay small when put inside an `Option`.
+#[derive(Debug, Clone, Copy)]
+#[repr(transparent)]
+pub(crate) struct FreePointer(NonZeroU32);
+
+impl FreePointer {
+ #[must_use]
+ pub(crate) fn from_slot(slot: u32) -> Self {
+ let value = slot
+ .checked_add(1)
+ .expect("u32 overflowed calculating free pointer from u32");
+
+ // This is safe because any u32 + 1 that didn't overflow must not be
+ // zero.
+ FreePointer(unsafe { NonZeroU32::new_unchecked(value) })
+ }
+
+ #[must_use]
+ #[allow(clippy::integer_arithmetic)]
+ pub(crate) fn slot(self) -> u32 {
+ // This will never underflow due to the field being guaranteed non-zero.
+ self.0.get() - 1
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::FreePointer;
+
+ #[test]
+ fn from_slot() {
+ let ptr = FreePointer::from_slot(0);
+ assert_eq!(ptr.slot(), 0);
+ }
+
+ #[test]
+ #[should_panic(expected = "u32 overflowed calculating free pointer from u32")]
+ fn panic_on_overflow() {
+ let _ = FreePointer::from_slot(std::u32::MAX);
+ }
+}
diff --git a/third_party/rust/thunderdome/src/generation.rs b/third_party/rust/thunderdome/src/generation.rs new file mode 100644 index 0000000000..e6982c383c --- /dev/null +++ b/third_party/rust/thunderdome/src/generation.rs @@ -0,0 +1,61 @@ +use std::num::NonZeroU32; + +/// Tracks the generation of an entry in an arena. Encapsulates NonZeroU32 to +/// reduce the number of redundant checks needed, as well as enforcing checked +/// arithmetic on advancing a generation. +/// +/// Uses NonZeroU32 to help `Index` stay the same size when put inside an +/// `Option`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[repr(transparent)] +pub(crate) struct Generation(NonZeroU32); + +impl Generation { + #[must_use] + pub(crate) fn first() -> Self { + // This is safe because 1 is not zero. + Generation(unsafe { NonZeroU32::new_unchecked(1) }) + } + + #[must_use] + pub(crate) fn next(self) -> Self { + let last_generation = self.0.get(); + let next_generation = last_generation.checked_add(1).unwrap_or(1); + + // This is safe because value that would overflow is instead made 1. + Generation(unsafe { NonZeroU32::new_unchecked(next_generation) }) + } + + pub(crate) fn to_u32(self) -> u32 { + self.0.get() + } + + pub(crate) fn from_u32(gen: u32) -> Self { + Generation(NonZeroU32::new(gen).expect("generation IDs must be nonzero!")) + } +} + +#[cfg(test)] +mod test { + use super::Generation; + + use std::num::NonZeroU32; + + #[test] + fn first_and_next() { + let first = Generation::first(); + assert_eq!(first.0.get(), 1); + + let second = first.next(); + assert_eq!(second.0.get(), 2); + } + + #[test] + fn wrap_on_overflow() { + let max = Generation(NonZeroU32::new(std::u32::MAX).unwrap()); + assert_eq!(max.0.get(), std::u32::MAX); + + let next = max.next(); + assert_eq!(next.0.get(), 1); + } +} diff --git a/third_party/rust/thunderdome/src/into_iter.rs b/third_party/rust/thunderdome/src/into_iter.rs new file mode 100644 index 0000000000..e7dd0d9403 --- /dev/null +++ b/third_party/rust/thunderdome/src/into_iter.rs @@ -0,0 +1,79 @@ +use std::iter::{ExactSizeIterator, FusedIterator};
+
+use crate::arena::{Arena, Index};
+
+/// Iterator typed used when an Arena is turned [`IntoIterator`][IntoIterator].
+pub struct IntoIter<T> {
+ pub(crate) arena: Arena<T>,
+ pub(crate) slot: u32,
+}
+
+impl<T> Iterator for IntoIter<T> {
+ type Item = (Index, T);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ // If there are no entries remaining in the arena, we should always
+ // return None. Using this check instead of comparing with the
+ // arena's size allows us to skip any trailing empty entries.
+ if self.arena.is_empty() {
+ return None;
+ }
+
+ // slot may overflow if the arena's underlying storage contains more
+ // than 2^32 elements, but its internal length value was not
+ // changed, as it overflowing would panic before reaching this code.
+ let slot = self.slot;
+ self.slot = self
+ .slot
+ .checked_add(1)
+ .unwrap_or_else(|| panic!("Overflowed u32 trying to into_iter Arena"));
+
+ // If this entry is occupied, this method will mark it as an empty.
+ // Otherwise, we'll continue looping until we've removed all
+ // occupied entries from the arena.
+ if let Some((index, value)) = self.arena.remove_entry_by_slot(slot) {
+ return Some((index, value));
+ }
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.arena.len(), Some(self.arena.len()))
+ }
+}
+
+impl<T> FusedIterator for IntoIter<T> {}
+impl<T> ExactSizeIterator for IntoIter<T> {}
+
+#[cfg(test)]
+mod test {
+ use crate::Arena;
+
+ use std::collections::HashSet;
+
+ #[test]
+ fn into_iter() {
+ let mut arena = Arena::with_capacity(2);
+ let one = arena.insert(1);
+ let two = arena.insert(2);
+
+ let mut pairs = HashSet::new();
+ let mut into_iter = arena.into_iter();
+ assert_eq!(into_iter.size_hint(), (2, Some(2)));
+
+ pairs.insert(into_iter.next().unwrap());
+ assert_eq!(into_iter.size_hint(), (1, Some(1)));
+
+ pairs.insert(into_iter.next().unwrap());
+ assert_eq!(into_iter.size_hint(), (0, Some(0)));
+
+ assert_eq!(into_iter.next(), None);
+ assert_eq!(into_iter.next(), None);
+ assert_eq!(into_iter.size_hint(), (0, Some(0)));
+
+ assert_eq!(pairs.len(), 2);
+ assert!(pairs.contains(&(one, 1)));
+ assert!(pairs.contains(&(two, 2)));
+ }
+}
diff --git a/third_party/rust/thunderdome/src/iter.rs b/third_party/rust/thunderdome/src/iter.rs new file mode 100644 index 0000000000..0a2404cae7 --- /dev/null +++ b/third_party/rust/thunderdome/src/iter.rs @@ -0,0 +1,82 @@ +use std::convert::TryInto;
+use std::iter::{Enumerate, ExactSizeIterator, FusedIterator};
+use std::slice;
+
+use crate::arena::{Entry, Index};
+
+/// See [`Arena::iter`][Arena::iter].
+pub struct Iter<'a, T> {
+ pub(crate) len: u32,
+ pub(crate) inner: Enumerate<slice::Iter<'a, Entry<T>>>,
+}
+
+impl<'a, T> Iterator for Iter<'a, T> {
+ type Item = (Index, &'a T);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ if self.len == 0 {
+ return None;
+ }
+
+ match self.inner.next()? {
+ (_, Entry::Empty(_)) => continue,
+ (slot, Entry::Occupied(occupied)) => {
+ self.len = self
+ .len
+ .checked_sub(1)
+ .unwrap_or_else(|| unreachable!("Underflowed u32 trying to iterate Arena"));
+
+ let slot: u32 = slot
+ .try_into()
+ .unwrap_or_else(|_| unreachable!("Overflowed u32 trying to iterate Arena"));
+
+ let index = Index {
+ slot,
+ generation: occupied.generation,
+ };
+
+ return Some((index, &occupied.value));
+ }
+ }
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.len as usize, Some(self.len as usize))
+ }
+}
+
+impl<'a, T> FusedIterator for Iter<'a, T> {}
+impl<'a, T> ExactSizeIterator for Iter<'a, T> {}
+
+#[cfg(test)]
+mod test {
+ use crate::Arena;
+
+ use std::collections::HashSet;
+
+ #[test]
+ fn iter() {
+ let mut arena = Arena::with_capacity(2);
+ let one = arena.insert(1);
+ let two = arena.insert(2);
+
+ let mut pairs = HashSet::new();
+ let mut iter = arena.iter();
+ assert_eq!(iter.size_hint(), (2, Some(2)));
+
+ pairs.insert(iter.next().unwrap());
+ assert_eq!(iter.size_hint(), (1, Some(1)));
+
+ pairs.insert(iter.next().unwrap());
+ assert_eq!(iter.size_hint(), (0, Some(0)));
+
+ assert_eq!(iter.next(), None);
+ assert_eq!(iter.next(), None);
+ assert_eq!(iter.size_hint(), (0, Some(0)));
+
+ assert!(pairs.contains(&(one, &1)));
+ assert!(pairs.contains(&(two, &2)));
+ }
+}
diff --git a/third_party/rust/thunderdome/src/iter_mut.rs b/third_party/rust/thunderdome/src/iter_mut.rs new file mode 100644 index 0000000000..7ff71c12e3 --- /dev/null +++ b/third_party/rust/thunderdome/src/iter_mut.rs @@ -0,0 +1,82 @@ +use std::convert::TryInto;
+use std::iter::{Enumerate, ExactSizeIterator, FusedIterator};
+use std::slice;
+
+use crate::arena::{Entry, Index};
+
+/// See [`Arena::iter_mut`][Arena::iter_mut].
+pub struct IterMut<'a, T> {
+ pub(crate) len: u32,
+ pub(crate) inner: Enumerate<slice::IterMut<'a, Entry<T>>>,
+}
+
+impl<'a, T> Iterator for IterMut<'a, T> {
+ type Item = (Index, &'a T);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ if self.len == 0 {
+ return None;
+ }
+
+ match self.inner.next()? {
+ (_, Entry::Empty(_)) => continue,
+ (slot, Entry::Occupied(occupied)) => {
+ self.len = self
+ .len
+ .checked_sub(1)
+ .unwrap_or_else(|| unreachable!("Underflowed u32 trying to iterate Arena"));
+
+ let slot: u32 = slot
+ .try_into()
+ .unwrap_or_else(|_| unreachable!("Overflowed u32 trying to iterate Arena"));
+
+ let index = Index {
+ slot,
+ generation: occupied.generation,
+ };
+
+ return Some((index, &mut occupied.value));
+ }
+ }
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.len as usize, Some(self.len as usize))
+ }
+}
+
+impl<'a, T> FusedIterator for IterMut<'a, T> {}
+impl<'a, T> ExactSizeIterator for IterMut<'a, T> {}
+
+#[cfg(test)]
+mod test {
+ use crate::Arena;
+
+ use std::collections::HashSet;
+
+ #[test]
+ fn iter_mut() {
+ let mut arena = Arena::with_capacity(2);
+ let one = arena.insert(1);
+ let two = arena.insert(2);
+
+ let mut pairs = HashSet::new();
+ let mut iter = arena.iter_mut();
+ assert_eq!(iter.size_hint(), (2, Some(2)));
+
+ pairs.insert(iter.next().unwrap());
+ assert_eq!(iter.size_hint(), (1, Some(1)));
+
+ pairs.insert(iter.next().unwrap());
+ assert_eq!(iter.size_hint(), (0, Some(0)));
+
+ assert_eq!(iter.next(), None);
+ assert_eq!(iter.next(), None);
+ assert_eq!(iter.size_hint(), (0, Some(0)));
+
+ assert!(pairs.contains(&(one, &1)));
+ assert!(pairs.contains(&(two, &2)));
+ }
+}
diff --git a/third_party/rust/thunderdome/src/lib.rs b/third_party/rust/thunderdome/src/lib.rs new file mode 100644 index 0000000000..08bd29e07e --- /dev/null +++ b/third_party/rust/thunderdome/src/lib.rs @@ -0,0 +1,90 @@ +/*! +[![GitHub CI Status](https://github.com/LPGhatguy/thunderdome/workflows/CI/badge.svg)](https://github.com/LPGhatguy/thunderdome/actions) +[![thunderdome on crates.io](https://img.shields.io/crates/v/thunderdome.svg)](https://crates.io/crates/thunderdome) +[![thunderdome docs](https://img.shields.io/badge/docs-docs.rs-orange.svg)](https://docs.rs/thunderdome) + +Thunderdome is a ~gladitorial~ generational arena inspired by +[generational-arena](https://crates.io/crates/generational-arena), +[slotmap](https://crates.io/crates/slotmap), and +[slab](https://crates.io/crates/slab). It provides constant time insertion, +lookup, and removal via small (8 byte) keys returned from `Arena`. + +Thunderdome's key type, `Index`, is still 8 bytes when put inside of an +`Option<T>` thanks to Rust's `NonZero*` types. + +## Basic Examples + +```rust +# use thunderdome::{Arena, Index}; +let mut arena = Arena::new(); + +let foo = arena.insert("Foo"); +let bar = arena.insert("Bar"); + +assert_eq!(arena[foo], "Foo"); +assert_eq!(arena[bar], "Bar"); + +arena[bar] = "Replaced"; +assert_eq!(arena[bar], "Replaced"); + +let foo_value = arena.remove(foo); +assert_eq!(foo_value, Some("Foo")); + +// The slot previously used by foo will be reused for baz +let baz = arena.insert("Baz"); +assert_eq!(arena[baz], "Baz"); + +// foo is no longer a valid key +assert_eq!(arena.get(foo), None); +``` + +## Comparison With Similar Crates + +| Feature | Thunderdome | generational-arena | slotmap | slab | +|------------------------------|-------------|--------------------|---------|------| +| Generational Indices¹ | Yes | Yes | Yes | No | +| `size_of::<Index>()` | 8 | 16 | 8 | 8 | +| `size_of::<Option<Index>>()` | 8 | 24 | 8 | 16 | +| Max Elements | 2³² | 2⁶⁴ | 2³² | 2⁶⁴ | +| Non-`Copy` Values | Yes | Yes | Sorta² | Yes | +| `no_std` Support | No | Yes | No | No | +| Serde Support | No | Yes | Yes | No | + +* Sizes calculated on rustc `1.44.0-x86_64-pc-windows-msvc` +* See [the Thunderdome comparison + Cargo.toml](https://github.com/LPGhatguy/thunderdome/blob/main/comparison/Cargo.toml) + for versions of each library tested. + +1. Generational indices help solve the [ABA + Problem](https://en.wikipedia.org/wiki/ABA_problem), which can cause dangling + keys to mistakenly access newly-inserted data. +2. slotmap's `SlotMap` and `HopSlotMap` require values to be `Copy` on stable + Rust versions. slotmap's `DenseSlotMap` type supports non-`Copy` types on + stable, but has different performance trade-offs. + +## Minimum Supported Rust Version (MSRV) + +Thunderdome supports Rust 1.34.1 and newer. Until Thunderdome reaches 1.0, +changes to the MSRV will require major version bumps. After 1.0, MSRV changes +will only require minor version bumps, but will need significant justification. +*/ + +#![forbid(missing_docs)] +// This crate is sensitive to integer overflow and wrapping behavior. As such, +// we should usually use methods like `checked_add` and `checked_sub` instead +// of the `Add` or `Sub` operators. +#![deny(clippy::integer_arithmetic)] + +mod arena; +mod drain; +mod free_pointer; +mod generation; +mod into_iter; +mod iter; +mod iter_mut; + +pub use crate::arena::{Arena, Index}; +pub use crate::drain::Drain; +pub use crate::into_iter::IntoIter; +pub use crate::iter::Iter; +pub use crate::iter_mut::IterMut; |