diff --git a/LibConf b/LibConf index f7f60e6..340ad98 100644 --- a/LibConf +++ b/LibConf @@ -52,6 +52,7 @@ src_files = Split(""" source/pipeline.cpp source/render_pass.cpp source/semaphore.cpp + source/serialization.cpp source/shader_module.cpp source/swapchain.cpp source/texture.cpp diff --git a/include/iwa/serialization.hpp b/include/iwa/serialization.hpp new file mode 100644 index 0000000..21f7668 --- /dev/null +++ b/include/iwa/serialization.hpp @@ -0,0 +1,105 @@ + +#pragma once + +#if !defined(IWA_SERIALIZATION_HPP_INCLUDED) +#define IWA_SERIALIZATION_HPP_INCLUDED 1 + +#include +#include +#include "iwa/type_meta.hpp" + +namespace iwa +{ +enum class SerializationFormat +{ + YAML +}; + +struct SerializeOptions +{ + mijin::Stream* stream; + SerializationFormat format = SerializationFormat::YAML; +}; + +struct DeserializeOptions +{ + mijin::Stream* stream; + SerializationFormat format = SerializationFormat::YAML; +}; + +void structToYAML(const void* data, const Struct& type, YAML::Emitter& emitter); +void structFromYAML(void* data, const Struct& type, const YAML::Node& node); + +template +void valueToYAML(const T& value, YAML::Emitter& emitter) +{ + if constexpr (std::is_fundamental_v>) + { + emitter << value; + } + else if constexpr (BuildStruct::value) + { + structToYAML(&value, reflectStruct(), emitter); + } +} + +template +void valueFromYAML(T& value, const YAML::Node& node) +{ + if constexpr (std::is_fundamental_v>) + { + value = node.as(); + } + else if constexpr (BuildStruct::value) + { + structFromYAML(&value, reflectStruct(), node); + } +} + +template +void serializeYAML(const T& value, const SerializeOptions& options) +{ + YAML::Emitter emitter; + valueToYAML(value, emitter); + if (mijin::StreamError error = options.stream->writeText(emitter.c_str()); error != mijin::StreamError::SUCCESS) + { + throw std::runtime_error("Error writing YAML."); + } +} + +template +void deserializeYAML(T& value, const DeserializeOptions& options) +{ + std::string text; + if (mijin::StreamError error = options.stream->readAsString(text); error != mijin::StreamError::SUCCESS) + { + throw std::runtime_error("Error reading text."); + } + const YAML::Node node = YAML::Load(text); + valueFromYAML(value, node); +} + +template +void serialize(const T& value, const SerializeOptions& options) +{ + switch (options.format) + { + case SerializationFormat::YAML: + serializeYAML(value, options); + break; + } +} + +template +void deserialize(T& value, const DeserializeOptions& options) +{ + switch (options.format) + { + case SerializationFormat::YAML: + deserializeYAML(value, options); + break; + } +} +} + +#endif // !defined(IWA_SERIALIZATION_HPP_INCLUDED) diff --git a/include/iwa/type_meta.hpp b/include/iwa/type_meta.hpp index 870e1f1..1d71df4 100644 --- a/include/iwa/type_meta.hpp +++ b/include/iwa/type_meta.hpp @@ -4,14 +4,285 @@ #if !defined(IWA_CLASS_HPP_INCLUDED) #define IWA_CLASS_HPP_INCLUDED +#include +#include #include #include #include +#include #include +#include #include namespace iwa { +class Class; +class Struct; +struct Type; + +template +struct BuildStruct : std::false_type {}; + +#define IWA_STRUCT_REFL_BEGIN(Type) \ +template<> \ +struct iwa::BuildStruct : std::true_type \ +{ \ + void operator()([[maybe_unused]] StructBuilder& builder) noexcept \ + { \ + using reflected_t [[maybe_unused]] = Type; +#define IWA_STRUCT_REFL_END() \ + } \ +}; +#define IWA_STRUCT_REFL_PROPERTY(Name) \ + builder.addProperty(#Name, &reflected_t::Name, offsetof(reflected_t, Name)) + +struct ClassType +{ + const Class* clazz; +}; + +struct StructType +{ + const Struct* struct_; +}; + +struct InnerType +{ + std::unique_ptr type; + + InnerType() noexcept : type(std::make_unique()) {} + InnerType(const InnerType& other) noexcept : type(std::make_unique(*other.type)) {} + InnerType(InnerType&&) noexcept = default; + + InnerType& operator=(const InnerType& other) noexcept + { + type = std::make_unique(*other.type); + return *this; + } + InnerType& operator=(InnerType&&) noexcept = default; + + template + InnerType& operator=(TType&& newType) noexcept + { + type = std::make_unique(std::forward(newType)); + return *this; + } + + [[nodiscard]] Type& operator*() noexcept { return *type; } + [[nodiscard]] const Type& operator*() const noexcept { return *type; } +}; + +struct ArrayType +{ + InnerType baseType; + std::size_t arraySize = 0; + std::size_t arrayStride = 0; +}; + +struct PointerType +{ + InnerType baseType; +}; + +struct ReferenceType +{ + InnerType baseType; +}; + +struct ConstType +{ + InnerType baseType; +}; + +struct VolatileType +{ + InnerType baseType; +}; + +struct VoidType {}; + +struct BoolType {}; + +enum class IntBits +{ + _8, + _16, + _32, + _64 +}; + +constexpr IntBits intBitsFromSize(std::size_t size) noexcept +{ + switch (size) + { + case 8: + return IntBits::_8; + case 16: + return IntBits::_16; + case 32: + return IntBits::_32; + case 64: + return IntBits::_64; + } + std::terminate(); +} + +struct IntType +{ + IntBits bits; + bool isSigned = false; +}; + +enum class FloatBits +{ + _32, + _64 +}; + +constexpr FloatBits floatBitsFromSize(std::size_t size) noexcept +{ + switch (size) + { + case 32: + return FloatBits::_32; + case 64: + return FloatBits::_64; + } + std::terminate(); +} + +struct FloatType +{ + FloatBits bits; +}; + +enum class CharType +{ + WCHAR_T, + CHAR8_T, + CHAR16_T, + CHAR32_T +}; + +enum class STDType +{ + STRING +}; + +struct BitFlagsType +{ + // InnerType baseType; + unsigned maxBits = 0; +}; + +using type_t = std::variant; +struct Type : type_t +{ + template + constexpr Type(TArgs&&... args) noexcept : type_t(std::forward(args)...) {} + constexpr Type(const Type&) noexcept = default; + constexpr Type(Type&&) noexcept = default; + + constexpr Type& operator=(const Type&) noexcept = default; + constexpr Type& operator=(Type&&) noexcept = default; +}; + +template +const Struct& reflectStruct() noexcept; + +template +constexpr Type getType() noexcept +{ + if constexpr (std::is_same_v) + { + return VoidType(); + } + if constexpr (std::is_same_v) + { + return BoolType(); + } + if constexpr (std::is_same_v) + { + return CharType::WCHAR_T; + } + if constexpr (std::is_same_v) + { + return CharType::CHAR8_T; + } + if constexpr (std::is_same_v) + { + return CharType::CHAR16_T; + } + if constexpr (std::is_same_v) + { + return CharType::CHAR32_T; + } + if constexpr (std::is_integral_v) + { + return IntType{ + .bits = intBitsFromSize(sizeof(T)), + .isSigned = std::is_signed_v + }; + } + if constexpr (std::is_floating_point_v) + { + return FloatType{ + .bits = floatBitsFromSize(sizeof(T)) + }; + } + if constexpr (std::is_pointer_v) + { + return PointerType{ + .baseType = getType>() + }; + } + if constexpr (std::is_reference_v) + { + return PointerType{ + .baseType = getType>() + }; + } + if constexpr (std::is_const_v) + { + return ConstType{ + .baseType = getType>() + }; + } + if constexpr (std::is_volatile_v) + { + return ConstType{ + .baseType = getType>() + }; + } + if constexpr (std::is_array_v) + { + using base_t = std::remove_extent_t; + return ArrayType{ + .baseType = getType(), + .arraySize = sizeof(T) / sizeof(base_t), + .arrayStride = sizeof(base_t) + }; + } + if constexpr (mijin::is_bitflags_v) + { + return BitFlagsType{ + // .baseType = getType() + .maxBits = 8 * sizeof(T) + }; + } + if constexpr (BuildStruct::value) + { + return StructType{ + .struct_ = &reflectStruct() + }; + } + if constexpr (std::is_same_v) + { + return STDType::STRING; + } + return VoidType(); // TODO! +} + struct Function { const char* name; @@ -20,6 +291,9 @@ struct Function struct Property { const char* name; + Type type; + int Struct::* member; + std::size_t offset; }; struct ClassCreationArgs @@ -83,10 +357,14 @@ public: } template - ClassBuilder& addProperty(const char* name, TField TObject::* field) + ClassBuilder& addProperty(const char* name, TField TObject::* property, std::size_t offset) { - (void) field; - mProperties.push_back({.name = name}); + mProperties.push_back({ + .name = name, + .type = getType(), + .member = reinterpret_cast(property), + .offset = offset + }); return *this; } }; @@ -108,16 +386,19 @@ namespace \ struct StructCreationArgs { std::string name; + std::vector properties; }; class Struct { private: std::string mName; + std::vector mProperties; public: - explicit Struct(StructCreationArgs args) noexcept : mName(std::move(args.name)) {} + explicit Struct(StructCreationArgs args) noexcept : mName(std::move(args.name)), mProperties(std::move(args.properties)) {} [[nodiscard]] const std::string& getName() const noexcept { return mName; } + [[nodiscard]] const std::vector getProperties() const noexcept { return mProperties; } }; template @@ -125,13 +406,27 @@ class StructBuilder { private: std::string mName; + std::vector mProperties; public: explicit StructBuilder(std::string name) noexcept : mName(std::move(name)) {} + template + StructBuilder& addProperty(const char* name, TField TStruct::* property, std::size_t offset) + { + mProperties.push_back({ + .name = name, + .type = getType(), + .member = reinterpret_cast(property), + .offset = offset + }); + return *this; + } + [[nodiscard]] Struct make() noexcept { return Struct({ - .name = std::move(mName) + .name = std::move(mName), + .properties = std::move(mProperties) }); } }; @@ -139,12 +434,6 @@ public: void registerClass(const Class* clazz) noexcept; [[nodiscard]] std::span getRegisteredClasses() noexcept; -template -void buildStruct(StructBuilder& /* builder */) noexcept -{ - static_assert(mijin::always_false_v, "Missing buildStruct() function."); -} - namespace impl { [[nodiscard]] constexpr std::string getStructNameFromSourceLocation(const std::source_location& sourceLocation) noexcept @@ -175,14 +464,277 @@ namespace impl template const Struct& reflectStruct() noexcept { + static_assert(BuildStruct::value, "No BuildStruct for this type."); static const Struct theStruct = [](std::string&& name) { StructBuilder builder(std::move(name)); - buildStruct(builder); + BuildStruct()(builder); return builder.make(); }(impl::getStructNameFromSourceLocation(std::source_location::current())); return theStruct; } + +template +constexpr decltype(auto) visitIntValue(TVisitor&& visitor, const void* value, const IntType& type) +{ + using return_t = std::result_of_t; + + if constexpr (std::is_same_v) + { + if (type.isSigned) + { + switch (type.bits) + { + case IntBits::_8: + return std::invoke(visitor, *static_cast(value)); + case IntBits::_16: + return std::invoke(visitor, *static_cast(value)); + case IntBits::_32: + return std::invoke(visitor, *static_cast(value)); + case IntBits::_64: + return std::invoke(visitor, *static_cast(value)); + } + } + else + { + switch (type.bits) + { + case IntBits::_8: + return std::invoke(visitor, *static_cast(value)); + case IntBits::_16: + return std::invoke(visitor, *static_cast(value)); + case IntBits::_32: + return std::invoke(visitor, *static_cast(value)); + case IntBits::_64: + return std::invoke(visitor, *static_cast(value)); + } + } + } + else + { + if (type.isSigned) + { + switch (type.bits) + { + case IntBits::_8: + std::invoke(visitor, *static_cast(value)); + case IntBits::_16: + std::invoke(visitor, *static_cast(value)); + case IntBits::_32: + std::invoke(visitor, *static_cast(value)); + case IntBits::_64: + std::invoke(visitor, *static_cast(value)); + } + } + else + { + switch (type.bits) + { + case IntBits::_8: + std::invoke(visitor, *static_cast(value)); + case IntBits::_16: + std::invoke(visitor, *static_cast(value)); + case IntBits::_32: + std::invoke(visitor, *static_cast(value)); + case IntBits::_64: + std::invoke(visitor, *static_cast(value)); + } + } + } +} + +template +constexpr decltype(auto) visitFloatValue(TVisitor&& visitor, const void* value, const FloatType& type) +{ + using return_t = std::result_of_t; + + if constexpr (std::is_same_v) + { + switch (type.bits) + { + case FloatBits::_32: + return std::invoke(visitor, *static_cast(value)); + case FloatBits::_64: + return std::invoke(visitor, *static_cast(value)); + } + } + else + { + switch (type.bits) + { + case FloatBits::_32: + std::invoke(visitor, *static_cast(value)); + case FloatBits::_64: + std::invoke(visitor, *static_cast(value)); + } + } +} + +template +constexpr decltype(auto) visitCharValue(TVisitor&& visitor, const void* value, CharType type) +{ + using return_t = std::result_of_t; + + if constexpr (std::is_same_v) + { + switch (type) + { + case CharType::WCHAR_T: + return std::invoke(visitor, *static_cast(value)); + case CharType::CHAR8_T: + return std::invoke(visitor, *static_cast(value)); + case CharType::CHAR16_T: + return std::invoke(visitor, *static_cast(value)); + case CharType::CHAR32_T: + return std::invoke(visitor, *static_cast(value)); + } + } + else + { + switch (type) + { + case CharType::WCHAR_T: + std::invoke(visitor, *static_cast(value)); + case CharType::CHAR8_T: + std::invoke(visitor, *static_cast(value)); + case CharType::CHAR16_T: + std::invoke(visitor, *static_cast(value)); + case CharType::CHAR32_T: + std::invoke(visitor, *static_cast(value)); + } + } +} + +template +constexpr decltype(auto) visitIntValue(TVisitor&& visitor, void* value, const IntType& type) +{ + using return_t = std::result_of_t; + + if constexpr (std::is_same_v) + { + if (type.isSigned) + { + switch (type.bits) + { + case IntBits::_8: + return std::invoke(visitor, *static_cast(value)); + case IntBits::_16: + return std::invoke(visitor, *static_cast(value)); + case IntBits::_32: + return std::invoke(visitor, *static_cast(value)); + case IntBits::_64: + return std::invoke(visitor, *static_cast(value)); + } + } + else + { + switch (type.bits) + { + case IntBits::_8: + return std::invoke(visitor, *static_cast(value)); + case IntBits::_16: + return std::invoke(visitor, *static_cast(value)); + case IntBits::_32: + return std::invoke(visitor, *static_cast(value)); + case IntBits::_64: + return std::invoke(visitor, *static_cast(value)); + } + } + } + else + { + if (type.isSigned) + { + switch (type.bits) + { + case IntBits::_8: + std::invoke(visitor, *static_cast(value)); + case IntBits::_16: + std::invoke(visitor, *static_cast(value)); + case IntBits::_32: + std::invoke(visitor, *static_cast(value)); + case IntBits::_64: + std::invoke(visitor, *static_cast(value)); + } + } + else + { + switch (type.bits) + { + case IntBits::_8: + std::invoke(visitor, *static_cast(value)); + case IntBits::_16: + std::invoke(visitor, *static_cast(value)); + case IntBits::_32: + std::invoke(visitor, *static_cast(value)); + case IntBits::_64: + std::invoke(visitor, *static_cast(value)); + } + } + } +} + +template +constexpr decltype(auto) visitFloatValue(TVisitor&& visitor, void* value, const FloatType& type) +{ + using return_t = std::result_of_t; + + if constexpr (std::is_same_v) + { + switch (type.bits) + { + case FloatBits::_32: + return std::invoke(visitor, *static_cast(value)); + case FloatBits::_64: + return std::invoke(visitor, *static_cast(value)); + } + } + else + { + switch (type.bits) + { + case FloatBits::_32: + std::invoke(visitor, *static_cast(value)); + case FloatBits::_64: + std::invoke(visitor, *static_cast(value)); + } + } +} + +template +constexpr decltype(auto) visitCharValue(TVisitor&& visitor, void* value, CharType type) +{ + using return_t = std::result_of_t; + + if constexpr (std::is_same_v) + { + switch (type) + { + case CharType::WCHAR_T: + return std::invoke(visitor, *static_cast(value)); + case CharType::CHAR8_T: + return std::invoke(visitor, *static_cast(value)); + case CharType::CHAR16_T: + return std::invoke(visitor, *static_cast(value)); + case CharType::CHAR32_T: + return std::invoke(visitor, *static_cast(value)); + } + } + else + { + switch (type) + { + case CharType::WCHAR_T: + std::invoke(visitor, *static_cast(value)); + case CharType::CHAR8_T: + std::invoke(visitor, *static_cast(value)); + case CharType::CHAR16_T: + std::invoke(visitor, *static_cast(value)); + case CharType::CHAR32_T: + std::invoke(visitor, *static_cast(value)); + } + } +} } #endif // !defined(IWA_CLASS_HPP_INCLUDED) diff --git a/include/iwa/window.hpp b/include/iwa/window.hpp index 4d25b63..912f735 100644 --- a/include/iwa/window.hpp +++ b/include/iwa/window.hpp @@ -81,13 +81,18 @@ public: mijin::Signal<> mouseLeft; mijin::Signal<> closeRequested; }; +} // reflection -template<> -inline void buildStruct(StructBuilder& builder) noexcept -{ - (void) builder; -} -} +// IWA_STRUCT_REFL_BEGIN(iwa::WindowCreationFlags) +// // IWA_STRUCT_REFL_PROPERTY(hidden) +// IWA_STRUCT_REFL_END() + +IWA_STRUCT_REFL_BEGIN(iwa::WindowCreationArgs) + IWA_STRUCT_REFL_PROPERTY(title); + IWA_STRUCT_REFL_PROPERTY(flags); + IWA_STRUCT_REFL_PROPERTY(width); + IWA_STRUCT_REFL_PROPERTY(height); +IWA_STRUCT_REFL_END() #endif //IWA_WINDOW_HPP_INCLUDED diff --git a/source/serialization.cpp b/source/serialization.cpp new file mode 100644 index 0000000..76a66df --- /dev/null +++ b/source/serialization.cpp @@ -0,0 +1,247 @@ +#include "iwa/serialization.hpp" + +namespace iwa +{ +namespace +{ +void propertyToYAML(const void* data, const iwa::Type& type, YAML::Emitter& emitter); +void propertyFromYAML(void* data, const iwa::Type& type, const YAML::Node& node); + +struct PropertyEmitVisitor +{ + const void* data; + YAML::Emitter& emitter; + + void operator()(const VoidType&) + { + emitter << YAML::Null; + } + + void operator()(const BoolType&) + { + emitter << *static_cast(data); + } + + void operator()(const IntType& type) + { + visitIntValue([&](auto&& value) + { + emitter << value; + }, data, type); + } + + void operator()(const FloatType& type) + { + visitFloatValue([&](auto&& value) + { + emitter << value; + }, data, type); + } + + void operator()(const CharType& type) + { + visitCharValue([&](auto&& value) + { + emitter << value; + }, data, type); + } + + void operator()(const ArrayType& type) + { + emitter << YAML::BeginSeq; + for (std::size_t idx = 0; idx < type.arraySize; ++idx) + { + const std::byte* ele = static_cast(data) + (idx * type.arrayStride); + propertyToYAML(ele, *type.baseType, emitter); + } + emitter << YAML::EndSeq; + } + + void operator()(const ClassType&) + { + MIJIN_ERROR("Not implemented."); + emitter << YAML::Null; + } + + void operator()(const StructType& type) + { + structToYAML(data, *type.struct_, emitter); + } + + void operator()(const PointerType&) + { + MIJIN_ERROR("Not implemented."); + emitter << YAML::Null; + } + + void operator()(const ReferenceType&) + { + MIJIN_ERROR("Not implemented."); + emitter << YAML::Null; + } + + void operator()(const ConstType& type) + { + propertyToYAML(data, *type.baseType, emitter); + } + + void operator()(const VolatileType& type) + { + propertyToYAML(data, *type.baseType, emitter); + } + + void operator()(const STDType& type) + { + switch (type) + { + case STDType::STRING: + emitter << *static_cast(data); + break; + } + } + + void operator()(const BitFlagsType&) + { + MIJIN_ERROR("Not implemented."); + emitter << YAML::Null; + } +}; + +struct PropertyParseVisitor +{ + void* data; + const YAML::Node& node; + + void operator()(const VoidType&) { /* ??? */ } + + void operator()(const BoolType&) + { + *static_cast(data) = node.as(); + } + + void operator()(const IntType& type) + { + visitIntValue([&](auto& value) + { + using type_t = std::decay_t; + value = node.as(); + }, data, type); + } + + void operator()(const FloatType& type) + { + visitFloatValue([&](auto& value) + { + using type_t = std::decay_t; + value = node.as(); + }, data, type); + } + + void operator()(const CharType& type) + { + MIJIN_ERROR("Not implemented."); + (void) type; + // visitCharValue([&](auto& value) + // { + // using type_t = std::decay_t; + // value = node.as(); + // }, data, type); + } + + void operator()(const ArrayType& type) + { + if (!node.IsSequence() || node.size() != type.arraySize) + { + throw std::runtime_error("Node not an array or size doesn't fit."); + } + for (std::size_t idx = 0; idx < type.arraySize; ++idx) + { + std::byte* ele = static_cast(data) + (idx * type.arrayStride); + propertyFromYAML(ele, *type.baseType, node[idx]); + } + } + + void operator()(const ClassType&) + { + MIJIN_ERROR("Not implemented."); + } + + void operator()(const StructType& type) + { + structFromYAML(data, *type.struct_, node); + } + + void operator()(const PointerType&) + { + MIJIN_ERROR("Not implemented."); + } + + void operator()(const ReferenceType&) + { + MIJIN_ERROR("Not implemented."); + } + + void operator()(const ConstType&) + { + MIJIN_ERROR("Can't read const values."); + } + + void operator()(const VolatileType& type) + { + propertyFromYAML(data, *type.baseType, node); + } + + void operator()(const STDType& type) + { + switch (type) + { + case STDType::STRING: + *static_cast(data) = node.as(); + break; + } + } + + void operator()(const BitFlagsType&) + { + MIJIN_ERROR("Not implemented."); + } +}; + +void propertyToYAML(const void* data, const iwa::Type& type, YAML::Emitter& emitter) +{ + std::visit(PropertyEmitVisitor{data, emitter}, type); +} + +void propertyFromYAML(void* data, const iwa::Type& type, const YAML::Node& node) +{ + std::visit(PropertyParseVisitor{data, node}, type); +} +} + +void structToYAML(const void* data, const Struct& type, YAML::Emitter& emitter) +{ + emitter << YAML::BeginMap; + for (const Property& property : type.getProperties()) + { + emitter << YAML::Key << property.name; + propertyToYAML(static_cast(data) + property.offset, property.type, emitter); + } + emitter << YAML::EndMap; +} + +void structFromYAML(void* data, const Struct& type, const YAML::Node& node) +{ + if (!node.IsMap()) + { + throw std::runtime_error("Expected map."); + } + for (const Property& property : type.getProperties()) + { + const YAML::Node& propNode = node[property.name]; + if (propNode.IsNull()) { + continue; + } + propertyFromYAML(static_cast(data) + property.offset, property.type, propNode); + } +} +}