Program Listing for File serialize.h

Return to documentation for file (portal/serialization/serialize.h)

//
// Copyright © 2025 Jonatan Nevo.
// Distributed under the MIT license (see LICENSE file).
//

#pragma once

#include <glaze/core/reflect.hpp>

#include <portal/core/log.h>
#include <portal/core/reflection/property.h>

#include "portal/core/reflection/property_concepts.h"
#include "portal/core/strings/string_id.h"

namespace portal
{
class Serializer;
class Deserializer;

template <typename T>
struct Serializable;

template <typename T>
concept SerializableConcept = requires(const T t, Serializer& s) {
    { t.serialize(s) } -> std::same_as<void>;
};

template <typename T>
concept DeserializableConcept = requires(T t, Deserializer& d) {
    { T::deserialize(d) } -> std::same_as<T>;
};

template <typename T>
concept ExternalSerializable = requires(const T& t, Serializer& s) {
    { Serializable<std::remove_cvref_t<T>>::serialize(t, s) } -> std::same_as<void>;
};

template <typename T>
concept ExternalDeserializable = requires(Deserializer& d) {
    { Serializable<std::remove_cvref_t<T>>::deserialize(d) } -> std::same_as<T>;
};

class Serializer
{
public:
    virtual ~Serializer() = default;

    template <typename T> requires std::integral<std::remove_const_t<T>> || std::floating_point<std::remove_const_t<T>>
    void add_value(const T& t)
    {
        add_property(
            reflection::Property{
                Buffer{const_cast<void*>(static_cast<const void*>(&t)), sizeof(T)},
                reflection::get_property_type<std::remove_const_t<T>>(),
                reflection::PropertyContainerType::scalar,
                1
            }
        );
    }

    template <typename T> requires std::is_same_v<T, uint128_t>
    void add_value(const T& t)
    {
        add_property(
            reflection::Property{
                Buffer{const_cast<void*>(static_cast<const void*>(&t)), sizeof(T)},
                reflection::PropertyType::integer128,
                reflection::PropertyContainerType::scalar,
                1
            }
        );
    }


    template <reflection::Vector T> requires (!reflection::IsFundamental<typename T::value_type>)
    void add_value(const T& t)
    {
        const size_t size = t.size();
        add_value(size);

        for (const auto& value : t)
        {
            using ValueType = std::remove_cvref_t<typename T::value_type>;
            if constexpr (std::is_same_v<ValueType, StringId>)
                add_value(value);
            else
                add_value<ValueType>(value);
        }
    }

    template <reflection::Vector T> requires reflection::IsFundamental<typename T::value_type>
    void add_value(const T& t)
    {
        add_property(
            reflection::Property{
                Buffer{const_cast<void*>(static_cast<const void*>(t.data())), t.size() * sizeof(typename T::value_type)},
                reflection::get_property_type<typename T::value_type>(),
                reflection::PropertyContainerType::array,
                t.size()
            }
        );
    }

    template <reflection::String T>
    void add_value(const T& t)
    {
        add_property(
            reflection::Property{
                Buffer{const_cast<void*>(static_cast<const void*>(t.data())), (t.size() * sizeof(typename T::value_type)) + 1},
                reflection::PropertyType::character,
                reflection::PropertyContainerType::null_term_string,
                t.size() + 1
            }
        );
    }

    void add_value(const std::string_view string_view)
    {
        add_property(
            reflection::Property{
                Buffer{
                    const_cast<void*>(static_cast<const void*>(string_view.data())),
                    (string_view.size() * sizeof(typename std::string_view::value_type)) + 1
                },
                reflection::PropertyType::character,
                reflection::PropertyContainerType::null_term_string,
                string_view.size() + 1
            }
        );
    }

    template <reflection::GlmVec1 T>
    void add_value(const T& t)
    {
        add_property(
            reflection::Property{
                Buffer{&t.x, sizeof(typename T::value_type)},
                reflection::get_property_type<typename T::value_type>(),
                reflection::PropertyContainerType::vector,
                1
            }
        );
    }

    template <reflection::GlmVec2 T>
    void add_value(const T& t)
    {
        add_property(
            reflection::Property{
                Buffer{&t.x, 2 * sizeof(typename T::value_type)},
                reflection::get_property_type<typename T::value_type>(),
                reflection::PropertyContainerType::vector,
                2
            }
        );
    }

    template <reflection::GlmVec3 T>
    void add_value(const T& t)
    {
        add_property(
            reflection::Property{
                Buffer{&t.x, 3 * sizeof(typename T::value_type)},
                reflection::get_property_type<typename T::value_type>(),
                reflection::PropertyContainerType::vector,
                3
            }
        );
    }

    template <reflection::GlmVec4 T>
    void add_value(const T& t)
    {
        add_property(
            reflection::Property{
                Buffer{&t.x, 4 * sizeof(typename T::value_type)},
                reflection::get_property_type<typename T::value_type>(),
                reflection::PropertyContainerType::vector,
                4
            }
        );
    }

    template <reflection::IsMatrix T>
    void add_value(const T& t)
    {
        constexpr auto element_number = T::length() * T::col_type::length();
        constexpr auto value_size = sizeof(typename T::value_type);

        add_property(
            reflection::Property{
                Buffer{&t[0][0], element_number * value_size},
                reflection::get_property_type<typename T::value_type>(),
                reflection::PropertyContainerType::matrix,
                element_number
            }
        );
    }

    template <reflection::Map T>
    void add_value(const T& t)
    {
        const size_t size = t.size();
        add_value(size);

        for (const auto& [key, value] : t)
        {
            using KeyType = std::remove_const_t<typename T::key_type>;
            using ValueType = std::remove_const_t<typename T::mapped_type>;

            add_value<KeyType>(key);
            add_value<ValueType>(value);
        }
    }

    template <typename T> requires std::is_enum_v<T>
    void add_value(const T& t)
    {
        add_value<std::underlying_type_t<T>>(static_cast<std::underlying_type_t<T>>(t));
    }

    template <SerializableConcept T>
    void add_value(const T& t)
    {
        t.serialize(*this);
    }

    template <ExternalSerializable T>
    void add_value(const T& t)
    {
        Serializable<std::remove_cvref_t<T>>::serialize(t, *this);
    }

    template <typename T>
    void add_value(const std::optional<T>& optional_t)
    {
        add_value(optional_t.has_value());
        if (optional_t.has_value())
            add_value(optional_t.value());
    }

    template <typename T> requires glz::reflectable<T> && (!SerializableConcept<T> && !ExternalSerializable<T>)
    void add_value(const T& t)
    {
        glz::for_each_field(
            t,
            [&](auto&& field)
            {
                add_value(field);
            }
        );
    }

    void add_value(const char* t)
    {
        const size_t length = strlen(t);
        add_property(
            reflection::Property{
                Buffer{const_cast<void*>(static_cast<const void*>(t)), (length * sizeof(char)) + 1},
                reflection::PropertyType::character,
                reflection::PropertyContainerType::null_term_string,
                length + 1
            }
        );
    }

    void add_value(void* t, const size_t length)
    {
        add_property(reflection::Property{Buffer{t, length}, reflection::PropertyType::binary, reflection::PropertyContainerType::string, length});
    }

    void add_value(const StringId& id)
    {
        add_value(id.id);
    }

protected:
    virtual void add_property(reflection::Property property) = 0;
};

class Deserializer
{
public:
    virtual ~Deserializer() = default;

    template <typename T> requires std::integral<T> || std::floating_point<T>
    void get_value(T& t)
    {
        auto property = get_property();

        PORTAL_ASSERT(property.container_type == reflection::PropertyContainerType::scalar, "Property container type mismatch");
        PORTAL_ASSERT(property.type == reflection::get_property_type<T>(), "Property type mismatch");
        PORTAL_ASSERT(property.value.size == sizeof(T), "Value size mismatch, expected: {} got {}", sizeof(T), property.value.size);

        t = *property.value.as<T*>();
    }

    template <typename T> requires std::is_same_v<T, uint128_t>
    void get_value(T& t)
    {
        auto property = get_property();

        PORTAL_ASSERT(property.container_type == reflection::PropertyContainerType::scalar, "Property container type mismatch");
        PORTAL_ASSERT(property.type == reflection::PropertyType::integer128, "Property type mismatch");
        PORTAL_ASSERT(property.value.size == sizeof(T), "Value size mismatch, expected: {} got {}", sizeof(T), property.value.size);

        t = *property.value.as<T*>();
    }

    template <reflection::Vector T> requires (!reflection::IsFundamental<typename T::value_type>)
    void get_value(T& t)
    {
        size_t size;
        get_value<size_t>(size);

        if constexpr (requires { t.reserve(size); })
        {
            t.reserve(size);
        }

        for (size_t i = 0; i < size; ++i)
        {
            typename T::value_type value;
            if constexpr (std::is_same_v<typename T::value_type, StringId>)
                get_value(value);
            else
                get_value<typename T::value_type>(value);
            t.push_back(std::move(value));
        }
    }

    template <reflection::Vector T> requires reflection::IsFundamental<typename T::value_type>
    void get_value(T& t)
    {
        auto property = get_property();

        PORTAL_ASSERT(property.container_type == reflection::PropertyContainerType::array, "Property container type mismatch");
        PORTAL_ASSERT(property.type == reflection::get_property_type<typename T::value_type>(), "Property type mismatch");

        auto array_length = property.elements_number;
        auto* data = property.value.as<typename T::value_type*>();
        t = T(data, data + array_length);
    }

    template <reflection::String T>
    void get_value(T& t)
    {
        auto property = get_property();

        PORTAL_ASSERT(property.type == reflection::PropertyType::character, "Property type mismatch");

        size_t string_length = 0;
        if (property.container_type == reflection::PropertyContainerType::null_term_string)
            string_length = property.elements_number - 1;
        else if (property.container_type == reflection::PropertyContainerType::string)
            string_length = property.elements_number;

        const auto* data = property.value.as<const typename T::value_type*>();
        t = T(data, string_length);
    }

    template <reflection::IsVec T>
    void get_value(T& t)
    {
        constexpr auto element_number = T::length();

        auto property = get_property();

        PORTAL_ASSERT(property.type == reflection::get_property_type<typename T::value_type>(), "Property type mismatch");
        PORTAL_ASSERT(property.container_type == reflection::PropertyContainerType::vector, "Property container type mismatch");
        PORTAL_ASSERT(property.elements_number == element_number, "Property elements number mismatch");

        t = T(*property.value.as<T*>());
    }

    template <reflection::IsMatrix T>
    bool get_value(T& out)
    {
        [[maybe_unused]] constexpr auto element_number = T::length() * T::col_type::length();

        const auto& property = get_property();
        if (property.type == reflection::PropertyType::invalid)
            return false;

        PORTAL_ASSERT(property.type == reflection::get_property_type<typename T::value_type>(), "Property type mismatch");
        PORTAL_ASSERT(property.container_type == reflection::PropertyContainerType::matrix, "Property container type mismatch");
        PORTAL_ASSERT(property.elements_number == element_number, "Property elements number mismatch");

        out = T(*property.value.as<T*>());
        return true;
    }

    template <reflection::Map T>
    void get_value(T& t)
    {
        size_t size;
        get_value<size_t>(size);

        t.clear();
        if constexpr (requires { t.reserve(size); })
        {
            t.reserve(size);
        }

        for (size_t i = 0; i < size; ++i)
        {
            typename T::key_type key;
            typename T::mapped_type value;
            get_value(key);
            get_value(value);
            t.insert_or_assign(std::move(key), std::move(value));
        }
    }


    template <typename T> requires std::is_enum_v<T>
    void get_value(T& t)
    {
        std::underlying_type_t<T> underlying;
        get_value(underlying);
        t = static_cast<T>(underlying);
    }

    template <DeserializableConcept T>
    void get_value(T& t)
    {
        t = T::deserialize(*this);
    }

    template <ExternalDeserializable T>
    void get_value(T& t)
    {
        t = Serializable<std::remove_cvref_t<T>>::deserialize(*this);
    }

    template <typename T>
    void get_value(std::optional<T>& optional_t)
    {
        bool has_value;
        get_value<bool>(has_value);

        if (has_value)
        {
            T underlying;
            if constexpr (std::is_same_v<T, StringId>)
                get_value(underlying);
            else
                get_value<T>(underlying);
            optional_t = std::optional<T>{std::move(underlying)};
        }
        else
            optional_t = std::nullopt;
    }

    template <typename T> requires glz::reflectable<T> && (!DeserializableConcept<T> && !ExternalDeserializable<T>)
    void get_value(T& output)
    {
        constexpr auto N = glz::reflect<T>::size;

        if constexpr (N > 0)
        {
            glz::for_each<N>(
                [&]<size_t I>()
                {
                    auto& field = glz::get_member(output, glz::get<I>(glz::to_tie(output)));
                    get_value<std::remove_cvref_t<decltype(field)>>(field);
                }
            );
        }
    }

    void get_value(char*& t, const size_t length)
    {
        const auto property = get_property();

        PORTAL_ASSERT(property.type == reflection::PropertyType::character, "Property type mismatch");

        PORTAL_ASSERT(property.elements_number == length, "Value size mismatch, expected: {} got {}", length, property.elements_number);
        memcpy(t, property.value.data, (std::min)(length, property.elements_number));
    }

    void get_value(StringId& out)
    {
        StringId::HashType id = 0;
        get_value(id);
        out = StringId{id};
    }

protected:
    virtual reflection::Property get_property() = 0;
};
} // namespace portal

template <portal::SerializableConcept T>
portal::Serializer& operator<<(portal::Serializer& s, const T& t)
{
    t.serialize(s);
    return s;
}

template <portal::reflection::PropertyConcept T>
portal::Serializer& operator<<(portal::Serializer& s, const T& t)
{
    T copy = t;
    s.add_value<T>(copy);
    return s;
}

template <portal::reflection::PropertyConcept T>
portal::Serializer& operator<<(portal::Serializer& s, T& t)
{
    s.add_value<T>(t);
    return s;
}

template <typename T> requires std::is_enum_v<T>
portal::Serializer& operator<<(portal::Serializer& s, const T& value)
{
    return s << static_cast<std::underlying_type_t<T>>(value);
}

inline portal::Serializer& operator<<(portal::Serializer& s, const char* str)
{
    s.add_value(str);
    return s;
}

template <typename T>
portal::Serializer* operator<<(portal::Serializer* s, const T& t)
{
    return &(*s << t);
}


template <portal::DeserializableConcept T>
portal::Deserializer& operator>>(portal::Deserializer& d, T& t)
{
    t = T::deserialize(d);
    return d;
}

template <portal::reflection::PropertyConcept T>
portal::Deserializer& operator>>(portal::Deserializer& d, T& t)
{
    d.get_value<T>(t);
    return d;
}

template <typename T> requires std::is_enum_v<T>
portal::Deserializer& operator>>(portal::Deserializer& d, T& t)
{
    std::underlying_type_t<T> underlying;
    d >> underlying;
    t = static_cast<T>(underlying);
    return d;
}