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
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:
@@ -227,7 +227,7 @@ class DenseSlotMap: public SlotMapBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
std::size_t GetCapacity() const
|
std::size_t Capacity() const
|
||||||
{
|
{
|
||||||
return m_data.capacity();
|
return m_data.capacity();
|
||||||
}
|
}
|
||||||
@@ -753,7 +753,7 @@ class StableSlotMap: public SlotMapBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
std::size_t GetCapacity() const
|
std::size_t Capacity() const
|
||||||
{
|
{
|
||||||
return m_pages.size() * PAGE_SIZE;
|
return m_pages.size() * PAGE_SIZE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
TYPED_TEST(SlotMapFixture, Size_ShouldReturnTheSizeOfTheSlotMap)
|
||||||
{
|
{
|
||||||
EXPECT_EQ(this->m_slotMap.Size(), 0);
|
EXPECT_EQ(this->m_slotMap.Size(), 0);
|
||||||
@@ -147,4 +158,881 @@ TYPED_TEST(SlotMapFixture, Empty_ShouldReturnFalseIfTheSlotMapIsNotEmpty)
|
|||||||
EXPECT_FALSE(this->m_slotMap.Empty());
|
EXPECT_FALSE(this->m_slotMap.Empty());
|
||||||
this->m_slotMap.Remove(key2);
|
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
|
} // namespace Bigfoot
|
||||||
|
|||||||
Reference in New Issue
Block a user