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;
}