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
939 lines
23 KiB
C++
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
|