Files
OpenVulkano/openVulkanoCpp/Data/Containers/StableVector.hpp
2024-10-09 18:20:41 +03:00

591 lines
14 KiB
C++

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
#pragma once
#include "Base/Wrapper.hpp"
#include <initializer_list>
#include <iterator>
#include <stdexcept>
#include <list>
#include <vector>
#include <algorithm>
#include <iostream>
#pragma warning(push)
#pragma warning(disable : 4200)
#pragma warning(disable : 6011)
#pragma warning(disable : 6386)
namespace OpenVulkano
{
/**
* @class Stable Vector
* @brief Stable Vector is an alternative version for std::vector that provides chunk based memory allocation without re-aligning
*
* @throw Please know that this vector creates array gaps when you remove an element.
*/
template<typename T, size_t GROW_FACTOR = 2, size_t DEFAULT_CHUNK_SIZE = 32> class StableVector
{
struct VectorChunk
{
VectorChunk* m_next = nullptr; // Next chunk
VectorChunk* m_prev = nullptr; // Previous chunk
size_t m_size; // Size of the chunk
size_t m_capacity; // Capacity of the chunk
size_t m_nextIndex; // Next index to insert
size_t m_gapCount; // Count of emptied gaps in the chunk
bool* m_occupiedIndices; // filled gaps array
T m_data[0]; // data array
VectorChunk(size_t size) : m_size(0), m_capacity(size), m_nextIndex(0), m_gapCount(0)
{
m_occupiedIndices = reinterpret_cast<bool*>(m_data + size);
memset(m_occupiedIndices, 0, size * sizeof(bool));
}
~VectorChunk()
{
for (size_t i = 0; i < m_capacity; i++)
{
if (m_occupiedIndices[i])
{
m_data[i].~T();
}
}
m_prev = nullptr;
m_next = nullptr;
}
size_t GetRealIndex(size_t reqIndex)
{
if (m_gapCount == 0 || reqIndex == 0)
{
return reqIndex;
}
size_t gapCount = 0;
for (size_t i = 0; i <= reqIndex; i++)
{
if (!m_occupiedIndices[i])
{
gapCount++;
}
}
return reqIndex + gapCount;
}
T& GetAlignedData(size_t index)
{
size_t realIndex = GetRealIndex(index);
for (size_t i = realIndex; i < m_capacity; i++)
{
if (m_occupiedIndices[i])
{
return m_data[i];
}
}
throw std::out_of_range("Index out of range");
}
size_t GetLastOccupiedIndex()
{
for (size_t i = m_capacity - 1; i >= 0; i--)
{
if (m_occupiedIndices[i])
{
return i;
}
}
return 0;
}
};
public:
class Iterator
{
public:
Iterator(VectorChunk* chunk, size_t index) : m_chunk(chunk), m_index(index) {}
T& operator*()
{
auto realIndex = m_chunk->GetRealIndex(m_index);
return m_chunk->GetAlignedData(realIndex);
}
T* operator->() { return &operator*(); }
Iterator& operator++()
{
++m_index;
MoveToNextChunk();
return *this;
}
Iterator operator++(int)
{
Iterator temp = *this;
++temp;
return temp;
}
Iterator& operator--()
{
--m_index;
MoveToPrevChunk();
return *this;
}
Iterator operator--(int)
{
Iterator temp = *this;
--temp;
return temp;
}
Iterator operator+(size_t n)
{
Iterator temp = *this;
temp.m_index += n;
temp.MoveToNextChunk();
return temp;
}
Iterator operator-(size_t n)
{
Iterator temp = *this;
temp.m_index -= n;
temp.MoveToPrevChunk();
return temp;
}
bool operator==(const Iterator& other) const
{
return m_chunk == other.m_chunk && m_index == other.m_index;
}
bool operator!=(const Iterator& other) const
{
return m_chunk != other.m_chunk || m_index != other.m_index;
}
std::streamsize GetIndex() const { return m_chunk->GetRealIndex(m_index); }
VectorChunk* GetChunk() const { return m_chunk; }
protected:
void MoveToNextChunk()
{
while (m_chunk && m_index >= m_chunk->m_size)
{
m_index -= m_chunk->m_size;
m_chunk = m_chunk->m_next;
}
}
void MoveToPrevChunk()
{
while (m_chunk && m_index < 0)
{
m_index += m_chunk->m_size;
m_chunk = m_chunk->m_prev;
}
}
private:
VectorChunk* m_chunk;
std::streamsize m_index;
};
public:
StableVector() : m_firstChunk(nullptr), m_lastChunk(nullptr), m_size(0), m_capacity(0)
{
m_firstChunk = Grow(DEFAULT_CHUNK_SIZE);
m_lastChunk = m_firstChunk;
m_capacity = DEFAULT_CHUNK_SIZE;
}
StableVector(size_t size) : m_firstChunk(nullptr), m_lastChunk(nullptr), m_size(0), m_capacity(0)
{
m_firstChunk = Grow(size);
m_lastChunk = m_firstChunk;
m_capacity = size;
}
StableVector(size_t size, const T& value)
: m_firstChunk(nullptr), m_lastChunk(nullptr), m_size(0), m_capacity(0)
{
m_firstChunk = Grow(size);
m_lastChunk = m_firstChunk;
m_capacity = size;
for (size_t i = 0; i < size; i++)
{
PushBack(value);
}
}
StableVector(std::initializer_list<T> list)
: m_firstChunk(nullptr), m_lastChunk(nullptr), m_size(0), m_capacity(0)
{
m_firstChunk = Grow(list.size());
m_lastChunk = m_firstChunk;
m_capacity = list.size();
for (const T& value: list)
{
PushBack(value);
}
}
StableVector(const StableVector<T>& copy)
: m_firstChunk(nullptr), m_lastChunk(nullptr), m_size(0), m_capacity(0)
{
m_firstChunk = Grow(copy.m_capacity); // One big chunk to make Stable contiguous.
m_lastChunk = m_firstChunk;
m_capacity = copy.m_capacity;
for (size_t i = 0; i < copy.Size(); i++)
{
PushBack(copy.At(i));
}
}
StableVector(StableVector<T>&& move) noexcept
: m_firstChunk(nullptr), m_lastChunk(nullptr), m_size(0), m_capacity(0)
{
m_firstChunk = move.m_firstChunk;
m_lastChunk = move.m_lastChunk;
m_size = move.m_size;
m_capacity = move.m_capacity;
move.m_firstChunk = nullptr;
move.m_lastChunk = nullptr;
move.m_size = 0;
move.m_capacity = 0;
}
~StableVector()
{
VectorChunk* currentChunk = m_firstChunk;
while (currentChunk)
{
VectorChunk* temp = currentChunk;
currentChunk = currentChunk->m_next;
delete temp;
}
}
void PushBack(const T& value)
{
if (m_lastChunk->m_nextIndex == m_lastChunk->m_capacity)
{
VectorChunk* newChunk = Grow(m_lastChunk->m_capacity * GROW_FACTOR);
m_capacity += m_lastChunk->m_capacity * GROW_FACTOR;
m_lastChunk->m_next = newChunk;
newChunk->m_prev = m_lastChunk;
m_lastChunk = newChunk;
}
new (&m_lastChunk->m_data[m_lastChunk->m_nextIndex]) T(value);
m_lastChunk->m_occupiedIndices[m_lastChunk->m_nextIndex] = true;
m_lastChunk->m_nextIndex++;
m_lastChunk->m_size++;
m_size++;
}
void PushBack(T&& value) noexcept
{
if (m_lastChunk->m_nextIndex == m_lastChunk->m_capacity)
{
VectorChunk* newChunk = Grow(m_lastChunk->m_capacity * GROW_FACTOR);
m_capacity += m_lastChunk->m_capacity * GROW_FACTOR;
m_lastChunk->m_next = newChunk;
newChunk->m_prev = m_lastChunk;
m_lastChunk = newChunk;
}
new (&m_lastChunk->m_data[m_lastChunk->m_nextIndex]) T(std::move(value));
m_lastChunk->m_occupiedIndices[m_lastChunk->m_nextIndex] = true;
m_lastChunk->m_nextIndex++;
m_lastChunk->m_size++;
m_size++;
}
// Checks the first available gap and inserts. If no gap it works like push_back(const T& value)
void Push(const T& value)
{
VectorChunk* currentChunk = m_firstChunk;
while (currentChunk)
{
if (currentChunk->m_gapCount > 0) // If there is a gap check occupied indices to find the first gap
{
for (size_t i = 0; i < currentChunk->m_capacity; i++)
{
if (!currentChunk->m_occupiedIndices[i])
{
new (&currentChunk->m_data[i]) T(value);
currentChunk->m_occupiedIndices[i] = true;
currentChunk->m_size++;
currentChunk->m_gapCount--;
m_size++;
return;
}
}
}
currentChunk = currentChunk->m_next;
}
PushBack(value);
}
// Checks the first available gap and inserts. If no gap it works like push_back(T&& value)
void Push(T&& value) noexcept
{
VectorChunk* currentChunk = m_firstChunk;
while (currentChunk)
{
if (currentChunk->m_gapCount > 0) // If there is a gap check occupied indices to find the first gap
{
for (size_t i = 0; i < currentChunk->m_capacity; i++)
{
if (!currentChunk->m_occupiedIndices[i])
{
new (&currentChunk->m_data[i]) T(std::move(value));
currentChunk->m_occupiedIndices[i] = true;
currentChunk->m_size++;
currentChunk->m_gapCount--;
m_size++;
return;
}
}
}
currentChunk = currentChunk->m_next;
}
PushBack(std::move(value));
}
template<typename... Args> void EmplaceBack(Args&&... args)
{
if (m_lastChunk->m_nextIndex == m_lastChunk->m_capacity)
{
VectorChunk* newChunk = Grow(m_lastChunk->m_capacity * GROW_FACTOR);
m_capacity += m_lastChunk->m_capacity * GROW_FACTOR;
m_lastChunk->m_next = newChunk;
newChunk->m_prev = m_lastChunk;
m_lastChunk = newChunk;
}
new (&m_lastChunk->m_data[m_lastChunk->m_nextIndex]) T(std::forward<Args>(args)...);
m_lastChunk->m_occupiedIndices[m_lastChunk->m_nextIndex] = true;
m_lastChunk->m_nextIndex++;
m_lastChunk->m_size++;
m_size++;
}
// Checks the first available gap and inserts. If no gap it works like emplace_back(Args&&... args)
template<typename... Args> void Emplace(Args&&... args)
{
VectorChunk* currentChunk = m_firstChunk;
while (currentChunk)
{
if (currentChunk->m_gapCount > 0) // If there is a gap check occupied indices to find the first gap
{
for (size_t i = 0; i < currentChunk->m_capacity; i++)
{
if (!currentChunk->m_occupiedIndices[i])
{
new (&currentChunk->m_data[i]) T(std::forward<Args>(args)...);
currentChunk->m_occupiedIndices[i] = true;
currentChunk->m_size++;
currentChunk->m_gapCount--;
m_size++;
return;
}
}
}
currentChunk = currentChunk->m_next;
}
EmplaceBack(std::forward<Args>(args)...);
}
void PopBack()
{
if (m_size == 0)
{
return; // return? or make
}
if (m_lastChunk->m_nextIndex == -1)
{
VectorChunk* temp = m_lastChunk;
m_lastChunk = m_lastChunk->m_prev;
m_lastChunk->m_next = nullptr;
temp->~VectorChunk();
::operator delete(temp);
}
m_lastChunk->m_data[m_lastChunk->m_nextIndex - 1].~T();
m_lastChunk->m_occupiedIndices[m_lastChunk->m_nextIndex - 1] = false;
m_lastChunk->m_nextIndex--;
m_lastChunk->m_size--;
m_size--;
}
constexpr T& Back() const
{
if (m_size == 0)
{
throw std::out_of_range("Index out of range");
}
return m_lastChunk->m_data[m_lastChunk->GetLastOccupiedIndex()];
}
constexpr T& Front() const
{
if (m_size == 0)
{
throw std::out_of_range("Index out of range");
}
return m_firstChunk->m_data[0];
}
void Remove(size_t index)
{
auto handle = FindChunk(index);
if (!handle.first)
{
throw std::out_of_range("Index out of range");
}
size_t realIndex = handle.first->GetRealIndex(handle.second);
if (realIndex >= handle.first->m_size)
{
throw std::out_of_range("Index out of range");
}
handle.first->m_data[realIndex].~T();
handle.first->m_occupiedIndices[realIndex] = false;
handle.first->m_size--;
handle.first->m_gapCount++;
m_size--;
}
void Remove(const Iterator& it)
{
it.GetChunk()->m_data[it.GetIndex()].~T();
it.GetChunk()->m_occupiedIndices[it.GetIndex()] = false;
it.GetChunk()->m_size--;
it.GetChunk()->m_gapCount++;
m_size--;
}
T& operator[](size_t index) noexcept
{
auto handle = FindChunk(index);
return handle.first->GetAlignedData(handle.second);
}
T& At(size_t index)
{
if (index >= m_size) [[unlikely]]
{
throw std::out_of_range("Index out of range");
}
return operator[](index);
}
void Clear()
{
VectorChunk* currentChunk = m_firstChunk;
while (currentChunk)
{
VectorChunk* nextChunk = currentChunk->m_next;
delete currentChunk;
currentChunk = nextChunk;
}
m_firstChunk = Grow(DEFAULT_CHUNK_SIZE);
m_lastChunk = m_firstChunk;
m_capacity = DEFAULT_CHUNK_SIZE;
m_size = 0;
}
size_t Size() const noexcept { return m_size; }
size_t Capacity() const noexcept { return m_capacity; }
bool Empty() const noexcept { return m_size == 0; }
Iterator begin() { return Iterator(m_firstChunk, 0); }
Iterator end() { return Iterator(m_lastChunk, m_lastChunk->m_nextIndex - 1); }
const Iterator& cbegin() { return Iterator(m_firstChunk, 0); }
const Iterator& cend() { return Iterator(m_lastChunk, m_lastChunk->m_nextIndex - 1); }
//region std aliases
void push_back(const T& value) { PushBack(value); }
void push_back(T&& value) { PushBack(std::move(value)); }
template<typename... Args> void emplace_back(Args&&... args) { EmplaceBack(std::forward<Args>(args)...); }
void pop_back() { PopBack(); }
T& back() { return Back(); }
T& front() { return Front(); }
bool empty() const noexcept { return Empty(); }
void clear() { Clear(); }
void erase(const Iterator& it) { Remove(it); }
size_t size() const noexcept { return Size(); }
size_t capacity() const noexcept { return Capacity(); }
//endregion
protected:
VectorChunk* Grow(size_t requestSize)
{
VectorChunk* newChunk = static_cast<VectorChunk*>(
::operator new(sizeof(VectorChunk) + requestSize * sizeof(T) + requestSize * sizeof(bool)));
new (newChunk) VectorChunk(requestSize);
return newChunk;
}
std::pair<VectorChunk*, size_t> FindChunk(size_t index)
{
size_t leftIndex = index;
VectorChunk* currentChunk = m_firstChunk;
while (currentChunk)
{
if (leftIndex < currentChunk->m_size)
{
return { currentChunk, leftIndex };
}
leftIndex -= currentChunk->m_size;
currentChunk = currentChunk->m_next;
}
throw std::out_of_range("Index out of range");
}
private:
VectorChunk* m_firstChunk;
VectorChunk* m_lastChunk;
size_t m_size;
size_t m_capacity;
};
}
#pragma warning(pop)