SlotMap testing
Bigfoot / Build & Test Debug with ./ConanProfiles/clang (Unity Build: OFF) (push) Successful in 6m14s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang (Unity Build: ON) (push) Successful in 5m45s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Successful in 6m10s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Successful in 6m0s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang (Unity Build: OFF) (push) Successful in 6m20s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang (Unity Build: ON) (push) Successful in 6m21s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Successful in 7m22s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Successful in 7m22s
Bigfoot / Build & Test Release with ./ConanProfiles/clang (Unity Build: OFF) (push) Successful in 6m12s
Bigfoot / Build & Test Release with ./ConanProfiles/clang (Unity Build: ON) (push) Successful in 6m11s
Bigfoot / Build & Test Release with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Successful in 7m4s
Bigfoot / Build & Test Release with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Successful in 6m59s
Bigfoot / Clang Format Checks (push) Failing after 1m43s

This commit is contained in:
2026-08-09 15:33:12 +02:00
parent f9a57e5172
commit f5e3a447c8
2 changed files with 890 additions and 2 deletions
@@ -227,7 +227,7 @@ class DenseSlotMap: public SlotMapBase
}
[[nodiscard]]
std::size_t GetCapacity() const
std::size_t Capacity() const
{
return m_data.capacity();
}
@@ -753,7 +753,7 @@ class StableSlotMap: public SlotMapBase
}
[[nodiscard]]
std::size_t GetCapacity() const
std::size_t Capacity() const
{
return m_pages.size() * PAGE_SIZE;
}
+888
View File
@@ -111,6 +111,17 @@ TYPED_TEST(SlotMapFixture, Has_ReturnFalseIfSlotMapDoesNotHaveTheKey)
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, Has_ReturnFalseForStaleKey)
{
const SlotMapKey staleKey = this->m_slotMap.Insert(42);
this->m_slotMap.Remove(staleKey);
std::ignore = this->m_slotMap.Insert(69);
EXPECT_FALSE(this->m_slotMap.Has(staleKey));
}
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, Size_ShouldReturnTheSizeOfTheSlotMap)
{
EXPECT_EQ(this->m_slotMap.Size(), 0);
@@ -147,4 +158,881 @@ TYPED_TEST(SlotMapFixture, Empty_ShouldReturnFalseIfTheSlotMapIsNotEmpty)
EXPECT_FALSE(this->m_slotMap.Empty());
this->m_slotMap.Remove(key2);
}
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, Insert_ShouldReturnAValidSlotKey)
{
EXPECT_TRUE(this->m_slotMap.Insert(42).Valid());
}
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, Insert_ShouldRecycleIndicesWhenPossible)
{
const SlotMapKey key1 = this->m_slotMap.Insert(42);
const SlotMapKey key2 = this->m_slotMap.Insert(69);
this->m_slotMap.Remove(key1);
this->m_slotMap.Remove(key2);
const SlotMapKey key11 = this->m_slotMap.Insert(42);
const SlotMapKey key21 = this->m_slotMap.Insert(69);
EXPECT_EQ(key1.GetVersion() + 1, key11.GetVersion());
EXPECT_EQ(key1.GetIndex(), key21.GetIndex()); //LIFO
EXPECT_EQ(key2.GetVersion() + 1, key21.GetVersion());
EXPECT_EQ(key2.GetIndex(), key11.GetIndex()); //LIFO
}
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, Get_ShouldReturnTheValue)
{
const SlotMapKey key = this->m_slotMap.Insert(42);
EXPECT_EQ(*this->m_slotMap.Get(key), 42);
EXPECT_EQ(*const_cast<const TypeParam&>(this->m_slotMap).Get(key), 42);
}
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, Get_ShouldNullptrIfTheValueIsNotInMap)
{
const SlotMapKey key {1, 0};
EXPECT_EQ(this->m_slotMap.Get(key), nullptr);
EXPECT_EQ(const_cast<const TypeParam&>(this->m_slotMap).Get(key), nullptr);
}
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, Get_ShouldReturnNullptrForStaleKey)
{
const SlotMapKey staleKey = this->m_slotMap.Insert(42);
this->m_slotMap.Remove(staleKey);
std::ignore = this->m_slotMap.Insert(69);
EXPECT_EQ(this->m_slotMap.Get(staleKey), nullptr);
EXPECT_EQ(const_cast<const TypeParam&>(this->m_slotMap).Get(staleKey), nullptr);
}
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, Remove_ShouldRemoveTheValue)
{
const SlotMapKey key = this->m_slotMap.Insert(42);
EXPECT_TRUE(this->m_slotMap.Has(key));
this->m_slotMap.Remove(key);
EXPECT_FALSE(this->m_slotMap.Has(key));
}
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, Remove_ShouldBeANoOpForStaleKey)
{
const SlotMapKey staleKey = this->m_slotMap.Insert(42);
this->m_slotMap.Remove(staleKey);
const SlotMapKey freshKey = this->m_slotMap.Insert(69);
this->m_slotMap.Remove(staleKey);
EXPECT_TRUE(this->m_slotMap.Has(freshKey));
EXPECT_EQ(*this->m_slotMap.Get(freshKey), 69);
EXPECT_EQ(this->m_slotMap.Size(), 1);
}
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, Reset_ShouldClearTheMapAndOldKeysAreValid)
{
const SlotMapKey key1 = this->m_slotMap.Insert(42);
const SlotMapKey key2 = this->m_slotMap.Insert(69);
this->m_slotMap.Reset();
EXPECT_EQ(this->m_slotMap.Size(), 0);
EXPECT_TRUE(this->m_slotMap.Empty());
EXPECT_FALSE(this->m_slotMap.Has(key1));
EXPECT_FALSE(this->m_slotMap.Has(key2));
EXPECT_EQ(this->m_slotMap.Insert(42), key1);
EXPECT_EQ(this->m_slotMap.Insert(69), key2);
}
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, Clear_ShouldClearTheMapAndOldKeysAreInvalid)
{
const SlotMapKey key1 = this->m_slotMap.Insert(42);
const SlotMapKey key2 = this->m_slotMap.Insert(69);
this->m_slotMap.Clear();
EXPECT_EQ(this->m_slotMap.Size(), 0);
EXPECT_TRUE(this->m_slotMap.Empty());
EXPECT_FALSE(this->m_slotMap.Has(key1));
EXPECT_FALSE(this->m_slotMap.Has(key2));
EXPECT_NE(this->m_slotMap.Insert(42), key1);
EXPECT_NE(this->m_slotMap.Insert(69), key2);
}
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, Iterator)
{
EXPECT_EQ(this->m_slotMap.begin(), this->m_slotMap.end());
std::ignore = this->m_slotMap.Insert(1);
const SlotMapKey key2 = this->m_slotMap.Insert(2);
std::ignore = this->m_slotMap.Insert(3);
// Forward traversal follows insertion order
std::uint32_t expected = 1;
for (auto it = this->m_slotMap.begin(); it != this->m_slotMap.end(); ++it)
{
EXPECT_EQ(*it, expected);
++expected;
}
// Pre-increment advances and returns *this
auto preIt = this->m_slotMap.begin();
auto& preIncrementResult = ++preIt;
EXPECT_EQ(&preIncrementResult, &preIt);
EXPECT_EQ(*preIt, 2);
// Post-increment advances but returns the old value
auto postIt = this->m_slotMap.begin();
const auto postIncrementResult = postIt++;
EXPECT_EQ(*postIncrementResult, 1);
EXPECT_EQ(*postIt, 2);
// Pre-decrement retreats and returns *this
auto preDecIt = this->m_slotMap.end();
--preDecIt; // now at 3
auto& preDecrementResult = --preDecIt; // now at 2
EXPECT_EQ(&preDecrementResult, &preDecIt);
EXPECT_EQ(*preDecIt, 2);
// Post-decrement retreats but returns the old value
auto postDecIt = this->m_slotMap.end();
--postDecIt; // now at 3
const auto postDecrementResult = postDecIt--;
EXPECT_EQ(*postDecrementResult, 3);
EXPECT_EQ(*postDecIt, 2);
// Removed slots are skipped during traversal
this->m_slotMap.Remove(key2);
std::size_t count = 0;
std::uint32_t sum = 0;
for (const std::uint32_t value: this->m_slotMap)
{
++count;
sum += value;
}
EXPECT_EQ(count, 2);
EXPECT_EQ(sum, 4);
// Multi-page traversal: StableSlotMap's PAGE_SIZE is 64, so 100 elements force it to span pages
this->m_slotMap.Reset();
constexpr std::uint32_t elementCount = 100;
eastl::array<SlotMapKey, elementCount> keys;
for (std::uint32_t value = 0; value < elementCount; ++value)
{
keys[value] = this->m_slotMap.Insert(value);
}
expected = 0;
for (const std::uint32_t value: this->m_slotMap)
{
EXPECT_EQ(value, expected);
++expected;
}
EXPECT_EQ(expected, elementCount);
// Explicitly cross the page boundary (index 63 -> 64) forward and backward
auto boundaryIt = this->m_slotMap.begin();
for (std::uint32_t step = 0; step < 63; ++step)
{
++boundaryIt;
}
EXPECT_EQ(*boundaryIt, 63);
++boundaryIt;
EXPECT_EQ(*boundaryIt, 64);
--boundaryIt;
EXPECT_EQ(*boundaryIt, 63);
// Pre/post-increment/decrement across the boundary
auto preBoundaryIt = this->m_slotMap.begin();
auto& preBoundaryIncrementResult = ++preBoundaryIt;
EXPECT_EQ(&preBoundaryIncrementResult, &preBoundaryIt);
EXPECT_EQ(*preBoundaryIt, 1);
auto postBoundaryIt = this->m_slotMap.begin();
const auto postBoundaryIncrementResult = postBoundaryIt++;
EXPECT_EQ(*postBoundaryIncrementResult, 0);
EXPECT_EQ(*postBoundaryIt, 1);
auto preBoundaryDecIt = this->m_slotMap.end();
--preBoundaryDecIt; // now at 99
auto& preBoundaryDecrementResult = --preBoundaryDecIt; // now at 98
EXPECT_EQ(&preBoundaryDecrementResult, &preBoundaryDecIt);
EXPECT_EQ(*preBoundaryDecIt, 98);
auto postBoundaryDecIt = this->m_slotMap.end();
--postBoundaryDecIt; // now at 99
const auto postBoundaryDecrementResult = postBoundaryDecIt--;
EXPECT_EQ(*postBoundaryDecrementResult, 99);
EXPECT_EQ(*postBoundaryDecIt, 98);
// Remove elements straddling the boundary and check the holes are skipped
this->m_slotMap.Remove(keys[63]);
this->m_slotMap.Remove(keys[64]);
count = 0;
for (const std::uint32_t value: this->m_slotMap)
{
EXPECT_NE(value, 63);
EXPECT_NE(value, 64);
++count;
}
EXPECT_EQ(count, elementCount - 2);
// A fully empty leading page must be skipped by begin()/SeekFirstFrom
this->m_slotMap.Reset();
eastl::array<SlotMapKey, elementCount> leadingKeys;
for (std::uint32_t value = 0; value < elementCount; ++value)
{
leadingKeys[value] = this->m_slotMap.Insert(value);
}
for (std::uint32_t value = 0; value < 64; ++value)
{
this->m_slotMap.Remove(leadingKeys[value]);
}
count = 0;
sum = 0;
for (const std::uint32_t value: this->m_slotMap)
{
++count;
sum += value;
}
EXPECT_EQ(count, elementCount - 64);
EXPECT_EQ(sum, 2934u); // 64 + 65 + ... + 99
// A fully empty trailing page must be skipped when retreating past it
this->m_slotMap.Reset();
eastl::array<SlotMapKey, elementCount> trailingKeys;
for (std::uint32_t value = 0; value < elementCount; ++value)
{
trailingKeys[value] = this->m_slotMap.Insert(value);
}
for (std::uint32_t value = 64; value < elementCount; ++value)
{
this->m_slotMap.Remove(trailingKeys[value]);
}
count = 0;
sum = 0;
for (const std::uint32_t value: this->m_slotMap)
{
++count;
sum += value;
}
EXPECT_EQ(count, 64u);
EXPECT_EQ(sum, 2016u); // 0 + 1 + ... + 63
}
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, ConstIterator)
{
const TypeParam& constMap = this->m_slotMap;
// const_iterator is reachable two ways: cbegin()/cend(), or begin()/end() on a const object
EXPECT_EQ(this->m_slotMap.cbegin(), this->m_slotMap.cend());
EXPECT_EQ(constMap.begin(), constMap.end());
std::ignore = this->m_slotMap.Insert(1);
const SlotMapKey key2 = this->m_slotMap.Insert(2);
std::ignore = this->m_slotMap.Insert(3);
std::uint32_t expected = 1;
for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it)
{
EXPECT_EQ(*it, expected);
++expected;
}
expected = 1;
for (auto it = constMap.begin(); it != constMap.end(); ++it)
{
EXPECT_EQ(*it, expected);
++expected;
}
// Pre-increment advances and returns *this
auto preIt = this->m_slotMap.cbegin();
auto& preIncrementResult = ++preIt;
EXPECT_EQ(&preIncrementResult, &preIt);
EXPECT_EQ(*preIt, 2);
// Post-increment advances but returns the old value
auto postIt = this->m_slotMap.cbegin();
const auto postIncrementResult = postIt++;
EXPECT_EQ(*postIncrementResult, 1);
EXPECT_EQ(*postIt, 2);
// Pre-decrement retreats and returns *this
auto preDecIt = this->m_slotMap.cend();
--preDecIt; // now at 3
auto& preDecrementResult = --preDecIt; // now at 2
EXPECT_EQ(&preDecrementResult, &preDecIt);
EXPECT_EQ(*preDecIt, 2);
// Post-decrement retreats but returns the old value
auto postDecIt = this->m_slotMap.cend();
--postDecIt; // now at 3
const auto postDecrementResult = postDecIt--;
EXPECT_EQ(*postDecrementResult, 3);
EXPECT_EQ(*postDecIt, 2);
// Removed slots are skipped identically through cbegin()/cend() and the const-object overloads
this->m_slotMap.Remove(key2);
std::size_t count = 0;
std::uint32_t sum = 0;
for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it)
{
++count;
sum += *it;
}
EXPECT_EQ(count, 2);
EXPECT_EQ(sum, 4);
count = 0;
sum = 0;
for (auto it = constMap.begin(); it != constMap.end(); ++it)
{
++count;
sum += *it;
}
EXPECT_EQ(count, 2);
EXPECT_EQ(sum, 4);
// Multi-page traversal: StableSlotMap's PAGE_SIZE is 64, so 100 elements force it to span pages
this->m_slotMap.Reset();
constexpr std::uint32_t elementCount = 100;
eastl::array<SlotMapKey, elementCount> keys;
for (std::uint32_t value = 0; value < elementCount; ++value)
{
keys[value] = this->m_slotMap.Insert(value);
}
expected = 0;
for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it)
{
EXPECT_EQ(*it, expected);
++expected;
}
EXPECT_EQ(expected, elementCount);
// Explicitly cross the page boundary (index 63 -> 64) forward and backward
auto boundaryIt = this->m_slotMap.cbegin();
for (std::uint32_t step = 0; step < 63; ++step)
{
++boundaryIt;
}
EXPECT_EQ(*boundaryIt, 63);
++boundaryIt;
EXPECT_EQ(*boundaryIt, 64);
--boundaryIt;
EXPECT_EQ(*boundaryIt, 63);
// Pre/post-increment/decrement across the boundary
auto preBoundaryIt = this->m_slotMap.cbegin();
auto& preBoundaryIncrementResult = ++preBoundaryIt;
EXPECT_EQ(&preBoundaryIncrementResult, &preBoundaryIt);
EXPECT_EQ(*preBoundaryIt, 1);
auto postBoundaryIt = this->m_slotMap.cbegin();
const auto postBoundaryIncrementResult = postBoundaryIt++;
EXPECT_EQ(*postBoundaryIncrementResult, 0);
EXPECT_EQ(*postBoundaryIt, 1);
auto preBoundaryDecIt = this->m_slotMap.cend();
--preBoundaryDecIt; // now at 99
auto& preBoundaryDecrementResult = --preBoundaryDecIt; // now at 98
EXPECT_EQ(&preBoundaryDecrementResult, &preBoundaryDecIt);
EXPECT_EQ(*preBoundaryDecIt, 98);
auto postBoundaryDecIt = this->m_slotMap.cend();
--postBoundaryDecIt; // now at 99
const auto postBoundaryDecrementResult = postBoundaryDecIt--;
EXPECT_EQ(*postBoundaryDecrementResult, 99);
EXPECT_EQ(*postBoundaryDecIt, 98);
// Remove elements straddling the boundary and check the holes are skipped
this->m_slotMap.Remove(keys[63]);
this->m_slotMap.Remove(keys[64]);
count = 0;
for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it)
{
EXPECT_NE(*it, 63);
EXPECT_NE(*it, 64);
++count;
}
EXPECT_EQ(count, elementCount - 2);
// A fully empty leading page must be skipped by cbegin()/SeekFirstFrom
this->m_slotMap.Reset();
eastl::array<SlotMapKey, elementCount> leadingKeys;
for (std::uint32_t value = 0; value < elementCount; ++value)
{
leadingKeys[value] = this->m_slotMap.Insert(value);
}
for (std::uint32_t value = 0; value < 64; ++value)
{
this->m_slotMap.Remove(leadingKeys[value]);
}
count = 0;
sum = 0;
for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it)
{
++count;
sum += *it;
}
EXPECT_EQ(count, elementCount - 64);
EXPECT_EQ(sum, 2934u); // 64 + 65 + ... + 99
// A fully empty trailing page must be skipped when retreating past it
this->m_slotMap.Reset();
eastl::array<SlotMapKey, elementCount> trailingKeys;
for (std::uint32_t value = 0; value < elementCount; ++value)
{
trailingKeys[value] = this->m_slotMap.Insert(value);
}
for (std::uint32_t value = 64; value < elementCount; ++value)
{
this->m_slotMap.Remove(trailingKeys[value]);
}
count = 0;
sum = 0;
for (auto it = this->m_slotMap.cbegin(); it != this->m_slotMap.cend(); ++it)
{
++count;
sum += *it;
}
EXPECT_EQ(count, 64u);
EXPECT_EQ(sum, 2016u); // 0 + 1 + ... + 63
}
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, ReverseIterator)
{
EXPECT_EQ(this->m_slotMap.rbegin(), this->m_slotMap.rend());
std::ignore = this->m_slotMap.Insert(1);
const SlotMapKey key2 = this->m_slotMap.Insert(2);
std::ignore = this->m_slotMap.Insert(3);
// Reverse traversal mirrors insertion order
std::uint32_t expected = 3;
for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it)
{
EXPECT_EQ(*it, expected);
--expected;
}
// Pre-increment advances and returns *this
auto preIt = this->m_slotMap.rbegin();
auto& preIncrementResult = ++preIt;
EXPECT_EQ(&preIncrementResult, &preIt);
EXPECT_EQ(*preIt, 2);
// Post-increment advances but returns the old value
auto postIt = this->m_slotMap.rbegin();
const auto postIncrementResult = postIt++;
EXPECT_EQ(*postIncrementResult, 3);
EXPECT_EQ(*postIt, 2);
// Pre-decrement retreats and returns *this
auto preDecIt = this->m_slotMap.rend();
--preDecIt; // now at 1
auto& preDecrementResult = --preDecIt; // now at 2
EXPECT_EQ(&preDecrementResult, &preDecIt);
EXPECT_EQ(*preDecIt, 2);
// Post-decrement retreats but returns the old value
auto postDecIt = this->m_slotMap.rend();
--postDecIt; // now at 1
const auto postDecrementResult = postDecIt--;
EXPECT_EQ(*postDecrementResult, 1);
EXPECT_EQ(*postDecIt, 2);
// Removed slots are skipped during traversal
this->m_slotMap.Remove(key2);
std::size_t count = 0;
std::uint32_t sum = 0;
for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it)
{
++count;
sum += *it;
}
EXPECT_EQ(count, 2);
EXPECT_EQ(sum, 4);
// Multi-page traversal: StableSlotMap's PAGE_SIZE is 64, so 100 elements force it to span pages
this->m_slotMap.Reset();
constexpr std::uint32_t elementCount = 100;
eastl::array<SlotMapKey, elementCount> keys;
for (std::uint32_t value = 0; value < elementCount; ++value)
{
keys[value] = this->m_slotMap.Insert(value);
}
expected = elementCount - 1;
for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it)
{
EXPECT_EQ(*it, expected);
--expected;
}
// Explicitly cross the page boundary (value 64 -> 63) forward and backward
auto boundaryIt = this->m_slotMap.rbegin();
while (*boundaryIt != 64)
{
++boundaryIt;
}
++boundaryIt;
EXPECT_EQ(*boundaryIt, 63);
--boundaryIt;
EXPECT_EQ(*boundaryIt, 64);
// Pre/post-increment/decrement across the boundary
auto preBoundaryIt = this->m_slotMap.rbegin();
auto& preBoundaryIncrementResult = ++preBoundaryIt;
EXPECT_EQ(&preBoundaryIncrementResult, &preBoundaryIt);
EXPECT_EQ(*preBoundaryIt, 98);
auto postBoundaryIt = this->m_slotMap.rbegin();
const auto postBoundaryIncrementResult = postBoundaryIt++;
EXPECT_EQ(*postBoundaryIncrementResult, 99);
EXPECT_EQ(*postBoundaryIt, 98);
auto preBoundaryDecIt = this->m_slotMap.rend();
--preBoundaryDecIt; // now at 0
auto& preBoundaryDecrementResult = --preBoundaryDecIt; // now at 1
EXPECT_EQ(&preBoundaryDecrementResult, &preBoundaryDecIt);
EXPECT_EQ(*preBoundaryDecIt, 1);
auto postBoundaryDecIt = this->m_slotMap.rend();
--postBoundaryDecIt; // now at 0
const auto postBoundaryDecrementResult = postBoundaryDecIt--;
EXPECT_EQ(*postBoundaryDecrementResult, 0);
EXPECT_EQ(*postBoundaryDecIt, 1);
// Remove elements straddling the boundary and check the holes are skipped
this->m_slotMap.Remove(keys[63]);
this->m_slotMap.Remove(keys[64]);
count = 0;
for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it)
{
EXPECT_NE(*it, 63);
EXPECT_NE(*it, 64);
++count;
}
EXPECT_EQ(count, elementCount - 2);
// A fully empty leading page must be skipped when retreating past it (rend() side)
this->m_slotMap.Reset();
eastl::array<SlotMapKey, elementCount> leadingKeys;
for (std::uint32_t value = 0; value < elementCount; ++value)
{
leadingKeys[value] = this->m_slotMap.Insert(value);
}
for (std::uint32_t value = 0; value < 64; ++value)
{
this->m_slotMap.Remove(leadingKeys[value]);
}
count = 0;
sum = 0;
for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it)
{
++count;
sum += *it;
}
EXPECT_EQ(count, elementCount - 64);
EXPECT_EQ(sum, 2934u); // 64 + 65 + ... + 99
// A fully empty trailing page must be skipped by rbegin()/SeekLastFrom
this->m_slotMap.Reset();
eastl::array<SlotMapKey, elementCount> trailingKeys;
for (std::uint32_t value = 0; value < elementCount; ++value)
{
trailingKeys[value] = this->m_slotMap.Insert(value);
}
for (std::uint32_t value = 64; value < elementCount; ++value)
{
this->m_slotMap.Remove(trailingKeys[value]);
}
count = 0;
sum = 0;
for (auto it = this->m_slotMap.rbegin(); it != this->m_slotMap.rend(); ++it)
{
++count;
sum += *it;
}
EXPECT_EQ(count, 64u);
EXPECT_EQ(sum, 2016u); // 0 + 1 + ... + 63
}
/****************************************************************************************/
TYPED_TEST(SlotMapFixture, ConstReverseIterator)
{
const TypeParam& constMap = this->m_slotMap;
// const_reverse_iterator is reachable two ways: crbegin()/crend(), or rbegin()/rend() on a
// const object
EXPECT_EQ(this->m_slotMap.crbegin(), this->m_slotMap.crend());
EXPECT_EQ(constMap.rbegin(), constMap.rend());
std::ignore = this->m_slotMap.Insert(1);
const SlotMapKey key2 = this->m_slotMap.Insert(2);
std::ignore = this->m_slotMap.Insert(3);
std::uint32_t expected = 3;
for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it)
{
EXPECT_EQ(*it, expected);
--expected;
}
expected = 3;
for (auto it = constMap.rbegin(); it != constMap.rend(); ++it)
{
EXPECT_EQ(*it, expected);
--expected;
}
// Pre-increment advances and returns *this
auto preIt = this->m_slotMap.crbegin();
auto& preIncrementResult = ++preIt;
EXPECT_EQ(&preIncrementResult, &preIt);
EXPECT_EQ(*preIt, 2);
// Post-increment advances but returns the old value
auto postIt = this->m_slotMap.crbegin();
const auto postIncrementResult = postIt++;
EXPECT_EQ(*postIncrementResult, 3);
EXPECT_EQ(*postIt, 2);
// Pre-decrement retreats and returns *this
auto preDecIt = this->m_slotMap.crend();
--preDecIt; // now at 1
auto& preDecrementResult = --preDecIt; // now at 2
EXPECT_EQ(&preDecrementResult, &preDecIt);
EXPECT_EQ(*preDecIt, 2);
// Post-decrement retreats but returns the old value
auto postDecIt = this->m_slotMap.crend();
--postDecIt; // now at 1
const auto postDecrementResult = postDecIt--;
EXPECT_EQ(*postDecrementResult, 1);
EXPECT_EQ(*postDecIt, 2);
// Removed slots are skipped identically through crbegin()/crend() and the const-object overloads
this->m_slotMap.Remove(key2);
std::size_t count = 0;
std::uint32_t sum = 0;
for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it)
{
++count;
sum += *it;
}
EXPECT_EQ(count, 2);
EXPECT_EQ(sum, 4);
count = 0;
sum = 0;
for (auto it = constMap.rbegin(); it != constMap.rend(); ++it)
{
++count;
sum += *it;
}
EXPECT_EQ(count, 2);
EXPECT_EQ(sum, 4);
// Multi-page traversal: StableSlotMap's PAGE_SIZE is 64, so 100 elements force it to span pages
this->m_slotMap.Reset();
constexpr std::uint32_t elementCount = 100;
eastl::array<SlotMapKey, elementCount> keys;
for (std::uint32_t value = 0; value < elementCount; ++value)
{
keys[value] = this->m_slotMap.Insert(value);
}
expected = elementCount - 1;
for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it)
{
EXPECT_EQ(*it, expected);
--expected;
}
// Explicitly cross the page boundary (value 64 -> 63) forward and backward
auto boundaryIt = this->m_slotMap.crbegin();
while (*boundaryIt != 64)
{
++boundaryIt;
}
++boundaryIt;
EXPECT_EQ(*boundaryIt, 63);
--boundaryIt;
EXPECT_EQ(*boundaryIt, 64);
// Pre/post-increment/decrement across the boundary
auto preBoundaryIt = this->m_slotMap.crbegin();
auto& preBoundaryIncrementResult = ++preBoundaryIt;
EXPECT_EQ(&preBoundaryIncrementResult, &preBoundaryIt);
EXPECT_EQ(*preBoundaryIt, 98);
auto postBoundaryIt = this->m_slotMap.crbegin();
const auto postBoundaryIncrementResult = postBoundaryIt++;
EXPECT_EQ(*postBoundaryIncrementResult, 99);
EXPECT_EQ(*postBoundaryIt, 98);
auto preBoundaryDecIt = this->m_slotMap.crend();
--preBoundaryDecIt; // now at 0
auto& preBoundaryDecrementResult = --preBoundaryDecIt; // now at 1
EXPECT_EQ(&preBoundaryDecrementResult, &preBoundaryDecIt);
EXPECT_EQ(*preBoundaryDecIt, 1);
auto postBoundaryDecIt = this->m_slotMap.crend();
--postBoundaryDecIt; // now at 0
const auto postBoundaryDecrementResult = postBoundaryDecIt--;
EXPECT_EQ(*postBoundaryDecrementResult, 0);
EXPECT_EQ(*postBoundaryDecIt, 1);
// Remove elements straddling the boundary and check the holes are skipped
this->m_slotMap.Remove(keys[63]);
this->m_slotMap.Remove(keys[64]);
count = 0;
for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it)
{
EXPECT_NE(*it, 63);
EXPECT_NE(*it, 64);
++count;
}
EXPECT_EQ(count, elementCount - 2);
// A fully empty leading page must be skipped when retreating past it (crend() side)
this->m_slotMap.Reset();
eastl::array<SlotMapKey, elementCount> leadingKeys;
for (std::uint32_t value = 0; value < elementCount; ++value)
{
leadingKeys[value] = this->m_slotMap.Insert(value);
}
for (std::uint32_t value = 0; value < 64; ++value)
{
this->m_slotMap.Remove(leadingKeys[value]);
}
count = 0;
sum = 0;
for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it)
{
++count;
sum += *it;
}
EXPECT_EQ(count, elementCount - 64);
EXPECT_EQ(sum, 2934u); // 64 + 65 + ... + 99
// A fully empty trailing page must be skipped by crbegin()/SeekLastFrom
this->m_slotMap.Reset();
eastl::array<SlotMapKey, elementCount> trailingKeys;
for (std::uint32_t value = 0; value < elementCount; ++value)
{
trailingKeys[value] = this->m_slotMap.Insert(value);
}
for (std::uint32_t value = 64; value < elementCount; ++value)
{
this->m_slotMap.Remove(trailingKeys[value]);
}
count = 0;
sum = 0;
for (auto it = this->m_slotMap.crbegin(); it != this->m_slotMap.crend(); ++it)
{
++count;
sum += *it;
}
EXPECT_EQ(count, 64u);
EXPECT_EQ(sum, 2016u); // 0 + 1 + ... + 63
}
/****************************************************************************************/
class StableSlotMapFixture: public ::testing::Test
{
protected:
StableSlotMap<std::uint32_t> m_slotMap;
};
/****************************************************************************************/
TEST_F(StableSlotMapFixture, PointersAreStable)
{
const SlotMapKey key = m_slotMap.Insert(42);
const std::uint32_t* const pointer = m_slotMap.Get(key);
// Inserting enough elements to span multiple pages (PAGE_SIZE is 64) must not move already
// -inserted data: unlike DenseSlotMap's contiguous eastl::vector, growth only appends new
// Page objects (each individually heap-allocated), it never reallocates existing ones.
constexpr std::uint32_t elementCount = 200;
eastl::array<SlotMapKey, elementCount> keys;
for (std::uint32_t value = 0; value < elementCount; ++value)
{
keys[value] = m_slotMap.Insert(value);
}
EXPECT_EQ(m_slotMap.Get(key), pointer);
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.
for (std::uint32_t value = 0; value < elementCount; value += 2)
{
m_slotMap.Remove(keys[value]);
}
EXPECT_EQ(m_slotMap.Get(key), pointer);
EXPECT_EQ(*pointer, 42);
// Re-inserting into the freed slots must not move it either
for (std::uint32_t value = 0; value < elementCount / 2; ++value)
{
std::ignore = m_slotMap.Insert(value);
}
EXPECT_EQ(m_slotMap.Get(key), pointer);
EXPECT_EQ(*pointer, 42);
}
} // namespace Bigfoot