diff --git a/Bigfoot/Sources/Utils/Include/Utils/Containers/SlotMap.hpp b/Bigfoot/Sources/Utils/Include/Utils/Containers/SlotMap.hpp index cf7de95..9ecdc52 100644 --- a/Bigfoot/Sources/Utils/Include/Utils/Containers/SlotMap.hpp +++ b/Bigfoot/Sources/Utils/Include/Utils/Containers/SlotMap.hpp @@ -11,13 +11,14 @@ #include #include #include -#include #include #include #include #include #include +#include +#include namespace Bigfoot { @@ -368,9 +369,42 @@ class StableSlotMap: public SlotMapBase private: static constexpr std::size_t PAGE_SIZE = 64; + struct AlignedSlot + { + alignas(TYPE) std::byte m_bytes[sizeof(TYPE)]; + }; + struct Page { - eastl::array, PAGE_SIZE> m_slots; + Page() = default; + + Page(const Page& p_page) = delete; + Page(Page&& p_page) = delete; + + ~Page() + { + for (std::size_t offset = m_occupied.find_first(); offset != PAGE_SIZE; offset = m_occupied.find_next(offset)) + { + std::destroy_at(SlotAt(offset)); + } + } + + Page& operator=(const Page& p_page) = delete; + Page& operator=(Page&& p_page) = delete; + + [[nodiscard]] + TYPE* SlotAt(const std::size_t p_offset) + { + return std::launder(reinterpret_cast(m_storage[p_offset].m_bytes)); + } + + [[nodiscard]] + const TYPE* SlotAt(const std::size_t p_offset) const + { + return std::launder(reinterpret_cast(m_storage[p_offset].m_bytes)); + } + + eastl::array m_storage; eastl::bitset m_occupied; }; @@ -447,7 +481,7 @@ class StableSlotMap: public SlotMapBase { m_pageIndex = p_pageIndex; m_offset = offset; - m_current = &(*m_map->m_pages[p_pageIndex]).m_slots[offset].value(); + m_current = m_map->m_pages[p_pageIndex]->SlotAt(offset); return; } } @@ -466,7 +500,7 @@ class StableSlotMap: public SlotMapBase { m_pageIndex = p_pageIndex; m_offset = offset; - m_current = &(*m_map->m_pages[p_pageIndex]).m_slots[offset].value(); + m_current = m_map->m_pages[p_pageIndex]->SlotAt(offset); return; } @@ -484,7 +518,7 @@ class StableSlotMap: public SlotMapBase if (offset != PAGE_SIZE) { m_offset = offset; - m_current = &(*m_map->m_pages[m_pageIndex]).m_slots[offset].value(); + m_current = m_map->m_pages[m_pageIndex]->SlotAt(offset); return; } @@ -503,7 +537,7 @@ class StableSlotMap: public SlotMapBase if (offset != PAGE_SIZE) { m_offset = offset; - m_current = &(*m_map->m_pages[m_pageIndex]).m_slots[offset].value(); + m_current = m_map->m_pages[m_pageIndex]->SlotAt(offset); return; } @@ -592,7 +626,7 @@ class StableSlotMap: public SlotMapBase { m_pageIndex = p_pageIndex; m_offset = offset; - m_current = &(*m_map->m_pages[p_pageIndex]).m_slots[offset].value(); + m_current = m_map->m_pages[p_pageIndex]->SlotAt(offset); return; } } @@ -611,7 +645,7 @@ class StableSlotMap: public SlotMapBase { m_pageIndex = p_pageIndex; m_offset = offset; - m_current = &(*m_map->m_pages[p_pageIndex]).m_slots[offset].value(); + m_current = m_map->m_pages[p_pageIndex]->SlotAt(offset); return; } @@ -629,7 +663,7 @@ class StableSlotMap: public SlotMapBase if (offset != PAGE_SIZE) { m_offset = offset; - m_current = &(*m_map->m_pages[m_pageIndex]).m_slots[offset].value(); + m_current = m_map->m_pages[m_pageIndex]->SlotAt(offset); return; } @@ -648,7 +682,7 @@ class StableSlotMap: public SlotMapBase if (offset != PAGE_SIZE) { m_offset = offset; - m_current = &(*m_map->m_pages[m_pageIndex]).m_slots[offset].value(); + m_current = m_map->m_pages[m_pageIndex]->SlotAt(offset); return; } @@ -689,7 +723,7 @@ class StableSlotMap: public SlotMapBase Page& page = *m_pages[pageIndex]; const std::size_t offset = index % PAGE_SIZE; - page.m_slots[offset].emplace(std::forward(p_args)...); + ::new (static_cast(page.m_storage[offset].m_bytes)) TYPE(std::forward(p_args)...); page.m_occupied.set(offset); ++m_size; @@ -706,7 +740,7 @@ class StableSlotMap: public SlotMapBase const SlotMapKey::IndexType index = p_slotKey.GetIndex(); Page& page = *m_pages[index / PAGE_SIZE]; const std::size_t offset = index % PAGE_SIZE; - page.m_slots[offset].reset(); + std::destroy_at(page.SlotAt(offset)); page.m_occupied.reset(offset); --m_size; @@ -727,9 +761,9 @@ class StableSlotMap: public SlotMapBase void Reset() { - for (const eastl::unique_ptr& page: m_pages) + for (eastl::unique_ptr& page: m_pages) { - *page = Page {}; + page = eastl::make_unique(); } ResetSlots(); @@ -743,7 +777,7 @@ class StableSlotMap: public SlotMapBase for (std::size_t offset = page.m_occupied.find_first(); offset != PAGE_SIZE; offset = page.m_occupied.find_next(offset)) { - page.m_slots[offset].reset(); + std::destroy_at(page.SlotAt(offset)); ReleaseSlotVersion(static_cast(pageIndex * PAGE_SIZE + offset)); } page.m_occupied.reset(); @@ -853,13 +887,13 @@ class StableSlotMap: public SlotMapBase [[nodiscard]] TYPE& DataAt(const std::size_t p_index) { - return (*m_pages[p_index / PAGE_SIZE]).m_slots[p_index % PAGE_SIZE].value(); + return *m_pages[p_index / PAGE_SIZE]->SlotAt(p_index % PAGE_SIZE); } [[nodiscard]] const TYPE& DataAt(const std::size_t p_index) const { - return (*m_pages[p_index / PAGE_SIZE]).m_slots[p_index % PAGE_SIZE].value(); + return *m_pages[p_index / PAGE_SIZE]->SlotAt(p_index % PAGE_SIZE); } [[nodiscard]] diff --git a/Bigfoot/Tests/Utils/Containers/SlotMap.cpp b/Bigfoot/Tests/Utils/Containers/SlotMap.cpp index b7d18cb..50a685e 100644 --- a/Bigfoot/Tests/Utils/Containers/SlotMap.cpp +++ b/Bigfoot/Tests/Utils/Containers/SlotMap.cpp @@ -6,6 +6,7 @@ *********************************************************************/ #include +#include #include namespace Bigfoot @@ -66,11 +67,72 @@ TEST_F(SlotMapKeyFixture, GetIndex_ShouldReturnTheIndex) /****************************************************************************************/ +class SlotMapValueMock +{ + public: + virtual ~SlotMapValueMock() = default; + virtual void Destruct() = 0; +}; + +class SlotMapValue +{ + public: + inline static SlotMapValueMock* ms_mock = nullptr; + + SlotMapValue() = default; + + SlotMapValue(const std::uint32_t p_value): + m_value(p_value) + { + } + + SlotMapValue(const SlotMapValue& p_value) = default; + SlotMapValue(SlotMapValue&& p_value) = default; + + ~SlotMapValue() + { + if (ms_mock) + { + ms_mock->Destruct(); + } + } + + SlotMapValue& operator=(const SlotMapValue& p_value) = default; + SlotMapValue& operator=(SlotMapValue&& p_value) = default; + + operator std::uint32_t() const + { + return m_value; + } + + private: + std::uint32_t m_value = 0; +}; + template class SlotMapFixture: public ::testing::Test { protected: + class SlotMapValueMockImpl: public SlotMapValueMock + { + public: + MOCK_METHOD(void, Destruct, (), (override)); + }; + + std::unique_ptr m_mock; SLOTMAP m_slotMap; + + void SetUp() override + { + m_mock = std::make_unique<::testing::NiceMock>(); + SlotMapValue::ms_mock = m_mock.get(); + } + + void TearDown() override + { + SlotMapValue::ms_mock = nullptr; + m_mock.reset(); + } }; class SlotMapNameGenerator @@ -79,18 +141,18 @@ class SlotMapNameGenerator template static std::string GetName(int) { - if constexpr (std::is_same_v>) + if constexpr (std::is_same_v>) { return "DenseSlotMap"; } - if constexpr (std::is_same_v>) + if constexpr (std::is_same_v>) { return "StableSlotMap"; } } }; -using SlotMapTypes = ::testing::Types, StableSlotMap>; +using SlotMapTypes = ::testing::Types, StableSlotMap>; TYPED_TEST_SUITE(SlotMapFixture, SlotMapTypes, SlotMapNameGenerator); /****************************************************************************************/ @@ -222,6 +284,8 @@ TYPED_TEST(SlotMapFixture, Remove_ShouldRemoveTheValue) { const SlotMapKey key = this->m_slotMap.Insert(42); EXPECT_TRUE(this->m_slotMap.Has(key)); + + EXPECT_CALL(*this->m_mock, Destruct()).Times(1); this->m_slotMap.Remove(key); EXPECT_FALSE(this->m_slotMap.Has(key)); } @@ -248,6 +312,7 @@ TYPED_TEST(SlotMapFixture, Reset_ShouldClearTheMapAndOldKeysAreValid) const SlotMapKey key1 = this->m_slotMap.Insert(42); const SlotMapKey key2 = this->m_slotMap.Insert(69); + EXPECT_CALL(*this->m_mock, Destruct()).Times(2); this->m_slotMap.Reset(); EXPECT_EQ(this->m_slotMap.Size(), 0); @@ -267,6 +332,7 @@ TYPED_TEST(SlotMapFixture, Clear_ShouldClearTheMapAndOldKeysAreInvalid) const SlotMapKey key1 = this->m_slotMap.Insert(42); const SlotMapKey key2 = this->m_slotMap.Insert(69); + EXPECT_CALL(*this->m_mock, Destruct()).Times(2); this->m_slotMap.Clear(); EXPECT_EQ(this->m_slotMap.Size(), 0); @@ -1017,7 +1083,7 @@ TEST_F(StableSlotMapFixture, PointersAreStable) EXPECT_EQ(*pointer, 42); // Removing unrelated elements, including ones sharing the same page as key, must not move it - // either: Remove() only resets that slot's optional, it never compacts the page's array. + // either: Remove() only destroys that slot in place, it never compacts the page's array. for (std::uint32_t value = 0; value < elementCount; value += 2) { m_slotMap.Remove(keys[value]);