use tightly packed memory
Bigfoot / Build & Test Debug with ./ConanProfiles/clang (Unity Build: OFF) (push) Successful in 5m42s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang (Unity Build: ON) (push) Successful in 5m39s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Successful in 6m9s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Successful in 6m5s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang (Unity Build: OFF) (push) Successful in 6m19s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang (Unity Build: ON) (push) Successful in 6m15s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Successful in 7m27s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Successful in 7m24s
Bigfoot / Build & Test Release with ./ConanProfiles/clang (Unity Build: OFF) (push) Successful in 6m17s
Bigfoot / Build & Test Release with ./ConanProfiles/clang (Unity Build: ON) (push) Successful in 6m17s
Bigfoot / Build & Test Release with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Successful in 7m5s
Bigfoot / Build & Test Release with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Successful in 7m2s
Bigfoot / Clang Format Checks (push) Failing after 12s

This commit is contained in:
2026-08-09 16:17:44 +02:00
parent f5e3a447c8
commit ccd256f87f
2 changed files with 121 additions and 21 deletions
@@ -11,13 +11,14 @@
#include <EASTL/array.h>
#include <EASTL/bitset.h>
#include <EASTL/iterator.h>
#include <EASTL/optional.h>
#include <EASTL/unique_ptr.h>
#include <EASTL/vector.h>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <new>
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<eastl::optional<TYPE>, 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<TYPE*>(m_storage[p_offset].m_bytes));
}
[[nodiscard]]
const TYPE* SlotAt(const std::size_t p_offset) const
{
return std::launder(reinterpret_cast<const TYPE*>(m_storage[p_offset].m_bytes));
}
eastl::array<AlignedSlot, PAGE_SIZE> m_storage;
eastl::bitset<PAGE_SIZE> 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<ARGS>(p_args)...);
::new (static_cast<void*>(page.m_storage[offset].m_bytes)) TYPE(std::forward<ARGS>(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>& page: m_pages)
for (eastl::unique_ptr<Page>& page: m_pages)
{
*page = Page {};
page = eastl::make_unique<Page>();
}
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<SlotMapKey::IndexType>(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]]
+70 -4
View File
@@ -6,6 +6,7 @@
*********************************************************************/
#include <Utils/Containers/SlotMap.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
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 SLOTMAP>
class SlotMapFixture: public ::testing::Test
{
protected:
class SlotMapValueMockImpl: public SlotMapValueMock
{
public:
MOCK_METHOD(void, Destruct, (), (override));
};
std::unique_ptr<SlotMapValueMockImpl> m_mock;
SLOTMAP m_slotMap;
void SetUp() override
{
m_mock = std::make_unique<::testing::NiceMock<SlotMapValueMockImpl>>();
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<typename T>
static std::string GetName(int)
{
if constexpr (std::is_same_v<T, DenseSlotMap<std::uint32_t>>)
if constexpr (std::is_same_v<T, DenseSlotMap<SlotMapValue>>)
{
return "DenseSlotMap";
}
if constexpr (std::is_same_v<T, StableSlotMap<std::uint32_t>>)
if constexpr (std::is_same_v<T, StableSlotMap<SlotMapValue>>)
{
return "StableSlotMap";
}
}
};
using SlotMapTypes = ::testing::Types<DenseSlotMap<std::uint32_t>, StableSlotMap<std::uint32_t>>;
using SlotMapTypes = ::testing::Types<DenseSlotMap<SlotMapValue>, StableSlotMap<SlotMapValue>>;
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]);