Skip to content

Commit

Permalink
feat(memory): add AnyVector
Browse files Browse the repository at this point in the history
  • Loading branch information
RiscadoA committed Oct 8, 2023
1 parent f3ec829 commit b8919e9
Show file tree
Hide file tree
Showing 6 changed files with 352 additions and 0 deletions.
1 change: 1 addition & 0 deletions core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ set(CUBOS_CORE_SOURCE
"src/cubos/core/memory/stream.cpp"
"src/cubos/core/memory/standard_stream.cpp"
"src/cubos/core/memory/buffer_stream.cpp"
"src/cubos/core/memory/any_vector.cpp"

"src/cubos/core/reflection/type.cpp"
"src/cubos/core/reflection/traits/constructible.cpp"
Expand Down
92 changes: 92 additions & 0 deletions core/include/cubos/core/memory/any_vector.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/// @file
/// @brief Class @ref cubos::core::memory::UnorderedBimap.
/// @ingroup core-memory

#pragma once

#include <cstddef>

namespace cubos::core::reflection
{
class Type;
class ConstructibleTrait;
} // namespace cubos::core::reflection

namespace cubos::core::memory
{
/// @brief Stores a dynamically sized array of blobs of a given reflected type.
/// @ingroup core-memory
class AnyVector final
{
public:
~AnyVector();

/// @brief Constructs with the given element type.
/// @note @p elementType must have @ref reflection::ConstructibleTrait.
/// @param elementType Element type.
AnyVector(const reflection::Type& elementType);

/// @brief Move constructs.
/// @param other Vector.
AnyVector(AnyVector&& other) noexcept;

/// @brief Get the type of the elements stored in the vector.
/// @return Element type.
const reflection::Type& elementType() const;

/// @brief Reserves space for at least @p capacity elements.
/// @note Aborts if @p capacity is less than @ref size().
/// @param capacity Minimum capacity.
void reserve(std::size_t capacity);

/// @brief Pushes a new default-constructed element to the back of the vector.
/// @note Aborts if @ref elementType() is not default-constructible.
void pushDefault();

/// @brief Pushes a new copy-constructed element to the back of the vector.
/// @note Aborts if @ref elementType() is not copy-constructible.
/// @param element Element to copy.
void pushCopy(const void* value);

/// @brief Pushes a new move-constructed element to the back of the vector.
/// @note Aborts if @ref elementType() is not move-constructible.
/// @param element Element to move.
void pushMove(void* value);

/// @brief Removes the last element from the vector.
/// @note Aborts if the vector is empty.
void pop();

/// @brief Removes all elements from the vector.
void clear();

/// @brief Get the element at the given index.
/// @note Aborts if @p index is out of bounds.
/// @param index Index of the element to get.
/// @return Pointer to the element.
void* at(std::size_t index);

/// @copydoc at(std::size_t)
const void* at(std::size_t index) const;

/// @brief Get the number of elements in the vector.
/// @return Element count.
std::size_t size() const;

/// @brief Get the number of elements the vector can hold without reallocating.
/// @return Element capacity.
std::size_t capacity() const;

/// @brief Checks if the vector is empty.
/// @return Whether the vector is empty.
bool empty() const;

private:
const reflection::Type& mElementType;
const reflection::ConstructibleTrait* mConstructibleTrait{nullptr};
void* mData{nullptr};
std::size_t mSize{0};
std::size_t mCapacity{0};
std::size_t mStride{0};
};
} // namespace cubos::core::memory
142 changes: 142 additions & 0 deletions core/src/cubos/core/memory/any_vector.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#include <cstdlib>

#include <cubos/core/log.hpp>
#include <cubos/core/memory/any_vector.hpp>
#include <cubos/core/reflection/traits/constructible.hpp>
#include <cubos/core/reflection/type.hpp>

using cubos::core::memory::AnyVector;
using cubos::core::reflection::ConstructibleTrait;
using cubos::core::reflection::Type;

AnyVector::~AnyVector()
{
std::free(mData);
}

AnyVector::AnyVector(const Type& elementType)
: mElementType(elementType)
{
CUBOS_ASSERT(mElementType.has<ConstructibleTrait>(), "Type must be constructible");
mConstructibleTrait = &mElementType.get<ConstructibleTrait>();

CUBOS_ASSERT(mConstructibleTrait->size() % mConstructibleTrait->alignment() == 0,
"Size must be a multiple of alignment");
mStride = mConstructibleTrait->size();
}

AnyVector::AnyVector(AnyVector&& other) noexcept
: mElementType(other.mElementType)
, mConstructibleTrait(other.mConstructibleTrait)
, mData(other.mData)
, mSize(other.mSize)
, mCapacity(other.mCapacity)
, mStride(other.mStride)
{
other.mSize = 0;
other.mCapacity = 0;
other.mData = nullptr;
}

const Type& AnyVector::elementType() const
{
return mElementType;
}

void AnyVector::reserve(std::size_t capacity)
{
CUBOS_ASSERT(capacity >= mSize, "Capacity must be greater than or equal to size");

if (mCapacity == capacity)
{
return;
}

mCapacity = capacity;
mData = std::realloc(mData, mStride * mCapacity);
CUBOS_ASSERT(mData != nullptr, "Vector memory reallocation failed");
}

void AnyVector::pushDefault()
{
CUBOS_ASSERT(mConstructibleTrait->hasDefaultConstruct(), "Type must be default-constructible");

if (mSize == mCapacity)
{
this->reserve(mCapacity == 0 ? 1 : mCapacity * 2);
}

mConstructibleTrait->defaultConstruct(static_cast<char*>(mData) + mStride * mSize);
++mSize;
}

void AnyVector::pushCopy(const void* value)
{
CUBOS_ASSERT(mConstructibleTrait->hasCopyConstruct(), "Type must be copy-constructible");

if (mSize == mCapacity)
{
this->reserve(mCapacity == 0 ? 1 : mCapacity * 2);
}

mConstructibleTrait->copyConstruct(static_cast<char*>(mData) + mStride * mSize, value);
++mSize;
}

void AnyVector::pushMove(void* value)
{
CUBOS_ASSERT(mConstructibleTrait->hasMoveConstruct(), "Type must be move-constructible");

if (mSize == mCapacity)
{
this->reserve(mCapacity == 0 ? 1 : mCapacity * 2);
}

mConstructibleTrait->moveConstruct(static_cast<char*>(mData) + mStride * mSize, value);
++mSize;
}

void AnyVector::pop()
{
CUBOS_ASSERT(mSize > 0, "Vector must not be empty");

--mSize;
mConstructibleTrait->destruct(static_cast<char*>(mData) + mStride * mSize);
}

void AnyVector::clear()
{
for (std::size_t i = 0; i < mSize; ++i)
{
mConstructibleTrait->destruct(static_cast<char*>(mData) + mStride * i);
}

mSize = 0;
}

void* AnyVector::at(std::size_t index)
{
CUBOS_ASSERT(index < mSize, "Index must be less than size");
return static_cast<char*>(mData) + mStride * index;
}

const void* AnyVector::at(std::size_t index) const
{
CUBOS_ASSERT(index < mSize, "Index must be less than size");
return static_cast<char*>(mData) + mStride * index;
}

std::size_t AnyVector::size() const
{
return mSize;
}

std::size_t AnyVector::capacity() const
{
return mCapacity;
}

bool AnyVector::empty() const
{
return mSize == 0;
}
1 change: 1 addition & 0 deletions core/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ add_executable(
data/fs/file_system.cpp
data/context.cpp

memory/any_vector.cpp
memory/unordered_bimap.cpp

ecs/utils.cpp
Expand Down
111 changes: 111 additions & 0 deletions core/tests/memory/any_vector.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#include <doctest/doctest.h>

#include <cubos/core/memory/any_vector.hpp>
#include <cubos/core/reflection/external/primitives.hpp>
#include <cubos/core/reflection/type.hpp>

#include "../utils.hpp"

TEST_CASE("memory::AnyVector")
{
using cubos::core::memory::AnyVector;
using cubos::core::reflection::reflect;

SUBCASE("with integers")
{
AnyVector vec{reflect<int>()};
CHECK(vec.elementType().is<int>());

SUBCASE("pushDefault")
{
vec.pushDefault();
CHECK(vec.size() == 1);
CHECK(vec.at(0) != nullptr);
CHECK(*static_cast<const int*>(vec.at(0)) == 0);
}

SUBCASE("pushCopy/pushMove")
{
int value = 42;

SUBCASE("pushCopy")
{
vec.pushCopy(&value);
}

SUBCASE("pushMove")
{
vec.pushMove(&value);
}

CHECK(vec.size() == 1);
CHECK(vec.at(0) != nullptr);
CHECK(*static_cast<const int*>(vec.at(0)) == 42);
}

SUBCASE("move constructor")
{
constexpr std::size_t Size = 100;

for (std::size_t i = 0; i < Size; ++i)
{
vec.pushDefault();
*static_cast<int*>(vec.at(i)) = static_cast<int>(i);
}

AnyVector vec2{std::move(vec)};
CHECK_FALSE(vec2.empty());
CHECK(vec2.size() == Size);

for (std::size_t i = 0; i < Size; ++i)
{
CHECK(*static_cast<const int*>(vec2.at(i)) == static_cast<int>(i));
}
}

CHECK(vec.capacity() >= vec.size());
}

SUBCASE("detect destructor")
{
AnyVector vec{reflect<DetectDestructor>()};
CHECK(vec.elementType().is<DetectDestructor>());

constexpr std::size_t Size = 100;
bool destroyed[Size];

for (std::size_t i = 0; i < Size; ++i)
{
CHECK(vec.size() == i);
vec.pushDefault();
CHECK(vec.size() == i + 1);
CHECK(vec.capacity() >= i + 1);

destroyed[i] = false;
static_cast<DetectDestructor*>(vec.at(i))->set(&destroyed[i]);
}

SUBCASE("pop until empty")
{
for (std::size_t i = 0; i < Size; ++i)
{
CHECK_FALSE(destroyed[Size - i - 1]);
vec.pop();
CHECK(destroyed[Size - i - 1]);
}
}

SUBCASE("clear")
{
vec.clear();
}

for (const auto& i : destroyed)
{
CHECK(i);
}

CHECK(vec.empty());
CHECK(vec.size() == 0);
}
}
5 changes: 5 additions & 0 deletions core/tests/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ class DetectDestructor
}
}

void set(bool* destructed)
{
mDestructed = destructed;
}

private:
bool* mDestructed;
};

0 comments on commit b8919e9

Please sign in to comment.