stable-vector (#72)

Co-authored-by: Metehan Tuncbilek <mtuncbilek95@gmail.com>
Reviewed-by: Georg Hagen <georg.hagen@madvoxel.com>
Co-authored-by: metehan.tuncbilek <metehan.tuncbilek@madvoxel.com>
Co-committed-by: metehan.tuncbilek <metehan.tuncbilek@madvoxel.com>
This commit is contained in:
metehan.tuncbilek
2024-09-21 14:51:46 +02:00
committed by Georg Hagen
parent 653be0a403
commit 35b2f49c2e
2 changed files with 654 additions and 0 deletions

View File

@@ -0,0 +1,464 @@
#pragma once
#include "Base/Wrapper.hpp"
#include <initializer_list>
#include <iterator>
#include <stdexcept>
#include <list>
#include <vector>
#include <algorithm>
#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 DEFAULT_CHUNK_SIZE = 32, int GROWTH_FACTOR = 2> class StableVector
{
struct VectorChunk
{
VectorChunk* m_prev = nullptr;
VectorChunk* m_next = nullptr;
size_t m_chunkSize;
int64_t m_lastUsedIndex;
bool* m_fill;
T m_data[0];
VectorChunk(size_t size) : m_chunkSize(size), m_lastUsedIndex(-1)
{
m_fill = reinterpret_cast<bool*>(m_data + size);
memset(m_fill, 0, size * sizeof(bool));
}
~VectorChunk()
{
for (size_t i = 0; i < m_chunkSize; i++)
{
if (m_fill[i])
{
m_data[i].~T();
}
}
m_prev = nullptr;
m_next = nullptr;
}
};
public:
class Iterator
{
public:
Iterator(VectorChunk* ptr, size_t index = 0) : m_ptr(ptr), m_index(index) {}
T& operator*() { return m_ptr->m_data[m_index]; }
T* operator->() { return &m_ptr->m_data[m_index]; }
Iterator operator++()
{
++m_index;
MovetoNextValidChunk();
return *this;
}
Iterator operator++(int)
{
Iterator temp = *this;
++(*this);
return temp;
}
bool operator==(const Iterator& other) const { return m_ptr == other.m_ptr && m_index == other.m_index; }
bool operator!=(const Iterator& other) const { return !(*this == other); }
private:
void MovetoNextValidChunk()
{
while (m_ptr && (m_index > m_ptr->m_chunkSize || !m_ptr->m_fill[m_index]))
{
if (m_index >= m_ptr->m_chunkSize)
{
m_ptr = m_ptr->m_next;
m_index = 0;
}
else ++m_index;
}
if (m_ptr && m_index >= m_ptr->m_chunkSize)
{
m_ptr = m_ptr->m_next;
m_index = 0;
MovetoNextValidChunk();
}
}
private:
VectorChunk* m_ptr;
size_t m_index;
};
public:
StableVector() : m_firstChunk(nullptr), m_lastChunk(nullptr)
{
VectorChunk* chunk = SpawnChunk(DEFAULT_CHUNK_SIZE);
m_firstChunk = chunk;
m_lastChunk = chunk;
m_currentSize = 0;
m_totalCap = DEFAULT_CHUNK_SIZE;
}
StableVector(const StableVector<T>& copy)
{
m_firstChunk = nullptr;
m_lastChunk = nullptr;
m_currentSize = 0;
m_totalCap = 0;
VectorChunk* currentChunk = copy.m_firstChunk;
while (currentChunk)
{
for (size_t i = 0; i < currentChunk->m_chunkSize; i++)
{
if (currentChunk->m_fill[i]) { PushBack(currentChunk->m_data[i]); }
}
currentChunk = currentChunk->m_next;
}
}
StableVector(StableVector<T>&& move) noexcept
{
m_firstChunk = move.m_firstChunk;
m_lastChunk = move.m_lastChunk;
m_currentSize = move.m_currentSize;
m_totalCap = move.m_totalCap;
move.m_firstChunk = nullptr;
move.m_lastChunk = nullptr;
move.m_currentSize = 0;
move.m_totalCap = 0;
}
~StableVector()
{
VectorChunk* currentChunk = m_firstChunk;
while (currentChunk)
{
VectorChunk* temp = currentChunk;
currentChunk = currentChunk->m_next;
temp->~VectorChunk();
::operator delete(temp);
}
}
/**
* Adds the value to the first empty slot in the StableVector
*
* @param value - The value to be added
*/
void Add(const T& value)
{
VectorChunk* currentChunk = m_firstChunk;
while (currentChunk)
{
for (size_t i = 0; i < currentChunk->m_chunkSize; i++)
{
if (!currentChunk->m_fill[i])
{
currentChunk->m_data[i] = value;
currentChunk->m_fill[i] = true;
m_currentSize++;
if (i > currentChunk->m_lastUsedIndex) currentChunk->m_lastUsedIndex = i;
return;
}
}
currentChunk = currentChunk->m_next;
}
VectorChunk* chunk = SpawnChunk(size_t(m_lastChunk->m_chunkSize * GROWTH_FACTOR));
new (&m_lastChunk->m_data[++m_lastChunk->m_lastUsedIndex]) T(value);
m_lastChunk->m_fill[m_lastChunk->m_lastUsedIndex] = true;
m_currentSize++;
}
void PushBack(const T& value)
{
if (m_lastChunk->m_lastUsedIndex + 1 == m_lastChunk->m_chunkSize)
{
VectorChunk* chunk = SpawnChunk(size_t(m_lastChunk->m_chunkSize * GROWTH_FACTOR));
}
new (&m_lastChunk->m_data[++m_lastChunk->m_lastUsedIndex]) T(value);
m_lastChunk->m_fill[m_lastChunk->m_lastUsedIndex] = true;
m_currentSize++;
}
void PushBack(T&& value) noexcept
{
if (m_lastChunk->m_lastUsedIndex + 1 == m_lastChunk->m_chunkSize)
{
VectorChunk* chunk = SpawnChunk(size_t(m_lastChunk->m_chunkSize * GROWTH_FACTOR));
}
new (&m_lastChunk->m_data[++m_lastChunk->m_lastUsedIndex]) T(std::move(value));
m_lastChunk->m_fill[m_lastChunk->m_lastUsedIndex] = true;
m_currentSize++;
}
template<typename... Args> void Emplace(Args&&... args)
{
VectorChunk* currentChunk = m_firstChunk;
while (currentChunk)
{
for (size_t i = 0; i < currentChunk->m_chunkSize; i++)
{
if (!currentChunk->m_fill[i])
{
currentChunk->m_data[i] = T(std::forward<Args>(args)...);
currentChunk->m_fill[i] = true;
m_currentSize++;
if (i > currentChunk->m_lastUsedIndex) currentChunk->m_lastUsedIndex = i;
return;
}
}
currentChunk = currentChunk->m_next;
}
VectorChunk* chunk = SpawnChunk(size_t(m_lastChunk->m_chunkSize * GROWTH_FACTOR));
new (&m_lastChunk->m_data[++m_lastChunk->m_lastUsedIndex]) T(std::forward<Args>(args)...);
m_lastChunk->m_fill[m_lastChunk->m_lastUsedIndex] = true;
m_currentSize++;
}
template<typename... Args> void EmplaceBack(Args&&... args)
{
if (m_lastChunk->m_lastUsedIndex + 1 == m_lastChunk->m_chunkSize)
VectorChunk* chunk = SpawnChunk(size_t(m_lastChunk->m_chunkSize * GROWTH_FACTOR));
new (&m_lastChunk->m_data[++m_lastChunk->m_lastUsedIndex]) T(std::forward<Args>(args)...);
m_lastChunk->m_fill[m_lastChunk->m_lastUsedIndex] = true;
m_currentSize++;
}
/**
* Pops the last element of the StableVector
*
* @throw Please know that this pop function also reduces the chunk's lastUsedIndex
*/
void PopBack()
{
if (m_currentSize == 0) return;
if (m_lastChunk->m_lastUsedIndex == -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_lastUsedIndex].~T();
m_lastChunk->m_fill[m_lastChunk->m_lastUsedIndex] = false;
m_lastChunk->m_lastUsedIndex--;
m_currentSize--;
}
void Remove(size_t index)
{
size_t localIndex = index;
VectorChunk* chunk = GetChunk(localIndex);
if (chunk)
{
chunk->m_data[localIndex].~T();
chunk->m_fill[localIndex] = false;
m_currentSize--;
}
else throw std::out_of_range("Index out of range!");
}
void Remove(const T& value)
{
VectorChunk* currentChunk = m_firstChunk;
while (currentChunk)
{
for (size_t i = 0; i < currentChunk->m_chunkSize; i++)
{
if (currentChunk->m_fill[i] && currentChunk->m_data[i] == value)
{
currentChunk->m_data[i].~T();
currentChunk->m_fill[i] = false;
m_currentSize--;
return;
}
}
currentChunk = currentChunk->m_next;
}
}
std::vector<T> ToVector() const
{
std::vector<T> vec;
VectorChunk* currentChunk = m_firstChunk;
while (currentChunk)
{
for (size_t i = 0; i < currentChunk->m_chunkSize; i++)
{
if (currentChunk->m_fill[i])
{
vec.push_back(currentChunk->m_data[i]);
}
}
currentChunk = currentChunk->m_next;
}
return vec;
}
T& At(size_t index) const
{
if (index >= Size()) [[unlikely]]
throw std::out_of_range("Index out of range!");
return (*this)[index];
}
T& operator[](size_t index) const
{
VectorChunk* chunk = m_firstChunk;
size_t localIndex = index;
while (chunk)
{
if (localIndex > chunk->m_chunkSize - 1)
{
localIndex -= (chunk->m_chunkSize);
chunk = chunk->m_next;
}
else break;
}
return chunk->m_data[localIndex];
}
size_t Size() const { return m_currentSize; }
size_t Capacity() const { return m_totalCap; }
void Clear()
{
VectorChunk* currentChunk = m_firstChunk;
while (currentChunk)
{
VectorChunk* temp = currentChunk;
currentChunk = currentChunk->m_next;
delete temp;
}
m_firstChunk = nullptr;
m_lastChunk = nullptr;
m_currentSize = 0;
m_totalCap = DEFAULT_CHUNK_SIZE;
m_firstChunk = SpawnChunk(DEFAULT_CHUNK_SIZE);
m_lastChunk = m_firstChunk;
}
StableVector<T>& operator=(const StableVector<T>& copy)
{
if (this == &copy) return *this;
Clear();
m_firstChunk = nullptr;
m_lastChunk = nullptr;
m_currentSize = 0;
m_totalCap = 0;
VectorChunk* currentChunk = copy.m_firstChunk;
for (auto it = copy.begin(); it != copy.end(); ++it) PushBack(*it);
}
StableVector<T>& operator=(StableVector<T>&& move) noexcept
{
if (this == &move) return *this;
Clear();
m_firstChunk = move.m_firstChunk;
m_lastChunk = move.m_lastChunk;
m_currentSize = move.m_currentSize;
m_totalCap = move.m_totalCap;
move.m_firstChunk = nullptr;
move.m_lastChunk = nullptr;
move.m_currentSize = 0;
move.m_totalCap = 0;
return *this;
}
Iterator begin() { return Iterator(m_firstChunk, 0); }
Iterator end() { return Iterator(m_lastChunk, m_lastChunk->m_lastUsedIndex + 1); }
const Iterator& cbegin() const { return Iterator(m_firstChunk, 0); }
const Iterator& cend() const { return Iterator(m_lastChunk, m_lastChunk->m_lastUsedIndex + 1); }
private:
VectorChunk* SpawnChunk(size_t requestedSize)
{
VectorChunk* chunk = static_cast<VectorChunk*>(
::operator new(sizeof(VectorChunk) + requestedSize * sizeof(T) + requestedSize * sizeof(bool)));
new (chunk) VectorChunk(requestedSize);
if (m_lastChunk)
{
chunk->m_prev = m_lastChunk;
m_lastChunk->m_next = chunk;
m_lastChunk = chunk;
m_totalCap += m_lastChunk->m_chunkSize;
}
return chunk;
}
VectorChunk* GetChunk(size_t& localIndex)
{
VectorChunk* chunk = m_firstChunk;
while (chunk)
{
if (localIndex > chunk->m_chunkSize - 1)
{
localIndex -= (chunk->m_chunkSize);
chunk = chunk->m_next;
}
else break;
}
return chunk;
}
private:
VectorChunk* m_firstChunk;
VectorChunk* m_lastChunk;
size_t m_currentSize;
size_t m_totalCap;
};
}
#pragma warning(pop)

190
tests/StableVectorTest.cpp Normal file
View File

@@ -0,0 +1,190 @@
#include <catch2/catch_all.hpp>
#include "Data/Containers/StableVector.hpp"
using namespace OpenVulkano;
struct Test
{
uint32_t mValue;
Test(uint32_t value) : mValue(value) {}
Test() : mValue(0) {}
};
static int incrementCounter = 0;
static int decrementCounter = 0;
struct TestCount
{
TestCount() : m_value("") { ++incrementCounter; }
TestCount(const std::string& val) : m_value(val) { ++incrementCounter; }
TestCount(TestCount&& other) noexcept : m_value(std::move(other.m_value)) { ++incrementCounter; }
TestCount(const TestCount& other) : m_value(other.m_value) { ++incrementCounter; }
TestCount& operator=(const TestCount& other)
{
m_value = other.m_value;
return *this;
}
TestCount& operator=(TestCount&& other) noexcept
{
m_value = std::move(other.m_value);
return *this;
}
~TestCount() { ++decrementCounter; }
std::string m_value;
};
TEST_CASE("ChunkVector")
{
SECTION("PushBack")
{
StableVector<uint32_t> vec;
REQUIRE(vec.Size() == 0);
REQUIRE(vec.Capacity() == 32);
for (uint32_t i = 0; i < 100; ++i)
{
vec.PushBack(i);
}
REQUIRE(vec.Size() == 100);
REQUIRE(vec.Capacity() == 224); // 32 + 64 + 128 = 3 | previous chunk size multiplied by 2
}
SECTION("EmplaceBack")
{
StableVector<Test> vec;
REQUIRE(vec.Size() == 0);
REQUIRE(vec.Capacity() == 32);
for (uint32_t i = 0; i < 100; ++i)
{
vec.EmplaceBack(i);
}
REQUIRE(vec.Size() == 100);
for (uint32_t i = 0; i < 100; ++i)
{
REQUIRE(vec[i].mValue == i);
}
}
SECTION("PopBack")
{
StableVector<uint32_t> vec;
REQUIRE(vec.Size() == 0);
REQUIRE(vec.Capacity() == 32);
for (uint32_t i = 0; i < 100; ++i)
{
vec.PushBack(i);
}
REQUIRE(vec.Size() == 100);
uint64_t tempVal = vec.Capacity();
for (uint32_t i = 0; i < 50; ++i)
{
vec.PopBack();
}
REQUIRE(vec.Size() == 50);
REQUIRE(vec.Capacity() == tempVal);
}
SECTION("Clear")
{
StableVector<uint32_t> vec;
REQUIRE(vec.Size() == 0);
REQUIRE(vec.Capacity() == 32);
for (uint32_t i = 0; i < 100; ++i)
{
vec.PushBack(i);
}
REQUIRE(vec.Size() == 100);
vec.Clear();
REQUIRE(vec.Size() == 0);
REQUIRE(vec.Capacity() == 32);
}
SECTION("Add/Fill")
{
StableVector<std::string> vec;
REQUIRE(vec.Size() == 0);
REQUIRE(vec.Capacity() == 32);
for (uint32_t i = 0; i < 100; ++i)
{
vec.PushBack("a");
}
REQUIRE(vec.Size() == 100);
vec.Remove(56);
vec.Add("z");
REQUIRE(vec.Size() == 100);
REQUIRE(vec[56] == "z");
}
SECTION("Correct Initialization")
{
StableVector<TestCount> vec;
REQUIRE(incrementCounter == 0);
REQUIRE(decrementCounter == 0);
vec.EmplaceBack("a");
REQUIRE(incrementCounter == 1);
REQUIRE(decrementCounter == 0);
vec.PushBack(TestCount("b"));
REQUIRE(incrementCounter == 3);
REQUIRE(decrementCounter == 1);
TestCount testClass = TestCount("c");
vec.PushBack(std::move(testClass));
REQUIRE(incrementCounter == 5);
REQUIRE(decrementCounter == 1);
vec.Clear();
REQUIRE(incrementCounter == 5);
REQUIRE(decrementCounter == 4);
vec.PushBack(TestCount("d"));
REQUIRE(incrementCounter == 7);
REQUIRE(decrementCounter == 5);
TestCount testClass2 = TestCount("e");
vec.PushBack(testClass2);
REQUIRE(incrementCounter == 9);
REQUIRE(decrementCounter == 5);
}
SECTION("Out of scope check")
{
REQUIRE(incrementCounter == 9);
REQUIRE(decrementCounter == 9);
}
}