Files
Bigfoot/Bigfoot/Sources/Utils/Include/Utils/Containers/SlotMap.hpp
T
rboullard aea18bb048
Bigfoot / Build & Test Debug with ./ConanProfiles/clang (Unity Build: OFF) (push) Successful in 5m44s
Bigfoot / Build & Test Debug with ./ConanProfiles/clang (Unity Build: ON) (push) Successful in 5m49s
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 6m8s
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 6m16s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang_asan (Unity Build: OFF) (push) Successful in 7m29s
Bigfoot / Build & Test RelWithDebInfo with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Successful in 7m26s
Bigfoot / Build & Test Release with ./ConanProfiles/clang (Unity Build: OFF) (push) Successful in 6m14s
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 7m7s
Bigfoot / Build & Test Release with ./ConanProfiles/clang_asan (Unity Build: ON) (push) Successful in 7m5s
Bigfoot / Clang Format Checks (push) Failing after 12s
Cache page
2026-08-09 16:25:45 +02:00

939 lines
23 KiB
C++

/*********************************************************************
* \file SlotMap.hpp
*
* \author Romain BOULLARD
* \date May 2026
*********************************************************************/
#ifndef BIGFOOT_UTILS_CONTAINERS_SLOTMAP_HPP
#define BIGFOOT_UTILS_CONTAINERS_SLOTMAP_HPP
#include <Utils/UtilsAssertHandler.hpp>
#include <EASTL/array.h>
#include <EASTL/bitset.h>
#include <EASTL/iterator.h>
#include <EASTL/unique_ptr.h>
#include <EASTL/vector.h>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <new>
namespace Bigfoot
{
class SlotMapKey
{
public:
using IndexType = std::uint32_t;
using VersionType = std::uint32_t;
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();
constexpr SlotMapKey(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))
{
}
constexpr SlotMapKey():
SlotMapKey(INVALID_VERSION, 0)
{
}
constexpr SlotMapKey(const SlotMapKey& p_slotKey) = default;
constexpr SlotMapKey(SlotMapKey&& p_slotKey) = default;
constexpr ~SlotMapKey() = default;
constexpr bool Valid() const
{
return GetVersion() != INVALID_VERSION;
}
constexpr VersionType GetVersion() const
{
return static_cast<VersionType>(m_key >> INDEX_BIT_COUNT);
}
constexpr IndexType GetIndex() const
{
return static_cast<IndexType>(m_key & INDEX_MASK);
}
constexpr SlotMapKey& operator=(const SlotMapKey& p_slotKey) = default;
constexpr SlotMapKey& operator=(SlotMapKey&& p_slotKey) = default;
[[nodiscard]]
constexpr bool operator==(const SlotMapKey& p_other) const = default;
private:
std::uint64_t m_key;
};
class SlotMapBase
{
public:
~SlotMapBase() = default;
[[nodiscard]]
bool Has(const SlotMapKey p_slotKey) const
{
return p_slotKey.Valid() && (p_slotKey.GetIndex() < m_slots.size() &&
p_slotKey.GetVersion() == m_slots[p_slotKey.GetIndex()].GetVersion());
}
[[nodiscard]]
std::size_t Size() const
{
return m_size;
}
[[nodiscard]]
bool Empty() const
{
return m_size == 0;
}
SlotMapBase& operator=(const SlotMapBase& p_base) = default;
SlotMapBase& operator=(SlotMapBase&& p_base) = default;
protected:
SlotMapBase() = default;
SlotMapBase(const SlotMapBase& p_base) = default;
SlotMapBase(SlotMapBase&& p_base) = default;
[[nodiscard]]
bool HasFreeSlots() const
{
return m_freeSlotHead != std::numeric_limits<SlotMapKey::IndexType>::max();
}
void RecycleSlot(const SlotMapKey::VersionType p_version, const SlotMapKey::IndexType p_slotIndex)
{
if (p_version == SlotMapKey::INVALID_VERSION)
{
m_slots[p_slotIndex] = SlotMapKey {SlotMapKey::INVALID_VERSION, 0};
return;
}
m_slots[p_slotIndex] = SlotMapKey {p_version, m_freeSlotHead};
m_freeSlotHead = p_slotIndex;
}
void ResetSlots()
{
m_size = 0;
m_slots.clear();
m_freeSlotHead = std::numeric_limits<SlotMapKey::IndexType>::max();
}
void ReserveSlots(const std::size_t p_size)
{
m_slots.reserve(p_size);
}
std::size_t m_size = 0;
eastl::vector<SlotMapKey> m_slots;
SlotMapKey::IndexType m_freeSlotHead = std::numeric_limits<SlotMapKey::IndexType>::max();
};
template<class TYPE>
class DenseSlotMap: public SlotMapBase
{
public:
using iterator = typename eastl::vector<TYPE>::iterator;
using const_iterator = typename eastl::vector<TYPE>::const_iterator;
using reverse_iterator = typename eastl::vector<TYPE>::reverse_iterator;
using const_reverse_iterator = typename eastl::vector<TYPE>::const_reverse_iterator;
DenseSlotMap() = default;
DenseSlotMap(const DenseSlotMap& p_map) = default;
DenseSlotMap(DenseSlotMap&& p_map) = default;
~DenseSlotMap() = default;
template<class... ARGS>
[[nodiscard]]
SlotMapKey Insert(ARGS&&... p_args)
{
const SlotMapKey::IndexType dataIndex = static_cast<SlotMapKey::IndexType>(m_size);
m_data.emplace_back(std::forward<ARGS>(p_args)...);
++m_size;
return AcquireSlot(dataIndex);
}
void Remove(const SlotMapKey p_slotKey)
{
if (!Has(p_slotKey))
{
return;
}
const SlotMapKey::IndexType dataIndex = GetDataIndex(p_slotKey);
if (dataIndex != m_size - 1)
{
m_data[dataIndex] = eastl::move(m_data.back());
}
m_data.pop_back();
--m_size;
ReleaseSlot(p_slotKey, dataIndex);
}
[[nodiscard]]
TYPE* Get(const SlotMapKey p_slotKey)
{
return Has(p_slotKey) ? &m_data[GetDataIndex(p_slotKey)] : nullptr;
}
[[nodiscard]]
const TYPE* Get(const SlotMapKey p_slotKey) const
{
return Has(p_slotKey) ? &m_data[GetDataIndex(p_slotKey)] : nullptr;
}
void Reset()
{
m_data.clear();
m_dataToSlots.clear();
ResetSlots();
}
void Clear()
{
for (const std::size_t slotIndex: m_dataToSlots)
{
RecycleSlot(m_slots[slotIndex].GetVersion() + 1, static_cast<SlotMapKey::IndexType>(slotIndex));
}
m_size = 0;
m_data.clear();
m_dataToSlots.clear();
}
[[nodiscard]]
std::size_t Capacity() const
{
return m_data.capacity();
}
void Reserve(const std::size_t p_size)
{
m_data.reserve(p_size);
m_dataToSlots.reserve(p_size);
ReserveSlots(p_size);
}
[[nodiscard]]
iterator begin()
{
return m_data.begin();
}
[[nodiscard]]
iterator end()
{
return m_data.end();
}
[[nodiscard]]
const_iterator begin() const
{
return m_data.begin();
}
[[nodiscard]]
const_iterator end() const
{
return m_data.end();
}
[[nodiscard]]
reverse_iterator rbegin()
{
return m_data.rbegin();
}
[[nodiscard]]
reverse_iterator rend()
{
return m_data.rend();
}
[[nodiscard]]
const_reverse_iterator rbegin() const
{
return m_data.rbegin();
}
[[nodiscard]]
const_reverse_iterator rend() const
{
return m_data.rend();
}
[[nodiscard]]
const_iterator cbegin() const
{
return m_data.cbegin();
}
[[nodiscard]]
const_iterator cend() const
{
return m_data.cend();
}
[[nodiscard]]
const_reverse_iterator crbegin() const
{
return m_data.crbegin();
}
[[nodiscard]]
const_reverse_iterator crend() const
{
return m_data.crend();
}
DenseSlotMap& operator=(const DenseSlotMap& p_map) = default;
DenseSlotMap& operator=(DenseSlotMap&& p_map) = default;
private:
[[nodiscard]]
SlotMapKey::IndexType GetDataIndex(const SlotMapKey p_slotKey) const
{
return m_slots[p_slotKey.GetIndex()].GetIndex();
}
[[nodiscard]]
SlotMapKey AcquireSlot(const SlotMapKey::IndexType p_dataIndex)
{
ASSERT(UtilsAssertHandler, m_slots.size() < SlotMapKey::MAX_INDEX, "All slots have been exhausted!");
if (HasFreeSlots())
{
const SlotMapKey::IndexType slotIndex = m_freeSlotHead;
m_freeSlotHead = m_slots[slotIndex].GetIndex();
m_slots[slotIndex] = SlotMapKey {m_slots[slotIndex].GetVersion(), p_dataIndex};
m_dataToSlots.push_back(slotIndex);
return SlotMapKey {m_slots[slotIndex].GetVersion(), slotIndex};
}
const SlotMapKey::IndexType slotIndex = static_cast<SlotMapKey::IndexType>(m_slots.size());
m_slots.emplace_back(1, p_dataIndex);
m_dataToSlots.push_back(slotIndex);
return SlotMapKey {1, slotIndex};
}
void ReleaseSlot(const SlotMapKey p_slotKey, const SlotMapKey::IndexType p_dataIndex)
{
m_dataToSlots.erase_unsorted(m_dataToSlots.begin() + p_dataIndex);
if (p_dataIndex < m_size)
{
m_slots[m_dataToSlots[p_dataIndex]] = SlotMapKey {m_slots[m_dataToSlots[p_dataIndex]].GetVersion(),
p_dataIndex};
}
RecycleSlot(p_slotKey.GetVersion() + 1, p_slotKey.GetIndex());
}
eastl::vector<TYPE> m_data;
eastl::vector<std::size_t> m_dataToSlots;
};
template<class TYPE>
class StableSlotMap: public SlotMapBase
{
private:
static constexpr std::size_t PAGE_SIZE = 64;
struct AlignedSlot
{
alignas(TYPE) std::byte m_bytes[sizeof(TYPE)];
};
struct Page
{
Page() = default;
Page(const Page& p_page) = delete;
Page(Page&& p_page) = delete;
~Page()
{
for (std::size_t offset = m_occupied.find_first(); offset != PAGE_SIZE; offset = m_occupied.find_next(offset))
{
std::destroy_at(SlotAt(offset));
}
}
Page& operator=(const Page& p_page) = delete;
Page& operator=(Page&& p_page) = delete;
[[nodiscard]]
TYPE* SlotAt(const std::size_t p_offset)
{
return std::launder(reinterpret_cast<TYPE*>(m_storage[p_offset].m_bytes));
}
[[nodiscard]]
const TYPE* SlotAt(const std::size_t p_offset) const
{
return std::launder(reinterpret_cast<const TYPE*>(m_storage[p_offset].m_bytes));
}
eastl::array<AlignedSlot, PAGE_SIZE> m_storage;
eastl::bitset<PAGE_SIZE> m_occupied;
};
public:
class iterator
{
public:
using iterator_category = eastl::bidirectional_iterator_tag;
using value_type = TYPE;
using difference_type = std::ptrdiff_t;
using pointer = TYPE*;
using reference = TYPE&;
iterator() = default;
[[nodiscard]]
reference operator*() const
{
return *m_current;
}
[[nodiscard]]
pointer operator->() const
{
return m_current;
}
iterator& operator++()
{
Advance();
return *this;
}
iterator operator++(int)
{
const iterator temp = *this;
++(*this);
return temp;
}
iterator& operator--()
{
Retreat();
return *this;
}
iterator operator--(int)
{
const iterator temp = *this;
--(*this);
return temp;
}
[[nodiscard]]
bool operator==(const iterator& p_other) const
{
return m_map == p_other.m_map && m_current == p_other.m_current;
}
private:
friend class StableSlotMap;
explicit iterator(StableSlotMap* p_map):
m_map(p_map)
{
}
void SeekFirstFrom(std::size_t p_pageIndex)
{
for (; p_pageIndex < m_map->m_pages.size(); ++p_pageIndex)
{
Page* const page = m_map->m_pages[p_pageIndex].get();
const std::size_t offset = page->m_occupied.find_first();
if (offset != PAGE_SIZE)
{
m_pageIndex = p_pageIndex;
m_page = page;
m_offset = offset;
m_current = page->SlotAt(offset);
return;
}
}
m_pageIndex = m_map->m_pages.size();
m_page = nullptr;
m_offset = 0;
m_current = nullptr;
}
void SeekLastFrom(std::size_t p_pageIndex)
{
for (;;)
{
Page* const page = m_map->m_pages[p_pageIndex].get();
const std::size_t offset = page->m_occupied.find_last();
if (offset != PAGE_SIZE)
{
m_pageIndex = p_pageIndex;
m_page = page;
m_offset = offset;
m_current = page->SlotAt(offset);
return;
}
if (p_pageIndex == 0)
{
return;
}
--p_pageIndex;
}
}
void Advance()
{
const std::size_t offset = m_page->m_occupied.find_next(m_offset);
if (offset != PAGE_SIZE)
{
m_offset = offset;
m_current = m_page->SlotAt(offset);
return;
}
SeekFirstFrom(m_pageIndex + 1);
}
void Retreat()
{
if (m_current == nullptr)
{
SeekLastFrom(m_map->m_pages.size() - 1);
return;
}
const std::size_t offset = m_page->m_occupied.find_prev(m_offset);
if (offset != PAGE_SIZE)
{
m_offset = offset;
m_current = m_page->SlotAt(offset);
return;
}
if (m_pageIndex == 0)
{
return;
}
SeekLastFrom(m_pageIndex - 1);
}
StableSlotMap* m_map = nullptr;
Page* m_page = nullptr;
std::size_t m_pageIndex = 0;
std::size_t m_offset = 0;
TYPE* m_current = nullptr;
};
class const_iterator
{
public:
using iterator_category = eastl::bidirectional_iterator_tag;
using value_type = TYPE;
using difference_type = std::ptrdiff_t;
using pointer = const TYPE*;
using reference = const TYPE&;
const_iterator() = default;
[[nodiscard]]
reference operator*() const
{
return *m_current;
}
[[nodiscard]]
pointer operator->() const
{
return m_current;
}
const_iterator& operator++()
{
Advance();
return *this;
}
const_iterator operator++(int)
{
const const_iterator temp = *this;
++(*this);
return temp;
}
const_iterator& operator--()
{
Retreat();
return *this;
}
const_iterator operator--(int)
{
const const_iterator temp = *this;
--(*this);
return temp;
}
[[nodiscard]]
bool operator==(const const_iterator& p_other) const
{
return m_map == p_other.m_map && m_current == p_other.m_current;
}
private:
friend class StableSlotMap;
explicit const_iterator(const StableSlotMap* p_map):
m_map(p_map)
{
}
void SeekFirstFrom(std::size_t p_pageIndex)
{
for (; p_pageIndex < m_map->m_pages.size(); ++p_pageIndex)
{
const Page* const page = m_map->m_pages[p_pageIndex].get();
const std::size_t offset = page->m_occupied.find_first();
if (offset != PAGE_SIZE)
{
m_pageIndex = p_pageIndex;
m_page = page;
m_offset = offset;
m_current = page->SlotAt(offset);
return;
}
}
m_pageIndex = m_map->m_pages.size();
m_page = nullptr;
m_offset = 0;
m_current = nullptr;
}
void SeekLastFrom(std::size_t p_pageIndex)
{
for (;;)
{
const Page* const page = m_map->m_pages[p_pageIndex].get();
const std::size_t offset = page->m_occupied.find_last();
if (offset != PAGE_SIZE)
{
m_pageIndex = p_pageIndex;
m_page = page;
m_offset = offset;
m_current = page->SlotAt(offset);
return;
}
if (p_pageIndex == 0)
{
return;
}
--p_pageIndex;
}
}
void Advance()
{
const std::size_t offset = m_page->m_occupied.find_next(m_offset);
if (offset != PAGE_SIZE)
{
m_offset = offset;
m_current = m_page->SlotAt(offset);
return;
}
SeekFirstFrom(m_pageIndex + 1);
}
void Retreat()
{
if (m_current == nullptr)
{
SeekLastFrom(m_map->m_pages.size() - 1);
return;
}
const std::size_t offset = m_page->m_occupied.find_prev(m_offset);
if (offset != PAGE_SIZE)
{
m_offset = offset;
m_current = m_page->SlotAt(offset);
return;
}
if (m_pageIndex == 0)
{
return;
}
SeekLastFrom(m_pageIndex - 1);
}
const StableSlotMap* m_map = nullptr;
const Page* m_page = nullptr;
std::size_t m_pageIndex = 0;
std::size_t m_offset = 0;
const TYPE* m_current = nullptr;
};
using reverse_iterator = eastl::reverse_iterator<iterator>;
using const_reverse_iterator = eastl::reverse_iterator<const_iterator>;
StableSlotMap() = default;
StableSlotMap(const StableSlotMap& p_map) = delete;
StableSlotMap(StableSlotMap&& p_map) = default;
~StableSlotMap() = default;
template<class... ARGS>
[[nodiscard]]
SlotMapKey Insert(ARGS&&... p_args)
{
const SlotMapKey key = AcquireFreeSlot();
const SlotMapKey::IndexType index = key.GetIndex();
const std::size_t pageIndex = index / PAGE_SIZE;
if (pageIndex >= m_pages.size())
{
m_pages.push_back(eastl::make_unique<Page>());
}
Page& page = *m_pages[pageIndex];
const std::size_t offset = index % PAGE_SIZE;
::new (static_cast<void*>(page.m_storage[offset].m_bytes)) TYPE(std::forward<ARGS>(p_args)...);
page.m_occupied.set(offset);
++m_size;
return key;
}
void Remove(const SlotMapKey p_slotKey)
{
if (!Has(p_slotKey))
{
return;
}
const SlotMapKey::IndexType index = p_slotKey.GetIndex();
Page& page = *m_pages[index / PAGE_SIZE];
const std::size_t offset = index % PAGE_SIZE;
std::destroy_at(page.SlotAt(offset));
page.m_occupied.reset(offset);
--m_size;
ReleaseSlotVersion(index);
}
[[nodiscard]]
TYPE* Get(const SlotMapKey p_slotKey)
{
return Has(p_slotKey) ? &DataAt(p_slotKey.GetIndex()) : nullptr;
}
[[nodiscard]]
const TYPE* Get(const SlotMapKey p_slotKey) const
{
return Has(p_slotKey) ? &DataAt(p_slotKey.GetIndex()) : nullptr;
}
void Reset()
{
for (eastl::unique_ptr<Page>& page: m_pages)
{
page = eastl::make_unique<Page>();
}
ResetSlots();
}
void Clear()
{
for (std::size_t pageIndex = 0; pageIndex < m_pages.size(); ++pageIndex)
{
Page& page = *m_pages[pageIndex];
for (std::size_t offset = page.m_occupied.find_first(); offset != PAGE_SIZE;
offset = page.m_occupied.find_next(offset))
{
std::destroy_at(page.SlotAt(offset));
ReleaseSlotVersion(static_cast<SlotMapKey::IndexType>(pageIndex * PAGE_SIZE + offset));
}
page.m_occupied.reset();
}
m_size = 0;
}
[[nodiscard]]
std::size_t Capacity() const
{
return m_pages.size() * PAGE_SIZE;
}
void Reserve(const std::size_t p_size)
{
const std::size_t pagesNeeded = (p_size + PAGE_SIZE - 1) / PAGE_SIZE;
m_pages.reserve(pagesNeeded);
for (std::size_t pageIndex = m_pages.size(); pageIndex < pagesNeeded; ++pageIndex)
{
m_pages.push_back(eastl::make_unique<Page>());
}
ReserveSlots(p_size);
}
[[nodiscard]]
iterator begin()
{
iterator it {this};
it.SeekFirstFrom(0);
return it;
}
[[nodiscard]]
iterator end()
{
return iterator {this};
}
[[nodiscard]]
const_iterator begin() const
{
const_iterator it {this};
it.SeekFirstFrom(0);
return it;
}
[[nodiscard]]
const_iterator end() const
{
return const_iterator {this};
}
[[nodiscard]]
reverse_iterator rbegin()
{
return reverse_iterator {end()};
}
[[nodiscard]]
reverse_iterator rend()
{
return reverse_iterator {begin()};
}
[[nodiscard]]
const_reverse_iterator rbegin() const
{
return const_reverse_iterator {end()};
}
[[nodiscard]]
const_reverse_iterator rend() const
{
return const_reverse_iterator {begin()};
}
[[nodiscard]]
const_iterator cbegin() const
{
return begin();
}
[[nodiscard]]
const_iterator cend() const
{
return end();
}
[[nodiscard]]
const_reverse_iterator crbegin() const
{
return rbegin();
}
[[nodiscard]]
const_reverse_iterator crend() const
{
return rend();
}
StableSlotMap& operator=(const StableSlotMap& p_map) = delete;
StableSlotMap& operator=(StableSlotMap&& p_map) = default;
private:
[[nodiscard]]
TYPE& DataAt(const std::size_t p_index)
{
return *m_pages[p_index / PAGE_SIZE]->SlotAt(p_index % PAGE_SIZE);
}
[[nodiscard]]
const TYPE& DataAt(const std::size_t p_index) const
{
return *m_pages[p_index / PAGE_SIZE]->SlotAt(p_index % PAGE_SIZE);
}
[[nodiscard]]
SlotMapKey AcquireFreeSlot()
{
ASSERT(UtilsAssertHandler, m_slots.size() < SlotMapKey::MAX_INDEX, "All slots have been exhausted!");
if (HasFreeSlots())
{
const SlotMapKey::IndexType slotIndex = m_freeSlotHead;
m_freeSlotHead = m_slots[slotIndex].GetIndex();
m_slots[slotIndex] = SlotMapKey {m_slots[slotIndex].GetVersion(), slotIndex};
return m_slots[slotIndex];
}
const SlotMapKey::IndexType slotIndex = static_cast<SlotMapKey::IndexType>(m_slots.size());
m_slots.emplace_back(1, slotIndex);
return m_slots[slotIndex];
}
void ReleaseSlotVersion(const SlotMapKey::IndexType p_index)
{
RecycleSlot(m_slots[p_index].GetVersion() + 1, p_index);
}
eastl::vector<eastl::unique_ptr<Page>> m_pages;
};
} // namespace Bigfoot
#endif