diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index abc85beb2..62f8de6c9 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -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_value.cpp" "src/cubos/core/memory/any_vector.cpp" "src/cubos/core/reflection/type.cpp" diff --git a/core/include/cubos/core/memory/any_value.hpp b/core/include/cubos/core/memory/any_value.hpp new file mode 100644 index 000000000..561b068ac --- /dev/null +++ b/core/include/cubos/core/memory/any_value.hpp @@ -0,0 +1,67 @@ +/// @file +/// @brief Class @ref cubos::core::memory::AnyValue. +/// @ingroup core-memory + +#pragma once + +namespace cubos::core::reflection +{ + class Type; +} // namespace cubos::core::reflection + +namespace cubos::core::memory +{ + /// @brief Stores a blob of a given reflected type. + /// @ingroup core-memory + class AnyValue final + { + public: + ~AnyValue(); + + /// @brief Move constructs. + /// @param other Value. + AnyValue(AnyValue&& other) noexcept; + + /// @brief Default constructs a value with the given type. + /// @note @p type must have @ref reflection::ConstructibleTrait and be default + /// constructible. + /// @param type Value type. + /// @return Value. + static AnyValue defaultConstruct(const reflection::Type& type) noexcept; + + /// @brief Copy constructs a value with the given type. + /// @note @p type must have @ref reflection::ConstructibleTrait and be copy constructible. + /// @param type Value type. + /// @param value Value to copy. + /// @return Value. + static AnyValue copyConstruct(const reflection::Type& type, const void* value) noexcept; + + /// @brief Move constructs a value with the given type. + /// @note @p type must have @ref reflection::ConstructibleTrait and be move constructible. + /// @param type Value type. + /// @param value Value to move. + /// @return Value. + static AnyValue moveConstruct(const reflection::Type& type, void* value) noexcept; + + /// @brief Get the type of the elements stored in the vector. + /// @return Element type. + const reflection::Type& type() const; + + /// @brief Gets a pointer to the underlying value. + /// @return Value. + void* get(); + + /// @copydoc get() + const void* get() const; + + private: + /// @brief Constructs with allocated uninitialized memory. + /// @warning The constructor must be called immediately after this. Will cause UB if the + /// returned value is deconstructed before being initialized. + /// @param type Value type. + AnyValue(const reflection::Type& type) noexcept; + + const reflection::Type& mType; + void* mValue; + }; +} // namespace cubos::core::memory diff --git a/core/src/cubos/core/memory/any_value.cpp b/core/src/cubos/core/memory/any_value.cpp new file mode 100644 index 000000000..271caba47 --- /dev/null +++ b/core/src/cubos/core/memory/any_value.cpp @@ -0,0 +1,86 @@ +#include + +#include +#include +#include +#include +#include + +using cubos::core::memory::AnyValue; +using cubos::core::reflection::ConstructibleTrait; +using cubos::core::reflection::Type; + +AnyValue::~AnyValue() +{ + // Might have been moved, we must check it here. + if (mValue != nullptr) + { + const auto& trait = mType.get(); + trait.destruct(mValue); + operator delete(mValue, static_cast(trait.alignment())); + } +} + +AnyValue::AnyValue(AnyValue&& other) noexcept + : mType(other.mType) + , mValue(other.mValue) +{ + other.mValue = nullptr; +} + +AnyValue AnyValue::defaultConstruct(const Type& type) noexcept +{ + CUBOS_ASSERT(type.has(), "Type must be constructible"); + const auto& trait = type.get(); + CUBOS_ASSERT(trait.hasDefaultConstruct(), "Type must be default constructible"); + + AnyValue any{type}; + trait.defaultConstruct(any.get()); + return move(any); +} + +AnyValue AnyValue::copyConstruct(const Type& type, const void* value) noexcept +{ + CUBOS_ASSERT(type.has(), "Type must be constructible"); + const auto& trait = type.get(); + CUBOS_ASSERT(trait.hasCopyConstruct(), "Type must be copy constructible"); + + AnyValue any{type}; + trait.copyConstruct(any.get(), value); + return move(any); +} + +AnyValue AnyValue::moveConstruct(const Type& type, void* value) noexcept +{ + CUBOS_ASSERT(type.has(), "Type must be constructible"); + const auto& trait = type.get(); + CUBOS_ASSERT(trait.hasMoveConstruct(), "Type must be move constructible"); + + AnyValue any{type}; + trait.moveConstruct(any.get(), value); + return move(any); +} + +const Type& AnyValue::type() const +{ + return mType; +} + +void* AnyValue::get() +{ + return mValue; +} + +const void* AnyValue::get() const +{ + return mValue; +} + +AnyValue::AnyValue(const Type& type) noexcept + : mType(type) +{ + const auto& trait = type.get(); + mValue = operator new(trait.size(), static_cast(trait.alignment()), std::nothrow); + + CUBOS_ASSERT(mValue != nullptr, "Could not allocate memory for value of type '{}'", type.name()); +} diff --git a/core/tests/CMakeLists.txt b/core/tests/CMakeLists.txt index 1e1e6f772..044091765 100644 --- a/core/tests/CMakeLists.txt +++ b/core/tests/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable( data/fs/file_system.cpp data/context.cpp + memory/any_value.cpp memory/any_vector.cpp memory/type_map.cpp memory/unordered_bimap.cpp diff --git a/core/tests/memory/any_value.cpp b/core/tests/memory/any_value.cpp new file mode 100644 index 000000000..e307b5ea4 --- /dev/null +++ b/core/tests/memory/any_value.cpp @@ -0,0 +1,52 @@ +#include + +#include +#include +#include + +#include "../utils.hpp" + +TEST_CASE("memory::AnyValue") +{ + using cubos::core::memory::AnyValue; + using cubos::core::reflection::reflect; + + SUBCASE("with integers") + { + SUBCASE("defaultConstruct") + { + const auto any = AnyValue::defaultConstruct(reflect()); + CHECK(any.type().is()); + CHECK(*static_cast(any.get()) == 0); + } + + SUBCASE("copyConstruct") + { + int value = 1337; + auto any = AnyValue::copyConstruct(reflect(), &value); + CHECK(any.type().is()); + CHECK(*static_cast(any.get()) == value); + } + + SUBCASE("moveConstruct") + { + int value = 1337; + auto any = AnyValue::moveConstruct(reflect(), &value); + CHECK(any.type().is()); + CHECK(*static_cast(any.get()) == value); + } + } + + SUBCASE("detect destructor") + { + bool destructed = false; + DetectDestructor detector{&destructed}; + + { + auto any = AnyValue::moveConstruct(reflect(), &detector); + CHECK_FALSE(destructed); + } + + CHECK(destructed); + } +}