diff --git a/Bigfoot/Sources/Utils/Include/Utils/Containers/SlotMap.hpp b/Bigfoot/Sources/Utils/Include/Utils/Containers/SlotMap.hpp index 23eafe5..1b2782c 100644 --- a/Bigfoot/Sources/Utils/Include/Utils/Containers/SlotMap.hpp +++ b/Bigfoot/Sources/Utils/Include/Utils/Containers/SlotMap.hpp @@ -15,24 +15,38 @@ namespace Bigfoot { -template + +template && std::is_integral_v, 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::max(); + private: + static constexpr std::uint32_t VERSION_BIT_COUNT = sizeof(VersionType) * + std::numeric_limits::digits; + static constexpr std::uint32_t INDEX_BIT_COUNT = sizeof(IndexType) * std::numeric_limits::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(1) << INDEX_BIT_COUNT) - 1; + + public: static constexpr VersionType INVALID_VERSION = 0; static constexpr IndexType MAX_INDEX = std::numeric_limits::max(); - SlotKey(const VersionType p_version, const IndexType p_index) + SlotKey(const VersionType p_version, const IndexType p_index): + m_key((static_cast(p_version) << INDEX_BIT_COUNT) | + (static_cast(p_index) & INDEX_MASK)) { - m_key = (static_cast(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(m_key >> 32); + return static_cast(m_key >> INDEX_BIT_COUNT); } - IndexType GetIndex() const + IndexType Index() const { - constexpr std::uint64_t indexMask = 0x00000000FFFFFFFF; - return static_cast(m_key & indexMask); + return static_cast(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(m_data.size()); + const typename SlotKey::IndexType dataIndex = static_cast(m_data.size()); m_data.emplace_back(std::forward(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(m_slots.size()); + const typename SlotKey::IndexType slotIndex = static_cast(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::max(); } + void Clear() + { + for (const typename eastl::vector::size_type slotIndex: m_dataToSlots) + { + RecycleSlot(m_slots[slotIndex].Version() + 1, static_cast(slotIndex)); + } + + m_data.clear(); + m_dataToSlots.clear(); + } + + [[nodiscard]] + eastl::vector::size_type Size() const + { + return m_data.size(); + } + + [[nodiscard]] + eastl::vector::size_type Capacity() const + { + return m_data.capacity(); + } + + [[nodiscard]] + eastl::vector::size_type Empty() const + { + return m_data.empty(); + } + + void Reserve(const eastl::vector::size_type p_size) + { + m_data.reserve(p_size); + m_dataToSlots.reserve(p_size); + m_slots.reserve(p_size); + } + + [[nodiscard]] + eastl::vector::iterator begin() + { + return m_data.begin(); + } + + [[nodiscard]] + eastl::vector::iterator end() + { + return m_data.end(); + } + + [[nodiscard]] + eastl::vector::const_iterator begin() const + { + return m_data.begin(); + } + + [[nodiscard]] + eastl::vector::const_iterator end() const + { + return m_data.end(); + } + + [[nodiscard]] + eastl::vector::reverse_iterator rbegin() + { + return m_data.rbegin(); + } + + [[nodiscard]] + eastl::vector::reverse_iterator rend() + { + return m_data.rend(); + } + + [[nodiscard]] + eastl::vector::const_reverse_iterator rbegin() const + { + return m_data.rbegin(); + } + + [[nodiscard]] + eastl::vector::const_reverse_iterator rend() const + { + return m_data.rend(); + } + + [[nodiscard]] + eastl::vector::const_iterator cbegin() const + { + return m_data.cbegin(); + } + + [[nodiscard]] + eastl::vector::const_iterator cend() const + { + return m_data.cend(); + } + + [[nodiscard]] + eastl::vector::const_reverse_iterator crbegin() const + { + return m_data.crbegin(); + } + + [[nodiscard]] + eastl::vector::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::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 m_data; eastl::vector m_slots; eastl::vector::size_type> m_dataToSlots; - typename SlotKey::IndexType m_freeSlotHead; + SlotKey::IndexType m_freeSlotHead; }; } // namespace Bigfoot diff --git a/Bigfoot/Tests/Utils/Containers/SlotMap.cpp b/Bigfoot/Tests/Utils/Containers/SlotMap.cpp index ebd80f1..223c6cd 100644 --- a/Bigfoot/Tests/Utils/Containers/SlotMap.cpp +++ b/Bigfoot/Tests/Utils/Containers/SlotMap.cpp @@ -10,65 +10,451 @@ namespace Bigfoot { +class SlotKeyFixture: public ::testing::Test +{ + protected: +}; + +/****************************************************************************************/ + +TEST_F(SlotKeyFixture, DefaultSlotKeyIsInvalid) +{ + constexpr SlotMap::SlotKey::IndexType index = 0; + constexpr SlotMap::SlotKey::VersionType version = 0; + + SlotMap::SlotKey slotKey {}; + EXPECT_FALSE(slotKey.Valid()); + EXPECT_EQ(slotKey.Version(), index); + EXPECT_EQ(slotKey.Index(), version); +} + +/****************************************************************************************/ + +TEST_F(SlotKeyFixture, Valid_ShouldReturnTrueIfTheSlotKeyIsValid) +{ + constexpr SlotMap::SlotKey::IndexType index = 0; + constexpr SlotMap::SlotKey::VersionType version = 1; + + SlotMap::SlotKey slotKey {version, index}; + EXPECT_TRUE(slotKey.Valid()); +} + +/****************************************************************************************/ + +TEST_F(SlotKeyFixture, Valid_ShouldReturnFalseIfTheSlotKeyIsValid) +{ + constexpr SlotMap::SlotKey::IndexType index = 0; + constexpr SlotMap::SlotKey::VersionType version = 0; + + SlotMap::SlotKey slotKey {version, index}; + EXPECT_FALSE(slotKey.Valid()); +} + +/****************************************************************************************/ + +TEST_F(SlotKeyFixture, Version_ShouldReturnTheVersion) +{ + constexpr SlotMap::SlotKey::IndexType index = 0; + constexpr SlotMap::SlotKey::VersionType version = 42; + + SlotMap::SlotKey slotKey {version, index}; + EXPECT_EQ(slotKey.Version(), version); +} + +/****************************************************************************************/ + +TEST_F(SlotKeyFixture, Index_ShouldReturnTheIndex) +{ + constexpr SlotMap::SlotKey::IndexType index = 42; + constexpr SlotMap::SlotKey::VersionType version = 0; + + SlotMap::SlotKey slotKey {version, index}; + EXPECT_EQ(slotKey.Index(), index); +} + +/****************************************************************************************/ + class SlotMapFixture: public ::testing::Test { protected: - SlotMap m_slotMap; + using SlotMapVersion = std::uint8_t; + using SlotMapIndex = std::uint32_t; + + SlotMap m_slotMap; }; -TEST_F(SlotMapFixture, Insert) +/****************************************************************************************/ + +TEST_F(SlotMapFixture, Insert_ShouldReturnAValidSlotKey) { - const SlotMap::SlotKey firstKey = m_slotMap.Insert(64); - const SlotMap::SlotKey secondKey = m_slotMap.Insert(2); - const SlotMap::SlotKey thirdKey = m_slotMap.Insert(42); - - m_slotMap.Remove(secondKey); - m_slotMap.Remove(firstKey); - - const SlotMap::SlotKey fourthKey = m_slotMap.Insert(3); - const SlotMap::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::SlotKey {})); - EXPECT_FALSE(m_slotMap.Has(SlotMap::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::SlotKey {}), nullptr); - EXPECT_EQ(m_slotMap.Get(SlotMap::SlotKey {1, 4}), nullptr); + const auto slotKey = m_slotMap.Insert(42); + EXPECT_TRUE(slotKey.Valid()); } -TEST(SlotMap, VersionOverflowDeactivatesSlot) +/****************************************************************************************/ + +TEST_F(SlotMapFixture, Insert_ShouldRecycleASlotKeyAfterARemove) { - SlotMap slotMap; + const auto slotKey = m_slotMap.Insert(42); + m_slotMap.Remove(slotKey); - SlotMap::SlotKey key = slotMap.Insert(1); + const auto secondSlotKey = m_slotMap.Insert(69); + EXPECT_NE(secondSlotKey.Version(), slotKey.Version()); + EXPECT_EQ(secondSlotKey.Index(), slotKey.Index()); +} - for (SlotMap::SlotKey::VersionType i = 1; - i < SlotMap::SlotKey::MAX_VERSION; - ++i) +/****************************************************************************************/ + +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::SlotKey {1, 22}))); + EXPECT_FALSE(m_slotMap.Has((SlotMap::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::max(); ++i) { - slotMap.Remove(key); - const SlotMap::SlotKey newKey = slotMap.Insert(1); - EXPECT_EQ(newKey.GetIndex(), key.GetIndex()); + 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::SlotKey::MAX_VERSION)); - slotMap.Remove(key); + EXPECT_EQ(key.Version(), std::numeric_limits::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::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::SlotKey {1, 3}), nullptr); + EXPECT_EQ(p_slotMap.Get(SlotMap::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 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 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 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 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 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 crValues; + for (auto it = m_slotMap.crbegin(); it != m_slotMap.crend(); ++it) + { + crValues.push_back(*it); + } + + EXPECT_EQ(values, crValues); } } // namespace Bigfoot