From f98367c653c18b3347eb653b9a3ee09efcfe2eac 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 | 176 ++++++++++++++++--- iceoryx2-bb/container/src/vec.rs | 2 +- iceoryx2-bb/container/tests/slotmap_tests.rs | 83 ++++++++- 3 files changed, 238 insertions(+), 23 deletions(-) diff --git a/iceoryx2-bb/container/src/slotmap.rs b/iceoryx2-bb/container/src/slotmap.rs index 2ba964714..40716c83d 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, @@ -109,9 +128,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 +143,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 +184,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 +210,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 +277,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 +288,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,6 +300,7 @@ pub mod details { new_self } + /// Returns the [`Iter`]ator to iterate over all entries. pub fn iter( &self, ) -> Iter>>, OwningPointer>> @@ -281,40 +308,60 @@ pub mod details { 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,6 +372,12 @@ pub mod details { RelocatablePointer>, > { + /// Returns the [`Iter`]ator to iterate over all entries. + /// + /// # Safety + /// + /// * [`RelocatableSlotMap::init()`] must be called once before + /// pub unsafe fn iter( &self, ) -> Iter< @@ -335,44 +388,95 @@ pub mod details { 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,10 +522,12 @@ impl Default for FixedSizeSlotMap { } impl FixedSizeSlotMap { + /// Creates a new empty [`FixedSizeSlotMap`]. pub fn new() -> Self { Self::default() } + /// Returns the [`Iter`]ator to iterate over all entries. pub fn iter( &self, ) -> details::Iter< @@ -424,39 +538,59 @@ impl FixedSizeSlotMap { 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..f0d6d0ad3 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,85 @@ 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); } }