SlotMap impl and tests
Bigfoot / Build & Test Debug with ./ConanProfiles/clang (Unity Build: OFF) (push) Successful in 5m31s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang (Unity Build: ON) (push) Successful in 5m31s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Successful in 5m44s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Successful in 5m49s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang (Unity Build: ON) (push) Has been cancelled
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Has been cancelled
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Has been cancelled
Bigfoot / Build & Test Release with ./ConanProfiles/clang (Unity Build: OFF) (push) Has been cancelled
Bigfoot / Build & Test Release with ./ConanProfiles/clang (Unity Build: ON) (push) Has been cancelled
Bigfoot / Build & Test Release with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Has been cancelled
Bigfoot / Build & Test Release with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Has been cancelled
Bigfoot / Clang Format Checks (push) Has been cancelled
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang (Unity Build: OFF) (push) Has been cancelled

This commit is contained in:
2026-05-15 22:59:30 +02:00
parent 65727753c0
commit 09ff240423
2 changed files with 587 additions and 75 deletions
@@ -15,24 +15,38 @@
namespace Bigfoot
{
template<class TYPE, class VERSION_TYPE = std::uint32_t>
template<class TYPE,
class VERSION_TYPE = std::uint32_t,
class INDEX_TYPE = std::uint32_t,
std::enable_if_t<std::is_integral_v<VERSION_TYPE> && std::is_integral_v<INDEX_TYPE>, bool> = true>
class SlotMap
{
public:
class SlotKey
{
public:
using IndexType = std::uint32_t;
using IndexType = INDEX_TYPE;
using VersionType = VERSION_TYPE;
static constexpr VersionType MAX_VERSION = std::numeric_limits<VersionType>::max();
private:
static constexpr std::uint32_t VERSION_BIT_COUNT = sizeof(VersionType) *
std::numeric_limits<unsigned char>::digits;
static constexpr std::uint32_t INDEX_BIT_COUNT = sizeof(IndexType) * std::numeric_limits<unsigned char>::digits;
static_assert(VERSION_BIT_COUNT + INDEX_BIT_COUNT <= 64,
"We cant construct a 64 bit key from the given Version and Index types!");
static constexpr std::uint64_t INDEX_MASK = (static_cast<std::uint64_t>(1) << INDEX_BIT_COUNT) - 1;
public:
static constexpr VersionType INVALID_VERSION = 0;
static constexpr IndexType MAX_INDEX = std::numeric_limits<IndexType>::max();
SlotKey(const VersionType p_version, const IndexType p_index)
SlotKey(const VersionType p_version, const IndexType p_index):
m_key((static_cast<std::uint64_t>(p_version) << INDEX_BIT_COUNT) |
(static_cast<std::uint64_t>(p_index) & INDEX_MASK))
{
m_key = (static_cast<std::uint64_t>(p_version) << 32) | p_index;
}
SlotKey():
@@ -45,25 +59,27 @@ class SlotMap
~SlotKey() = default;
bool IsValid() const
bool Valid() const
{
return GetVersion() != INVALID_VERSION;
return Version() != INVALID_VERSION;
}
VersionType GetVersion() const
VersionType Version() const
{
return static_cast<VersionType>(m_key >> 32);
return static_cast<VersionType>(m_key >> INDEX_BIT_COUNT);
}
IndexType GetIndex() const
IndexType Index() const
{
constexpr std::uint64_t indexMask = 0x00000000FFFFFFFF;
return static_cast<IndexType>(m_key & indexMask);
return static_cast<IndexType>(m_key & INDEX_MASK);
}
SlotKey& operator=(const SlotKey& p_slotKey) = default;
SlotKey& operator=(SlotKey&& p_slotKey) = default;
[[nodiscard]]
bool operator==(const SlotKey& p_other) const = default;
private:
std::uint64_t m_key;
};
@@ -84,22 +100,22 @@ class SlotMap
{
ASSERT(UtilsAssertHandler, m_data.size() < SlotKey::MAX_INDEX, "Too many elements for SlotMap!");
const typename SlotKey::IndexType dataIndex = static_cast<typename SlotKey::IndexType>(m_data.size());
const typename SlotKey::IndexType dataIndex = static_cast<SlotKey::IndexType>(m_data.size());
m_data.emplace_back(std::forward<ARGS>(p_args)...);
if (HasFreeSlots())
{
const typename SlotKey::IndexType slotIndex = m_freeSlotHead;
m_freeSlotHead = m_slots[slotIndex].GetIndex();
m_freeSlotHead = m_slots[slotIndex].Index();
m_slots[slotIndex] = SlotKey {m_slots[slotIndex].GetVersion(), dataIndex};
m_slots[slotIndex] = SlotKey {m_slots[slotIndex].Version(), dataIndex};
m_dataToSlots.push_back(slotIndex);
return SlotKey {m_slots[slotIndex].GetVersion(), slotIndex};
return SlotKey {m_slots[slotIndex].Version(), slotIndex};
}
const typename SlotKey::IndexType slotIndex = static_cast<typename SlotKey::IndexType>(m_slots.size());
const typename SlotKey::IndexType slotIndex = static_cast<SlotKey::IndexType>(m_slots.size());
m_slots.emplace_back(1, dataIndex);
m_dataToSlots.push_back(slotIndex);
@@ -113,45 +129,35 @@ class SlotMap
return;
}
const typename SlotKey::IndexType dataIndex = m_slots[p_slotKey.GetIndex()].GetIndex();
const typename SlotKey::IndexType dataIndex = m_slots[p_slotKey.Index()].Index();
m_data.erase_unsorted(m_data.begin() + dataIndex);
m_dataToSlots.erase_unsorted(m_dataToSlots.begin() + dataIndex);
if (dataIndex < m_data.size())
{
m_slots[m_dataToSlots[dataIndex]] = SlotKey {m_slots[m_dataToSlots[dataIndex]].GetVersion(), dataIndex};
m_slots[m_dataToSlots[dataIndex]] = SlotKey {m_slots[m_dataToSlots[dataIndex]].Version(), dataIndex};
}
const typename SlotKey::VersionType nextVersion = p_slotKey.GetVersion() + 1;
if (nextVersion == SlotKey::INVALID_VERSION)
{
m_slots[p_slotKey.GetIndex()] = SlotKey {SlotKey::INVALID_VERSION, 0};
return;
}
m_slots[p_slotKey.GetIndex()] = SlotKey {nextVersion, m_freeSlotHead};
m_freeSlotHead = p_slotKey.GetIndex();
RecycleSlot(p_slotKey.Version() + 1, p_slotKey.Index());
}
[[nodiscard]]
bool Has(const SlotKey p_slotKey) const
{
return p_slotKey.GetIndex() < m_data.size() &&
p_slotKey.GetVersion() == m_slots[p_slotKey.GetIndex()].GetVersion();
return p_slotKey.Index() < m_slots.size() && p_slotKey.Version() == m_slots[p_slotKey.Index()].Version();
}
[[nodiscard]]
TYPE* Get(const SlotKey p_slotKey)
{
return Has(p_slotKey) ? &m_data[m_slots[p_slotKey.GetIndex()].GetIndex()] : nullptr;
return Has(p_slotKey) ? &m_data[m_slots[p_slotKey.Index()].Index()] : nullptr;
}
[[nodiscard]]
const TYPE* Get(const SlotKey p_slotKey) const
{
return Has(p_slotKey) ? &m_data[m_slots[p_slotKey.GetIndex()].GetIndex()] : nullptr;
return Has(p_slotKey) ? &m_data[m_slots[p_slotKey.Index()].Index()] : nullptr;
}
void Reset()
@@ -162,6 +168,114 @@ class SlotMap
m_freeSlotHead = std::numeric_limits<typename SlotKey::IndexType>::max();
}
void Clear()
{
for (const typename eastl::vector<TYPE>::size_type slotIndex: m_dataToSlots)
{
RecycleSlot(m_slots[slotIndex].Version() + 1, static_cast<SlotKey::IndexType>(slotIndex));
}
m_data.clear();
m_dataToSlots.clear();
}
[[nodiscard]]
eastl::vector<TYPE>::size_type Size() const
{
return m_data.size();
}
[[nodiscard]]
eastl::vector<TYPE>::size_type Capacity() const
{
return m_data.capacity();
}
[[nodiscard]]
eastl::vector<TYPE>::size_type Empty() const
{
return m_data.empty();
}
void Reserve(const eastl::vector<TYPE>::size_type p_size)
{
m_data.reserve(p_size);
m_dataToSlots.reserve(p_size);
m_slots.reserve(p_size);
}
[[nodiscard]]
eastl::vector<TYPE>::iterator begin()
{
return m_data.begin();
}
[[nodiscard]]
eastl::vector<TYPE>::iterator end()
{
return m_data.end();
}
[[nodiscard]]
eastl::vector<TYPE>::const_iterator begin() const
{
return m_data.begin();
}
[[nodiscard]]
eastl::vector<TYPE>::const_iterator end() const
{
return m_data.end();
}
[[nodiscard]]
eastl::vector<TYPE>::reverse_iterator rbegin()
{
return m_data.rbegin();
}
[[nodiscard]]
eastl::vector<TYPE>::reverse_iterator rend()
{
return m_data.rend();
}
[[nodiscard]]
eastl::vector<TYPE>::const_reverse_iterator rbegin() const
{
return m_data.rbegin();
}
[[nodiscard]]
eastl::vector<TYPE>::const_reverse_iterator rend() const
{
return m_data.rend();
}
[[nodiscard]]
eastl::vector<TYPE>::const_iterator cbegin() const
{
return m_data.cbegin();
}
[[nodiscard]]
eastl::vector<TYPE>::const_iterator cend() const
{
return m_data.cend();
}
[[nodiscard]]
eastl::vector<TYPE>::const_reverse_iterator crbegin() const
{
return m_data.crbegin();
}
[[nodiscard]]
eastl::vector<TYPE>::const_reverse_iterator crend() const
{
return m_data.crend();
}
SlotMap& operator=(const SlotMap& p_slotMap) = default;
SlotMap& operator=(SlotMap&& p_slotMap) = default;
@@ -172,12 +286,24 @@ class SlotMap
return m_freeSlotHead != std::numeric_limits<typename SlotKey::IndexType>::max();
}
void RecycleSlot(const SlotKey::VersionType p_version, const SlotKey::IndexType p_slotIndex)
{
if (p_version == SlotKey::INVALID_VERSION)
{
m_slots[p_slotIndex] = SlotKey {SlotKey::INVALID_VERSION, 0};
return;
}
m_slots[p_slotIndex] = SlotKey {p_version, m_freeSlotHead};
m_freeSlotHead = p_slotIndex;
}
eastl::vector<TYPE> m_data;
eastl::vector<SlotKey> m_slots;
eastl::vector<typename eastl::vector<TYPE>::size_type> m_dataToSlots;
typename SlotKey::IndexType m_freeSlotHead;
SlotKey::IndexType m_freeSlotHead;
};
} // namespace Bigfoot