From 32caf056bf3241feac423388b73f5a3d5c489197 Mon Sep 17 00:00:00 2001 From: Christian Eltzschig Date: Thu, 7 Nov 2024 20:42:32 +0100 Subject: [PATCH] [#504] Finish SlotMap tests --- iceoryx2-bb/container/src/slotmap.rs | 208 +++++++++++++++---- iceoryx2-bb/container/src/vec.rs | 2 +- iceoryx2-bb/container/tests/slotmap_tests.rs | 97 ++++++++- iceoryx2-bb/memory/src/pool_allocator.rs | 1 - 4 files changed, 265 insertions(+), 43 deletions(-) diff --git a/iceoryx2-bb/container/src/slotmap.rs b/iceoryx2-bb/container/src/slotmap.rs index 2ba964714..3f66e8106 100644 --- a/iceoryx2-bb/container/src/slotmap.rs +++ b/iceoryx2-bb/container/src/slotmap.rs @@ -22,14 +22,32 @@ use iceoryx2_bb_elementary::relocatable_ptr::RelocatablePointer; use iceoryx2_bb_log::fail; use std::mem::MaybeUninit; +/// A key of a [`SlotMap`], [`RelocatableSlotMap`] or [`FixedSizeSlotMap`] that identifies a +/// value. #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)] pub struct SlotMapKey(usize); +impl SlotMapKey { + /// Creates a new [`SlotMapKey`] with the specified value. + pub fn new(value: usize) -> Self { + Self(value) + } + + /// Returns the underlying value of the [`SlotMapKey`]. + pub fn value(&self) -> usize { + self.0 + } +} + +/// A runtime fixed-size, non-shared memory compatible [`SlotMap`]. The [`SlotMap`]s memory resides +/// in the heap. pub type SlotMap = details::MetaSlotMap< T, OwningPointer>>, OwningPointer>, >; + +/// A runtime fixed-size, shared-memory compatible [`RelocatableSlotMap`]. pub type RelocatableSlotMap = details::MetaSlotMap< T, RelocatablePointer>>, @@ -42,6 +60,7 @@ const INVALID_KEY: usize = usize::MAX; pub mod details { use super::*; + /// The iterator of a [`SlotMap`], [`RelocatableSlotMap`] or [`FixedSizeSlotMap`]. pub struct Iter< 'slotmap, T, @@ -52,6 +71,15 @@ pub mod details { key: SlotMapKey, } + pub type OwningIter<'slotmap, T> = + Iter<'slotmap, T, OwningPointer>>, OwningPointer>>; + pub type RelocatableIter<'slotmap, T> = Iter< + 'slotmap, + T, + RelocatablePointer>>, + RelocatablePointer>, + >; + impl< 'slotmap, T, @@ -61,7 +89,7 @@ pub mod details { { type Item = (SlotMapKey, &'slotmap T); - fn next<'this>(&'this mut self) -> Option { + fn next(&mut self) -> Option { if let Some((key, value)) = self.slotmap.next(self.key) { self.key.0 = key.0 + 1; Some((key, value)) @@ -109,9 +137,9 @@ pub mod details { } pub(crate) unsafe fn initialize_data_structures(&mut self) { - self.idx_to_data.fill(INVALID_KEY); - self.data.fill_with(|| None); for n in 0..self.capacity_impl() { + self.idx_to_data.push_impl(INVALID_KEY); + self.data.push_impl(None); self.idx_to_data_next_free_index.push_impl(n); self.data_next_free_index.push_impl(n); } @@ -124,6 +152,10 @@ pub mod details { } } + pub(crate) unsafe fn contains_impl(&self, key: SlotMapKey) -> bool { + self.idx_to_data[key.0] != INVALID_KEY + } + pub(crate) unsafe fn get_impl(&self, key: SlotMapKey) -> Option<&T> { match self.idx_to_data[key.0] { INVALID_KEY => None, @@ -161,13 +193,13 @@ pub mod details { let data_idx = self.idx_to_data[key.0]; if data_idx != INVALID_KEY { self.data[data_idx] = Some(value); - true } else { let n = self.data_next_free_index.pop_impl().expect("data and idx_to_data correspond and there must be always a free index available."); self.idx_to_data[key.0] = n; self.data[n] = Some(value); - false } + + true } pub(crate) unsafe fn remove_impl(&mut self, key: SlotMapKey) -> bool { @@ -187,19 +219,19 @@ pub mod details { } } - pub(crate) unsafe fn len_impl(&self) -> usize { + pub(crate) fn len_impl(&self) -> usize { self.capacity_impl() - self.idx_to_data_next_free_index.len() } - pub(crate) unsafe fn capacity_impl(&self) -> usize { + pub(crate) fn capacity_impl(&self) -> usize { self.idx_to_data.capacity() } - pub(crate) unsafe fn is_empty_impl(&self) -> bool { + pub(crate) fn is_empty_impl(&self) -> bool { self.len_impl() == 0 } - pub(crate) unsafe fn is_full_impl(&self) -> bool { + pub(crate) fn is_full_impl(&self) -> bool { self.len_impl() == self.capacity_impl() } } @@ -254,6 +286,8 @@ pub mod details { RelocatablePointer>, > { + /// Returns how many memory the [`RelocatableSlotMap`] will allocate from the allocator + /// in [`RelocatableSlotMap::init()`]. pub const fn const_memory_size(capacity: usize) -> usize { RelocatableVec::::const_memory_size(capacity) + RelocatableQueue::::const_memory_size(capacity) @@ -263,6 +297,7 @@ pub mod details { } impl MetaSlotMap>>, OwningPointer>> { + /// Creates a new runtime-fixed size [`SlotMap`] on the heap with the given capacity. pub fn new(capacity: usize) -> Self { let mut new_self = Self { idx_to_data: MetaVec::new(capacity), @@ -274,47 +309,65 @@ pub mod details { new_self } - pub fn iter( - &self, - ) -> Iter>>, OwningPointer>> - { + /// Returns the [`Iter`]ator to iterate over all entries. + pub fn iter(&self) -> OwningIter { unsafe { self.iter_impl() } } + /// Returns `true` if the provided `key` is contained, otherwise `false`. + pub fn contains(&self, key: SlotMapKey) -> bool { + unsafe { self.contains_impl(key) } + } + + /// Returns a reference to the value stored under the given key. If there is no such key, + /// [`None`] is returned. pub fn get(&self, key: SlotMapKey) -> Option<&T> { unsafe { self.get_impl(key) } } + /// Returns a mutable reference to the value stored under the given key. If there is no + /// such key, [`None`] is returned. pub fn get_mut(&mut self, key: SlotMapKey) -> Option<&mut T> { unsafe { self.get_mut_impl(key) } } + /// Insert a value and returns the corresponding [`SlotMapKey`]. If the container is full + /// [`None`] is returned. pub fn insert(&mut self, value: T) -> Option { unsafe { self.insert_impl(value) } } + /// Insert a value at the specified [`SlotMapKey`] and returns true. If the provided key + /// is out-of-bounds it returns `false` and adds nothing. If there is already a value + /// stored at the `key`s index, the value is overridden with the provided value. pub fn insert_at(&mut self, key: SlotMapKey, value: T) -> bool { unsafe { self.insert_at_impl(key, value) } } + /// Removes a value at the specified [`SlotMapKey`]. If there was no value corresponding + /// to the [`SlotMapKey`] it returns false, otherwise true. pub fn remove(&mut self, key: SlotMapKey) -> bool { unsafe { self.remove_impl(key) } } + /// Returns the number of stored values. pub fn len(&self) -> usize { - unsafe { self.len_impl() } + self.len_impl() } + /// Returns the capacity. pub fn capacity(&self) -> usize { - unsafe { self.capacity_impl() } + self.capacity_impl() } + /// Returns true if the container is empty, otherwise false. pub fn is_empty(&self) -> bool { - unsafe { self.is_empty_impl() } + self.is_empty_impl() } + /// Returns true if the container is full, otherwise false. pub fn is_full(&self) -> bool { - unsafe { self.is_full_impl() } + self.is_full_impl() } } @@ -325,54 +378,105 @@ pub mod details { RelocatablePointer>, > { - pub unsafe fn iter( - &self, - ) -> Iter< - T, - RelocatablePointer>>, - RelocatablePointer>, - > { + /// Returns the [`Iter`]ator to iterate over all entries. + /// + /// # Safety + /// + /// * [`RelocatableSlotMap::init()`] must be called once before + /// + pub unsafe fn iter(&self) -> RelocatableIter { self.iter_impl() } + /// Returns `true` if the provided `key` is contained, otherwise `false`. + /// + /// # Safety + /// + /// * [`RelocatableSlotMap::init()`] must be called once before + /// + pub unsafe fn contains(&self, key: SlotMapKey) -> bool { + self.contains_impl(key) + } + + /// Returns a reference to the value stored under the given key. If there is no such key, + /// [`None`] is returned. + /// + /// # Safety + /// + /// * [`RelocatableSlotMap::init()`] must be called once before + /// pub unsafe fn get(&self, key: SlotMapKey) -> Option<&T> { self.get_impl(key) } + /// Returns a mutable reference to the value stored under the given key. If there is no + /// such key, [`None`] is returned. + /// + /// # Safety + /// + /// * [`RelocatableSlotMap::init()`] must be called once before + /// pub unsafe fn get_mut(&mut self, key: SlotMapKey) -> Option<&mut T> { self.get_mut_impl(key) } + /// Insert a value and returns the corresponding [`SlotMapKey`]. If the container is full + /// [`None`] is returned. + /// + /// # Safety + /// + /// * [`RelocatableSlotMap::init()`] must be called once before + /// pub unsafe fn insert(&mut self, value: T) -> Option { self.insert_impl(value) } + /// Insert a value at the specified [`SlotMapKey`] and returns true. If the provided key + /// is out-of-bounds it returns `false` and adds nothing. If there is already a value + /// stored at the `key`s index, the value is overridden with the provided value. + /// + /// # Safety + /// + /// * [`RelocatableSlotMap::init()`] must be called once before + /// pub unsafe fn insert_at(&mut self, key: SlotMapKey, value: T) -> bool { self.insert_at_impl(key, value) } + /// Removes a value at the specified [`SlotMapKey`]. If there was no value corresponding + /// to the [`SlotMapKey`] it returns false, otherwise true. + /// + /// # Safety + /// + /// * [`RelocatableSlotMap::init()`] must be called once before + /// pub unsafe fn remove(&mut self, key: SlotMapKey) -> bool { self.remove_impl(key) } - pub unsafe fn len(&self) -> usize { + /// Returns the number of stored values. + pub fn len(&self) -> usize { self.len_impl() } - pub unsafe fn capacity(&self) -> usize { + /// Returns the capacity. + pub fn capacity(&self) -> usize { self.capacity_impl() } - pub unsafe fn is_empty(&self) -> bool { + /// Returns true if the container is empty, otherwise false. + pub fn is_empty(&self) -> bool { self.is_empty_impl() } - pub unsafe fn is_full(&self) -> bool { + /// Returns true if the container is full, otherwise false. + pub fn is_full(&self) -> bool { self.is_full_impl() } } } +/// A compile-time fixed-size, shared memory compatible [`FixedSizeSlotMap`]. #[repr(C)] #[derive(Debug)] pub struct FixedSizeSlotMap { @@ -384,7 +488,15 @@ pub struct FixedSizeSlotMap { } impl PlacementDefault for FixedSizeSlotMap { - unsafe fn placement_default(ptr: *mut Self) {} + unsafe fn placement_default(ptr: *mut Self) { + let state_ptr = core::ptr::addr_of_mut!((*ptr).state); + state_ptr.write(unsafe { RelocatableSlotMap::new_uninit(CAPACITY) }); + let allocator = BumpAllocator::new(core::ptr::addr_of!((*ptr)._data) as usize); + (*ptr) + .state + .init(&allocator) + .expect("All required memory is preallocated."); + } } impl Default for FixedSizeSlotMap { @@ -410,53 +522,69 @@ impl Default for FixedSizeSlotMap { } impl FixedSizeSlotMap { + /// Creates a new empty [`FixedSizeSlotMap`]. pub fn new() -> Self { Self::default() } - pub fn iter( - &self, - ) -> details::Iter< - T, - RelocatablePointer>>, - RelocatablePointer>, - > { + /// Returns the [`Iter`]ator to iterate over all entries. + pub fn iter(&self) -> details::RelocatableIter { unsafe { self.state.iter_impl() } } + /// Returns `true` if the provided `key` is contained, otherwise `false`. + pub fn contains(&self, key: SlotMapKey) -> bool { + unsafe { self.state.contains_impl(key) } + } + + /// Returns a reference to the value stored under the given key. If there is no such key, + /// [`None`] is returned. pub fn get(&self, key: SlotMapKey) -> Option<&T> { unsafe { self.state.get_impl(key) } } + /// Returns a mutable reference to the value stored under the given key. If there is no + /// such key, [`None`] is returned. pub fn get_mut(&mut self, key: SlotMapKey) -> Option<&mut T> { unsafe { self.state.get_mut_impl(key) } } + /// Insert a value and returns the corresponding [`SlotMapKey`]. If the container is full + /// [`None`] is returned. pub fn insert(&mut self, value: T) -> Option { unsafe { self.state.insert_impl(value) } } + /// Insert a value at the specified [`SlotMapKey`] and returns true. If the provided key + /// is out-of-bounds it returns `false` and adds nothing. If there is already a value + /// stored at the `key`s index, the value is overridden with the provided value. pub fn insert_at(&mut self, key: SlotMapKey, value: T) -> bool { unsafe { self.state.insert_at_impl(key, value) } } + /// Removes a value at the specified [`SlotMapKey`]. If there was no value corresponding + /// to the [`SlotMapKey`] it returns false, otherwise true. pub fn remove(&mut self, key: SlotMapKey) -> bool { unsafe { self.state.remove_impl(key) } } + /// Returns the number of stored values. pub fn len(&self) -> usize { - unsafe { self.state.len_impl() } + self.state.len_impl() } + /// Returns the capacity. pub fn capacity(&self) -> usize { - unsafe { self.state.capacity_impl() } + self.state.capacity_impl() } + /// Returns true if the container is empty, otherwise false. pub fn is_empty(&self) -> bool { - unsafe { self.state.is_empty_impl() } + self.state.is_empty_impl() } + /// Returns true if the container is full, otherwise false. pub fn is_full(&self) -> bool { - unsafe { self.state.is_full_impl() } + self.state.is_full_impl() } } diff --git a/iceoryx2-bb/container/src/vec.rs b/iceoryx2-bb/container/src/vec.rs index 41b7989f5..cedacec9b 100644 --- a/iceoryx2-bb/container/src/vec.rs +++ b/iceoryx2-bb/container/src/vec.rs @@ -236,7 +236,7 @@ pub mod details { self.len == self.capacity } - unsafe fn push_impl(&mut self, value: T) -> bool { + pub(crate) unsafe fn push_impl(&mut self, value: T) -> bool { if self.is_full() { return false; } diff --git a/iceoryx2-bb/container/tests/slotmap_tests.rs b/iceoryx2-bb/container/tests/slotmap_tests.rs index 75358c8c4..2612cd5c9 100644 --- a/iceoryx2-bb/container/tests/slotmap_tests.rs +++ b/iceoryx2-bb/container/tests/slotmap_tests.rs @@ -15,7 +15,7 @@ use iceoryx2_bb_testing::assert_that; mod slot_map { - use iceoryx2_bb_container::slotmap::FixedSizeSlotMap; + use iceoryx2_bb_container::slotmap::{FixedSizeSlotMap, SlotMapKey}; use super::*; @@ -30,6 +30,7 @@ mod slot_map { assert_that!(sut, len 0); assert_that!(sut, is_empty); assert_that!(sut.is_full(), eq false); + assert_that!(sut.capacity(), eq SUT_CAPACITY); } #[test] @@ -39,5 +40,99 @@ mod slot_map { assert_that!(sut, len 0); assert_that!(sut, is_empty); assert_that!(sut.is_full(), eq false); + assert_that!(sut.capacity(), eq SUT_CAPACITY); + } + + #[test] + fn inserting_elements_works() { + let mut sut = FixedSizeSut::new(); + + for i in 0..SUT_CAPACITY { + assert_that!(sut.is_full(), eq false); + let key = sut.insert(i).unwrap(); + *sut.get_mut(key).unwrap() += i; + assert_that!(*sut.get(key).unwrap(), eq 2 * i); + assert_that!(sut, len i + 1); + assert_that!(sut.is_empty(), eq false); + } + + assert_that!(sut.is_full(), eq true); + assert_that!(sut.insert(123), is_none); + } + + #[test] + fn insert_when_full_fails() { + let mut sut = FixedSizeSut::new(); + + for i in 0..SUT_CAPACITY { + assert_that!(sut.insert(i), is_some); + } + + assert_that!(sut.insert(34), is_none); + } + + #[test] + fn removing_elements_works() { + let mut sut = FixedSizeSut::new(); + let mut keys = vec![]; + + for i in 0..SUT_CAPACITY { + keys.push(sut.insert(i).unwrap()); + } + + for (n, key) in keys.iter().enumerate() { + assert_that!(sut.len(), eq sut.capacity() - n); + assert_that!(sut.is_empty(), eq false); + assert_that!(sut.contains(*key), eq true); + assert_that!(sut.remove(*key), eq true); + assert_that!(sut.remove(*key), eq false); + assert_that!(sut.contains(*key), eq false); + assert_that!(sut.is_full(), eq false); + + assert_that!(sut.get(*key), is_none); + assert_that!(sut.get_mut(*key), is_none); + } + + assert_that!(sut.is_empty(), eq true); + } + + #[test] + fn removing_out_of_bounds_key_returns_false() { + let mut sut = FixedSizeSut::new(); + + assert_that!(sut.remove(SlotMapKey::new(SUT_CAPACITY + 1)), eq false); + } + + #[test] + fn insert_at_works() { + let mut sut = FixedSizeSut::new(); + + let key = SlotMapKey::new(5); + let value = 71823; + assert_that!(sut.insert_at(key, 781), eq true); + assert_that!(sut.insert_at(key, value), eq true); + + assert_that!(*sut.get(key).unwrap(), eq value); + } + + #[test] + fn insert_at_out_of_bounds_key_returns_false() { + let mut sut = FixedSizeSut::new(); + let key = SlotMapKey::new(SUT_CAPACITY + 1); + assert_that!(sut.insert_at(key, 781), eq false); + } + + #[test] + fn iterating_works() { + let mut sut = FixedSizeSut::new(); + let mut keys = vec![]; + + for i in 0..SUT_CAPACITY { + keys.push(sut.insert(5 * i + 3).unwrap()); + } + + for (key, value) in sut.iter() { + assert_that!(*value, eq 5 * key.value() + 3); + } } } diff --git a/iceoryx2-bb/memory/src/pool_allocator.rs b/iceoryx2-bb/memory/src/pool_allocator.rs index 9a4a5e519..c9f4c6e5b 100644 --- a/iceoryx2-bb/memory/src/pool_allocator.rs +++ b/iceoryx2-bb/memory/src/pool_allocator.rs @@ -49,7 +49,6 @@ use iceoryx2_bb_elementary::bump_allocator::BumpAllocator; use iceoryx2_bb_elementary::math::align; -use iceoryx2_bb_elementary::math::align_to; use iceoryx2_bb_elementary::relocatable_container::*; use iceoryx2_bb_lock_free::mpmc::unique_index_set::*;