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
461 lines
13 KiB
C++
461 lines
13 KiB
C++
/*********************************************************************
|
|
* \file SlotMap.cpp
|
|
*
|
|
* \author Romain BOULLARD
|
|
* \date May 2026
|
|
*********************************************************************/
|
|
#include <Utils/Containers/SlotMap.hpp>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
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:
|
|
using SlotMapVersion = std::uint8_t;
|
|
using SlotMapIndex = std::uint32_t;
|
|
|
|
SlotMap<std::uint32_t, SlotMapVersion, SlotMapIndex> m_slotMap;
|
|
};
|
|
|
|
/****************************************************************************************/
|
|
|
|
TEST_F(SlotMapFixture, Insert_ShouldReturnAValidSlotKey)
|
|
{
|
|
const auto slotKey = m_slotMap.Insert(42);
|
|
EXPECT_TRUE(slotKey.Valid());
|
|
}
|
|
|
|
/****************************************************************************************/
|
|
|
|
TEST_F(SlotMapFixture, Insert_ShouldRecycleASlotKeyAfterARemove)
|
|
{
|
|
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.Version(), std::numeric_limits<SlotMapVersion>::max());
|
|
m_slotMap.Remove(key);
|
|
|
|
EXPECT_FALSE(m_slotMap.Has(key));
|
|
|
|
// Dead slot must not be recycled; a new insert must allocate a fresh slot index
|
|
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
|