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
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:
@@ -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]]
|
||||
|
||||
@@ -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]);
|
||||
|
||||
Reference in New Issue
Block a user