SlotMap impl and tests
Some checks failed
Bigfoot / Build & Test Debug with ./ConanProfiles/clang (Unity Build: OFF) (push) Successful in 5m31s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang (Unity Build: ON) (push) Successful in 5m31s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Successful in 5m44s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Successful in 5m49s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang (Unity Build: ON) (push) Has been cancelled
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Has been cancelled
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Has been cancelled
Bigfoot / Build & Test Release with ./ConanProfiles/clang (Unity Build: OFF) (push) Has been cancelled
Bigfoot / Build & Test Release with ./ConanProfiles/clang (Unity Build: ON) (push) Has been cancelled
Bigfoot / Build & Test Release with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Has been cancelled
Bigfoot / Build & Test Release with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Has been cancelled
Bigfoot / Clang Format Checks (push) Has been cancelled
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang (Unity Build: OFF) (push) Has been cancelled
Some checks failed
Bigfoot / Build & Test Debug with ./ConanProfiles/clang (Unity Build: OFF) (push) Successful in 5m31s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang (Unity Build: ON) (push) Successful in 5m31s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Successful in 5m44s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Successful in 5m49s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang (Unity Build: ON) (push) Has been cancelled
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Has been cancelled
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Has been cancelled
Bigfoot / Build & Test Release with ./ConanProfiles/clang (Unity Build: OFF) (push) Has been cancelled
Bigfoot / Build & Test Release with ./ConanProfiles/clang (Unity Build: ON) (push) Has been cancelled
Bigfoot / Build & Test Release with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Has been cancelled
Bigfoot / Build & Test Release with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Has been cancelled
Bigfoot / Clang Format Checks (push) Has been cancelled
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang (Unity Build: OFF) (push) Has been cancelled
This commit is contained in:
@@ -15,24 +15,38 @@
|
||||
|
||||
namespace Bigfoot
|
||||
{
|
||||
template<class TYPE, class VERSION_TYPE = std::uint32_t>
|
||||
|
||||
template<class TYPE,
|
||||
class VERSION_TYPE = std::uint32_t,
|
||||
class INDEX_TYPE = std::uint32_t,
|
||||
std::enable_if_t<std::is_integral_v<VERSION_TYPE> && std::is_integral_v<INDEX_TYPE>, bool> = true>
|
||||
class SlotMap
|
||||
{
|
||||
public:
|
||||
class SlotKey
|
||||
{
|
||||
public:
|
||||
using IndexType = std::uint32_t;
|
||||
using IndexType = INDEX_TYPE;
|
||||
using VersionType = VERSION_TYPE;
|
||||
|
||||
static constexpr VersionType MAX_VERSION = std::numeric_limits<VersionType>::max();
|
||||
private:
|
||||
static constexpr std::uint32_t VERSION_BIT_COUNT = sizeof(VersionType) *
|
||||
std::numeric_limits<unsigned char>::digits;
|
||||
static constexpr std::uint32_t INDEX_BIT_COUNT = sizeof(IndexType) * std::numeric_limits<unsigned char>::digits;
|
||||
static_assert(VERSION_BIT_COUNT + INDEX_BIT_COUNT <= 64,
|
||||
"We cant construct a 64 bit key from the given Version and Index types!");
|
||||
|
||||
static constexpr std::uint64_t INDEX_MASK = (static_cast<std::uint64_t>(1) << INDEX_BIT_COUNT) - 1;
|
||||
|
||||
public:
|
||||
static constexpr VersionType INVALID_VERSION = 0;
|
||||
|
||||
static constexpr IndexType MAX_INDEX = std::numeric_limits<IndexType>::max();
|
||||
|
||||
SlotKey(const VersionType p_version, const IndexType p_index)
|
||||
SlotKey(const VersionType p_version, const IndexType p_index):
|
||||
m_key((static_cast<std::uint64_t>(p_version) << INDEX_BIT_COUNT) |
|
||||
(static_cast<std::uint64_t>(p_index) & INDEX_MASK))
|
||||
{
|
||||
m_key = (static_cast<std::uint64_t>(p_version) << 32) | p_index;
|
||||
}
|
||||
|
||||
SlotKey():
|
||||
@@ -45,25 +59,27 @@ class SlotMap
|
||||
|
||||
~SlotKey() = default;
|
||||
|
||||
bool IsValid() const
|
||||
bool Valid() const
|
||||
{
|
||||
return GetVersion() != INVALID_VERSION;
|
||||
return Version() != INVALID_VERSION;
|
||||
}
|
||||
|
||||
VersionType GetVersion() const
|
||||
VersionType Version() const
|
||||
{
|
||||
return static_cast<VersionType>(m_key >> 32);
|
||||
return static_cast<VersionType>(m_key >> INDEX_BIT_COUNT);
|
||||
}
|
||||
|
||||
IndexType GetIndex() const
|
||||
IndexType Index() const
|
||||
{
|
||||
constexpr std::uint64_t indexMask = 0x00000000FFFFFFFF;
|
||||
return static_cast<IndexType>(m_key & indexMask);
|
||||
return static_cast<IndexType>(m_key & INDEX_MASK);
|
||||
}
|
||||
|
||||
SlotKey& operator=(const SlotKey& p_slotKey) = default;
|
||||
SlotKey& operator=(SlotKey&& p_slotKey) = default;
|
||||
|
||||
[[nodiscard]]
|
||||
bool operator==(const SlotKey& p_other) const = default;
|
||||
|
||||
private:
|
||||
std::uint64_t m_key;
|
||||
};
|
||||
@@ -84,22 +100,22 @@ class SlotMap
|
||||
{
|
||||
ASSERT(UtilsAssertHandler, m_data.size() < SlotKey::MAX_INDEX, "Too many elements for SlotMap!");
|
||||
|
||||
const typename SlotKey::IndexType dataIndex = static_cast<typename SlotKey::IndexType>(m_data.size());
|
||||
const typename SlotKey::IndexType dataIndex = static_cast<SlotKey::IndexType>(m_data.size());
|
||||
m_data.emplace_back(std::forward<ARGS>(p_args)...);
|
||||
|
||||
if (HasFreeSlots())
|
||||
{
|
||||
const typename SlotKey::IndexType slotIndex = m_freeSlotHead;
|
||||
|
||||
m_freeSlotHead = m_slots[slotIndex].GetIndex();
|
||||
m_freeSlotHead = m_slots[slotIndex].Index();
|
||||
|
||||
m_slots[slotIndex] = SlotKey {m_slots[slotIndex].GetVersion(), dataIndex};
|
||||
m_slots[slotIndex] = SlotKey {m_slots[slotIndex].Version(), dataIndex};
|
||||
m_dataToSlots.push_back(slotIndex);
|
||||
|
||||
return SlotKey {m_slots[slotIndex].GetVersion(), slotIndex};
|
||||
return SlotKey {m_slots[slotIndex].Version(), slotIndex};
|
||||
}
|
||||
|
||||
const typename SlotKey::IndexType slotIndex = static_cast<typename SlotKey::IndexType>(m_slots.size());
|
||||
const typename SlotKey::IndexType slotIndex = static_cast<SlotKey::IndexType>(m_slots.size());
|
||||
m_slots.emplace_back(1, dataIndex);
|
||||
m_dataToSlots.push_back(slotIndex);
|
||||
|
||||
@@ -113,45 +129,35 @@ class SlotMap
|
||||
return;
|
||||
}
|
||||
|
||||
const typename SlotKey::IndexType dataIndex = m_slots[p_slotKey.GetIndex()].GetIndex();
|
||||
const typename SlotKey::IndexType dataIndex = m_slots[p_slotKey.Index()].Index();
|
||||
|
||||
m_data.erase_unsorted(m_data.begin() + dataIndex);
|
||||
m_dataToSlots.erase_unsorted(m_dataToSlots.begin() + dataIndex);
|
||||
|
||||
if (dataIndex < m_data.size())
|
||||
{
|
||||
m_slots[m_dataToSlots[dataIndex]] = SlotKey {m_slots[m_dataToSlots[dataIndex]].GetVersion(), dataIndex};
|
||||
m_slots[m_dataToSlots[dataIndex]] = SlotKey {m_slots[m_dataToSlots[dataIndex]].Version(), dataIndex};
|
||||
}
|
||||
|
||||
const typename SlotKey::VersionType nextVersion = p_slotKey.GetVersion() + 1;
|
||||
|
||||
if (nextVersion == SlotKey::INVALID_VERSION)
|
||||
{
|
||||
m_slots[p_slotKey.GetIndex()] = SlotKey {SlotKey::INVALID_VERSION, 0};
|
||||
return;
|
||||
}
|
||||
|
||||
m_slots[p_slotKey.GetIndex()] = SlotKey {nextVersion, m_freeSlotHead};
|
||||
m_freeSlotHead = p_slotKey.GetIndex();
|
||||
RecycleSlot(p_slotKey.Version() + 1, p_slotKey.Index());
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool Has(const SlotKey p_slotKey) const
|
||||
{
|
||||
return p_slotKey.GetIndex() < m_data.size() &&
|
||||
p_slotKey.GetVersion() == m_slots[p_slotKey.GetIndex()].GetVersion();
|
||||
return p_slotKey.Index() < m_slots.size() && p_slotKey.Version() == m_slots[p_slotKey.Index()].Version();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
TYPE* Get(const SlotKey p_slotKey)
|
||||
{
|
||||
return Has(p_slotKey) ? &m_data[m_slots[p_slotKey.GetIndex()].GetIndex()] : nullptr;
|
||||
return Has(p_slotKey) ? &m_data[m_slots[p_slotKey.Index()].Index()] : nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
const TYPE* Get(const SlotKey p_slotKey) const
|
||||
{
|
||||
return Has(p_slotKey) ? &m_data[m_slots[p_slotKey.GetIndex()].GetIndex()] : nullptr;
|
||||
return Has(p_slotKey) ? &m_data[m_slots[p_slotKey.Index()].Index()] : nullptr;
|
||||
}
|
||||
|
||||
void Reset()
|
||||
@@ -162,6 +168,114 @@ class SlotMap
|
||||
m_freeSlotHead = std::numeric_limits<typename SlotKey::IndexType>::max();
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
for (const typename eastl::vector<TYPE>::size_type slotIndex: m_dataToSlots)
|
||||
{
|
||||
RecycleSlot(m_slots[slotIndex].Version() + 1, static_cast<SlotKey::IndexType>(slotIndex));
|
||||
}
|
||||
|
||||
m_data.clear();
|
||||
m_dataToSlots.clear();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::size_type Size() const
|
||||
{
|
||||
return m_data.size();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::size_type Capacity() const
|
||||
{
|
||||
return m_data.capacity();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::size_type Empty() const
|
||||
{
|
||||
return m_data.empty();
|
||||
}
|
||||
|
||||
void Reserve(const eastl::vector<TYPE>::size_type p_size)
|
||||
{
|
||||
m_data.reserve(p_size);
|
||||
m_dataToSlots.reserve(p_size);
|
||||
m_slots.reserve(p_size);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::iterator begin()
|
||||
{
|
||||
return m_data.begin();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::iterator end()
|
||||
{
|
||||
return m_data.end();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::const_iterator begin() const
|
||||
{
|
||||
return m_data.begin();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::const_iterator end() const
|
||||
{
|
||||
return m_data.end();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::reverse_iterator rbegin()
|
||||
{
|
||||
return m_data.rbegin();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::reverse_iterator rend()
|
||||
{
|
||||
return m_data.rend();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::const_reverse_iterator rbegin() const
|
||||
{
|
||||
return m_data.rbegin();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::const_reverse_iterator rend() const
|
||||
{
|
||||
return m_data.rend();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::const_iterator cbegin() const
|
||||
{
|
||||
return m_data.cbegin();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::const_iterator cend() const
|
||||
{
|
||||
return m_data.cend();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::const_reverse_iterator crbegin() const
|
||||
{
|
||||
return m_data.crbegin();
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
eastl::vector<TYPE>::const_reverse_iterator crend() const
|
||||
{
|
||||
return m_data.crend();
|
||||
}
|
||||
|
||||
SlotMap& operator=(const SlotMap& p_slotMap) = default;
|
||||
SlotMap& operator=(SlotMap&& p_slotMap) = default;
|
||||
|
||||
@@ -172,12 +286,24 @@ class SlotMap
|
||||
return m_freeSlotHead != std::numeric_limits<typename SlotKey::IndexType>::max();
|
||||
}
|
||||
|
||||
void RecycleSlot(const SlotKey::VersionType p_version, const SlotKey::IndexType p_slotIndex)
|
||||
{
|
||||
if (p_version == SlotKey::INVALID_VERSION)
|
||||
{
|
||||
m_slots[p_slotIndex] = SlotKey {SlotKey::INVALID_VERSION, 0};
|
||||
return;
|
||||
}
|
||||
|
||||
m_slots[p_slotIndex] = SlotKey {p_version, m_freeSlotHead};
|
||||
m_freeSlotHead = p_slotIndex;
|
||||
}
|
||||
|
||||
eastl::vector<TYPE> m_data;
|
||||
eastl::vector<SlotKey> m_slots;
|
||||
|
||||
eastl::vector<typename eastl::vector<TYPE>::size_type> m_dataToSlots;
|
||||
|
||||
typename SlotKey::IndexType m_freeSlotHead;
|
||||
SlotKey::IndexType m_freeSlotHead;
|
||||
};
|
||||
} // namespace Bigfoot
|
||||
|
||||
|
||||
@@ -10,65 +10,451 @@
|
||||
|
||||
namespace Bigfoot
|
||||
{
|
||||
class SlotKeyFixture: public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
};
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotKeyFixture, DefaultSlotKeyIsInvalid)
|
||||
{
|
||||
constexpr SlotMap<std::uint32_t>::SlotKey::IndexType index = 0;
|
||||
constexpr SlotMap<std::uint32_t>::SlotKey::VersionType version = 0;
|
||||
|
||||
SlotMap<std::uint32_t>::SlotKey slotKey {};
|
||||
EXPECT_FALSE(slotKey.Valid());
|
||||
EXPECT_EQ(slotKey.Version(), index);
|
||||
EXPECT_EQ(slotKey.Index(), version);
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotKeyFixture, Valid_ShouldReturnTrueIfTheSlotKeyIsValid)
|
||||
{
|
||||
constexpr SlotMap<std::uint32_t>::SlotKey::IndexType index = 0;
|
||||
constexpr SlotMap<std::uint32_t>::SlotKey::VersionType version = 1;
|
||||
|
||||
SlotMap<std::uint32_t>::SlotKey slotKey {version, index};
|
||||
EXPECT_TRUE(slotKey.Valid());
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotKeyFixture, Valid_ShouldReturnFalseIfTheSlotKeyIsValid)
|
||||
{
|
||||
constexpr SlotMap<std::uint32_t>::SlotKey::IndexType index = 0;
|
||||
constexpr SlotMap<std::uint32_t>::SlotKey::VersionType version = 0;
|
||||
|
||||
SlotMap<std::uint32_t>::SlotKey slotKey {version, index};
|
||||
EXPECT_FALSE(slotKey.Valid());
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotKeyFixture, Version_ShouldReturnTheVersion)
|
||||
{
|
||||
constexpr SlotMap<std::uint32_t>::SlotKey::IndexType index = 0;
|
||||
constexpr SlotMap<std::uint32_t>::SlotKey::VersionType version = 42;
|
||||
|
||||
SlotMap<std::uint32_t>::SlotKey slotKey {version, index};
|
||||
EXPECT_EQ(slotKey.Version(), version);
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotKeyFixture, Index_ShouldReturnTheIndex)
|
||||
{
|
||||
constexpr SlotMap<std::uint32_t>::SlotKey::IndexType index = 42;
|
||||
constexpr SlotMap<std::uint32_t>::SlotKey::VersionType version = 0;
|
||||
|
||||
SlotMap<std::uint32_t>::SlotKey slotKey {version, index};
|
||||
EXPECT_EQ(slotKey.Index(), index);
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
class SlotMapFixture: public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
SlotMap<std::uint32_t> m_slotMap;
|
||||
using SlotMapVersion = std::uint8_t;
|
||||
using SlotMapIndex = std::uint32_t;
|
||||
|
||||
SlotMap<std::uint32_t, SlotMapVersion, SlotMapIndex> m_slotMap;
|
||||
};
|
||||
|
||||
TEST_F(SlotMapFixture, Insert)
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, Insert_ShouldReturnAValidSlotKey)
|
||||
{
|
||||
const SlotMap<std::uint32_t>::SlotKey firstKey = m_slotMap.Insert(64);
|
||||
const SlotMap<std::uint32_t>::SlotKey secondKey = m_slotMap.Insert(2);
|
||||
const SlotMap<std::uint32_t>::SlotKey thirdKey = m_slotMap.Insert(42);
|
||||
|
||||
m_slotMap.Remove(secondKey);
|
||||
m_slotMap.Remove(firstKey);
|
||||
|
||||
const SlotMap<std::uint32_t>::SlotKey fourthKey = m_slotMap.Insert(3);
|
||||
const SlotMap<std::uint32_t>::SlotKey fifthKey = m_slotMap.Insert(65);
|
||||
|
||||
EXPECT_FALSE(m_slotMap.Has(firstKey));
|
||||
EXPECT_FALSE(m_slotMap.Has(secondKey));
|
||||
EXPECT_TRUE(m_slotMap.Has(thirdKey));
|
||||
EXPECT_TRUE(m_slotMap.Has(fourthKey));
|
||||
EXPECT_TRUE(m_slotMap.Has(fifthKey));
|
||||
EXPECT_FALSE(m_slotMap.Has(SlotMap<std::uint32_t>::SlotKey {}));
|
||||
EXPECT_FALSE(m_slotMap.Has(SlotMap<std::uint32_t>::SlotKey {1, 4}));
|
||||
|
||||
EXPECT_EQ(m_slotMap.Get(firstKey), nullptr);
|
||||
EXPECT_EQ(m_slotMap.Get(secondKey), nullptr);
|
||||
EXPECT_EQ(*m_slotMap.Get(thirdKey), 42);
|
||||
EXPECT_EQ(*m_slotMap.Get(fourthKey), 3);
|
||||
EXPECT_EQ(*m_slotMap.Get(fifthKey), 65);
|
||||
EXPECT_EQ(m_slotMap.Get(SlotMap<std::uint32_t>::SlotKey {}), nullptr);
|
||||
EXPECT_EQ(m_slotMap.Get(SlotMap<std::uint32_t>::SlotKey {1, 4}), nullptr);
|
||||
const auto slotKey = m_slotMap.Insert(42);
|
||||
EXPECT_TRUE(slotKey.Valid());
|
||||
}
|
||||
|
||||
TEST(SlotMap, VersionOverflowDeactivatesSlot)
|
||||
{
|
||||
SlotMap<std::uint32_t, std::uint8_t> slotMap;
|
||||
/****************************************************************************************/
|
||||
|
||||
SlotMap<std::uint32_t, std::uint8_t>::SlotKey key = slotMap.Insert(1);
|
||||
|
||||
for (SlotMap<std::uint32_t, std::uint8_t>::SlotKey::VersionType i = 1;
|
||||
i < SlotMap<std::uint32_t, std::uint8_t>::SlotKey::MAX_VERSION;
|
||||
++i)
|
||||
TEST_F(SlotMapFixture, Insert_ShouldRecycleASlotKeyAfterARemove)
|
||||
{
|
||||
slotMap.Remove(key);
|
||||
const SlotMap<std::uint32_t, std::uint8_t>::SlotKey newKey = slotMap.Insert(1);
|
||||
EXPECT_EQ(newKey.GetIndex(), key.GetIndex());
|
||||
const auto slotKey = m_slotMap.Insert(42);
|
||||
m_slotMap.Remove(slotKey);
|
||||
|
||||
const auto secondSlotKey = m_slotMap.Insert(69);
|
||||
EXPECT_NE(secondSlotKey.Version(), slotKey.Version());
|
||||
EXPECT_EQ(secondSlotKey.Index(), slotKey.Index());
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, Has_ShouldReturnTrueIfTheSlotMapHasTheKey)
|
||||
{
|
||||
const auto slotKey = m_slotMap.Insert(42);
|
||||
EXPECT_TRUE(m_slotMap.Has(slotKey));
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, Has_ShouldReturnFalseIfTheSlotMapDoesNotHaveTheKey)
|
||||
{
|
||||
const auto slotKey = m_slotMap.Insert(42);
|
||||
m_slotMap.Remove(slotKey);
|
||||
EXPECT_FALSE(m_slotMap.Has(slotKey));
|
||||
EXPECT_FALSE(m_slotMap.Has((SlotMap<std::uint32_t, SlotMapVersion, SlotMapIndex>::SlotKey {1, 22})));
|
||||
EXPECT_FALSE(m_slotMap.Has((SlotMap<std::uint32_t, SlotMapVersion, SlotMapIndex>::SlotKey {})));
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, Remove_ShouldRemoveTheSlotKey)
|
||||
{
|
||||
const auto slotKey = m_slotMap.Insert(42);
|
||||
m_slotMap.Remove(slotKey);
|
||||
EXPECT_FALSE(m_slotMap.Has(slotKey));
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, Remove_ShouldNotRecycleASlotWhenVersionWasExhausted)
|
||||
{
|
||||
auto key = m_slotMap.Insert(1);
|
||||
|
||||
for (SlotMapVersion i = 1; i < std::numeric_limits<SlotMapVersion>::max(); ++i)
|
||||
{
|
||||
m_slotMap.Remove(key);
|
||||
const auto newKey = m_slotMap.Insert(1);
|
||||
EXPECT_EQ(newKey.Index(), key.Index());
|
||||
key = newKey;
|
||||
}
|
||||
|
||||
// Slot is at MAX_VERSION — one more remove should overflow and permanently deactivate it
|
||||
EXPECT_EQ(key.GetVersion(), (SlotMap<std::uint32_t, std::uint8_t>::SlotKey::MAX_VERSION));
|
||||
slotMap.Remove(key);
|
||||
EXPECT_EQ(key.Version(), std::numeric_limits<SlotMapVersion>::max());
|
||||
m_slotMap.Remove(key);
|
||||
|
||||
EXPECT_FALSE(slotMap.Has(key));
|
||||
EXPECT_FALSE(m_slotMap.Has(key));
|
||||
|
||||
// Dead slot must not be recycled; a new insert must allocate a fresh slot index
|
||||
const SlotMap<std::uint32_t, std::uint8_t>::SlotKey newKey = slotMap.Insert(2);
|
||||
EXPECT_NE(newKey.GetIndex(), key.GetIndex());
|
||||
const auto newKey = m_slotMap.Insert(2);
|
||||
EXPECT_NE(newKey.Index(), key.Index());
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, Get_ShouldReturnNullptrForInvalidSlotKeys)
|
||||
{
|
||||
const auto slotKey = m_slotMap.Insert(42);
|
||||
m_slotMap.Remove(slotKey);
|
||||
|
||||
const auto validate = [&](auto& p_slotMap)
|
||||
{
|
||||
EXPECT_EQ(p_slotMap.Get(slotKey), nullptr);
|
||||
EXPECT_EQ(p_slotMap.Get(SlotMap<std::uint32_t, SlotMapVersion, SlotMapIndex>::SlotKey {1, 3}), nullptr);
|
||||
EXPECT_EQ(p_slotMap.Get(SlotMap<std::uint32_t, SlotMapVersion, SlotMapIndex>::SlotKey {}), nullptr);
|
||||
};
|
||||
|
||||
validate(m_slotMap);
|
||||
const auto& constSlotMap = m_slotMap;
|
||||
validate(constSlotMap);
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, Get_ShouldReturnTheValueForValidSlotKeys)
|
||||
{
|
||||
const auto slotKey1 = m_slotMap.Insert(42);
|
||||
const auto slotKey2 = m_slotMap.Insert(69);
|
||||
const auto slotKey3 = m_slotMap.Insert(28);
|
||||
const auto slotKey4 = m_slotMap.Insert(0);
|
||||
|
||||
m_slotMap.Remove(slotKey2);
|
||||
|
||||
const auto validate = [&](auto& p_slotMap)
|
||||
{
|
||||
EXPECT_EQ(*p_slotMap.Get(slotKey1), 42);
|
||||
EXPECT_EQ(*p_slotMap.Get(slotKey3), 28);
|
||||
EXPECT_EQ(*p_slotMap.Get(slotKey4), 0);
|
||||
};
|
||||
|
||||
validate(m_slotMap);
|
||||
const auto& constSlotMap = m_slotMap;
|
||||
validate(constSlotMap);
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, Reset_ResetsTheSlotMapAndMakesCollisionWithOldSlotKeys)
|
||||
{
|
||||
const auto slotKey1 = m_slotMap.Insert(42);
|
||||
std::ignore = m_slotMap.Insert(69);
|
||||
std::ignore = m_slotMap.Insert(28);
|
||||
std::ignore = m_slotMap.Insert(0);
|
||||
|
||||
m_slotMap.Reset();
|
||||
|
||||
const auto slotKey5 = m_slotMap.Insert(128);
|
||||
|
||||
EXPECT_EQ(slotKey1, slotKey5);
|
||||
EXPECT_EQ(*m_slotMap.Get(slotKey5), 128);
|
||||
EXPECT_EQ(*m_slotMap.Get(slotKey1), 128);
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, Clear_ResetsTheSlotMapAndButGuaranteesNotCollisionWithOldSlotKeys)
|
||||
{
|
||||
const auto slotKey1 = m_slotMap.Insert(42);
|
||||
std::ignore = m_slotMap.Insert(69);
|
||||
std::ignore = m_slotMap.Insert(28);
|
||||
std::ignore = m_slotMap.Insert(0);
|
||||
|
||||
m_slotMap.Clear();
|
||||
|
||||
const auto slotKey5 = m_slotMap.Insert(128);
|
||||
|
||||
EXPECT_NE(slotKey1, slotKey5);
|
||||
EXPECT_EQ(*m_slotMap.Get(slotKey5), 128);
|
||||
EXPECT_EQ(m_slotMap.Get(slotKey1), nullptr);
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, Size_ReturnTheSizeOfTheSlotMap)
|
||||
{
|
||||
EXPECT_EQ(m_slotMap.Size(), 0);
|
||||
|
||||
std::ignore = m_slotMap.Insert(42);
|
||||
const auto slotKey2 = m_slotMap.Insert(69);
|
||||
std::ignore = m_slotMap.Insert(28);
|
||||
std::ignore = m_slotMap.Insert(0);
|
||||
|
||||
EXPECT_EQ(m_slotMap.Size(), 4);
|
||||
|
||||
m_slotMap.Remove(slotKey2);
|
||||
|
||||
EXPECT_EQ(m_slotMap.Size(), 3);
|
||||
|
||||
m_slotMap.Clear();
|
||||
|
||||
EXPECT_EQ(m_slotMap.Size(), 0);
|
||||
|
||||
std::ignore = m_slotMap.Insert(42);
|
||||
std::ignore = m_slotMap.Insert(69);
|
||||
std::ignore = m_slotMap.Insert(28);
|
||||
std::ignore = m_slotMap.Insert(0);
|
||||
|
||||
EXPECT_EQ(m_slotMap.Size(), 4);
|
||||
|
||||
m_slotMap.Reset();
|
||||
|
||||
EXPECT_EQ(m_slotMap.Size(), 0);
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, Empty_ShouldReturnTrueIfTheSlotMapIsEmpty)
|
||||
{
|
||||
EXPECT_TRUE(m_slotMap.Empty());
|
||||
|
||||
std::ignore = m_slotMap.Insert(42);
|
||||
std::ignore = m_slotMap.Insert(69);
|
||||
std::ignore = m_slotMap.Insert(28);
|
||||
std::ignore = m_slotMap.Insert(0);
|
||||
|
||||
m_slotMap.Clear();
|
||||
|
||||
EXPECT_TRUE(m_slotMap.Empty());
|
||||
|
||||
std::ignore = m_slotMap.Insert(42);
|
||||
std::ignore = m_slotMap.Insert(69);
|
||||
std::ignore = m_slotMap.Insert(28);
|
||||
std::ignore = m_slotMap.Insert(0);
|
||||
|
||||
m_slotMap.Reset();
|
||||
|
||||
EXPECT_TRUE(m_slotMap.Empty());
|
||||
|
||||
const auto slotKey9 = m_slotMap.Insert(42);
|
||||
m_slotMap.Remove(slotKey9);
|
||||
|
||||
EXPECT_TRUE(m_slotMap.Empty());
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, Empty_ShouldReturnFalseIfTheSlotMapIsNotEmpty)
|
||||
{
|
||||
std::ignore = m_slotMap.Insert(42);
|
||||
std::ignore = m_slotMap.Insert(69);
|
||||
std::ignore = m_slotMap.Insert(28);
|
||||
std::ignore = m_slotMap.Insert(0);
|
||||
|
||||
EXPECT_FALSE(m_slotMap.Empty());
|
||||
|
||||
m_slotMap.Clear();
|
||||
|
||||
std::ignore = m_slotMap.Insert(42);
|
||||
std::ignore = m_slotMap.Insert(69);
|
||||
std::ignore = m_slotMap.Insert(28);
|
||||
std::ignore = m_slotMap.Insert(0);
|
||||
|
||||
EXPECT_FALSE(m_slotMap.Empty());
|
||||
|
||||
m_slotMap.Reset();
|
||||
|
||||
const auto slotKey9 = m_slotMap.Insert(42);
|
||||
EXPECT_FALSE(m_slotMap.Empty());
|
||||
m_slotMap.Remove(slotKey9);
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, Iterator)
|
||||
{
|
||||
const auto slotKey1 = m_slotMap.Insert(42);
|
||||
const auto slotKey2 = m_slotMap.Insert(69);
|
||||
const auto slotKey3 = m_slotMap.Insert(28);
|
||||
const auto slotKey4 = m_slotMap.Insert(0);
|
||||
|
||||
m_slotMap.Remove(slotKey2);
|
||||
|
||||
eastl::vector<std::uint32_t> values;
|
||||
for (auto it = m_slotMap.begin(); it != m_slotMap.end(); ++it)
|
||||
{
|
||||
values.push_back(*it);
|
||||
}
|
||||
|
||||
ASSERT_EQ(values.size(), 3);
|
||||
EXPECT_EQ(values[0], 42);
|
||||
EXPECT_EQ(values[1], 0);
|
||||
EXPECT_EQ(values[2], 28);
|
||||
|
||||
// The non-const iterator is mutable.
|
||||
for (std::uint32_t& value: m_slotMap)
|
||||
{
|
||||
value += 100;
|
||||
}
|
||||
|
||||
EXPECT_EQ(*m_slotMap.Get(slotKey1), 142);
|
||||
EXPECT_EQ(*m_slotMap.Get(slotKey3), 128);
|
||||
EXPECT_EQ(*m_slotMap.Get(slotKey4), 100);
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, ConstIterator)
|
||||
{
|
||||
std::ignore = m_slotMap.Insert(42);
|
||||
const auto slotKey2 = m_slotMap.Insert(69);
|
||||
std::ignore = m_slotMap.Insert(28);
|
||||
std::ignore = m_slotMap.Insert(0);
|
||||
|
||||
m_slotMap.Remove(slotKey2);
|
||||
|
||||
const auto& constSlotMap = m_slotMap;
|
||||
|
||||
eastl::vector<std::uint32_t> values;
|
||||
for (auto it = constSlotMap.begin(); it != constSlotMap.end(); ++it)
|
||||
{
|
||||
values.push_back(*it);
|
||||
}
|
||||
|
||||
ASSERT_EQ(values.size(), 3);
|
||||
EXPECT_EQ(values[0], 42);
|
||||
EXPECT_EQ(values[1], 0);
|
||||
EXPECT_EQ(values[2], 28);
|
||||
|
||||
// cbegin/cend yield the same const traversal.
|
||||
eastl::vector<std::uint32_t> cValues;
|
||||
for (auto it = m_slotMap.cbegin(); it != m_slotMap.cend(); ++it)
|
||||
{
|
||||
cValues.push_back(*it);
|
||||
}
|
||||
|
||||
EXPECT_EQ(values, cValues);
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, ReverseIterator)
|
||||
{
|
||||
const auto slotKey1 = m_slotMap.Insert(42);
|
||||
const auto slotKey2 = m_slotMap.Insert(69);
|
||||
const auto slotKey3 = m_slotMap.Insert(28);
|
||||
const auto slotKey4 = m_slotMap.Insert(0);
|
||||
|
||||
m_slotMap.Remove(slotKey2);
|
||||
|
||||
eastl::vector<std::uint32_t> values;
|
||||
for (auto it = m_slotMap.rbegin(); it != m_slotMap.rend(); ++it)
|
||||
{
|
||||
values.push_back(*it);
|
||||
}
|
||||
|
||||
ASSERT_EQ(values.size(), 3);
|
||||
EXPECT_EQ(values[0], 28);
|
||||
EXPECT_EQ(values[1], 0);
|
||||
EXPECT_EQ(values[2], 42);
|
||||
|
||||
// The non-const reverse iterator is mutable.
|
||||
for (auto it = m_slotMap.rbegin(); it != m_slotMap.rend(); ++it)
|
||||
{
|
||||
*it += 100;
|
||||
}
|
||||
|
||||
EXPECT_EQ(*m_slotMap.Get(slotKey1), 142);
|
||||
EXPECT_EQ(*m_slotMap.Get(slotKey3), 128);
|
||||
EXPECT_EQ(*m_slotMap.Get(slotKey4), 100);
|
||||
}
|
||||
|
||||
/****************************************************************************************/
|
||||
|
||||
TEST_F(SlotMapFixture, ConstReverseIterator)
|
||||
{
|
||||
std::ignore = m_slotMap.Insert(42);
|
||||
const auto slotKey2 = m_slotMap.Insert(69);
|
||||
std::ignore = m_slotMap.Insert(28);
|
||||
std::ignore = m_slotMap.Insert(0);
|
||||
|
||||
m_slotMap.Remove(slotKey2);
|
||||
|
||||
const auto& constSlotMap = m_slotMap;
|
||||
|
||||
eastl::vector<std::uint32_t> values;
|
||||
for (auto it = constSlotMap.rbegin(); it != constSlotMap.rend(); ++it)
|
||||
{
|
||||
values.push_back(*it);
|
||||
}
|
||||
|
||||
ASSERT_EQ(values.size(), 3);
|
||||
EXPECT_EQ(values[0], 28);
|
||||
EXPECT_EQ(values[1], 0);
|
||||
EXPECT_EQ(values[2], 42);
|
||||
|
||||
// crbegin/crend yield the same const reverse traversal.
|
||||
eastl::vector<std::uint32_t> crValues;
|
||||
for (auto it = m_slotMap.crbegin(); it != m_slotMap.crend(); ++it)
|
||||
{
|
||||
crValues.push_back(*it);
|
||||
}
|
||||
|
||||
EXPECT_EQ(values, crValues);
|
||||
}
|
||||
} // namespace Bigfoot
|
||||
|
||||
Reference in New Issue
Block a user