Program Listing for File archive.h¶
↰ Return to documentation for file (portal/serialization/archive.h)
//
// Copyright © 2025 Jonatan Nevo.
// Distributed under the MIT license (see LICENSE file).
//
#pragma once
#include <concepts>
#include <filesystem>
#include <string>
#include <variant>
#include <llvm/ADT/StringMap.h>
#include <glaze/core/reflect.hpp>
#include <portal/core/buffer.h>
#include <portal/core/reflection/property.h>
#include <portal/core/reflection/property_concepts.h>
#include "portal/core/strings/string_id.h"
#include "portal/core/strings/string_utils.h"
#include "portal/serialization/archive.h"
namespace portal
{
class ArchiveObject;
template <typename T>
struct Archivable;
template <typename T>
concept ArchiveableConcept = requires(T t, ArchiveObject& s) {
{ t.archive(s) } -> std::same_as<void>;
};
template <typename T>
concept DearchiveableConcept = requires(T t, ArchiveObject& d) {
{ T::dearchive(d) } -> std::same_as<T>;
};
template <typename T>
concept ExternalArchiveableConcept = requires(const T& t, ArchiveObject& ar) {
{ Archivable<std::remove_cvref_t<T>>::archive(t, ar) } -> std::same_as<void>;
};
template <typename T>
concept ExternalDearchiveableConcept = requires(ArchiveObject& ar) {
{ Archivable<std::remove_cvref_t<T>>::dearchive(ar) } -> std::same_as<T>;
};
class ArchiveObject
{
public:
using PropertyName = std::string_view;
virtual ~ArchiveObject() = default;
ArchiveObject() = default;
ArchiveObject(ArchiveObject&& other) noexcept;
ArchiveObject& operator=(ArchiveObject&& other) noexcept;
ArchiveObject(const ArchiveObject& other);
ArchiveObject& operator=(const ArchiveObject& other);
void update(const ArchiveObject& other);
template <typename T> requires(std::integral<T> || std::floating_point<T>) && (!std::is_same_v<T, bool>)
void add_property(const PropertyName& name, const T& t)
{
add_property_to_map(name, {Buffer::create<T>(t), reflection::get_property_type<T>(), reflection::PropertyContainerType::scalar, 1});
}
template <reflection::SmallVector T>
void add_property(const PropertyName& name, const T& t)
{
using ValueT = typename T::ValueParamT;
if constexpr (ArchiveableConcept<ValueT> || ExternalArchiveableConcept<ValueT>)
{
Buffer buffer = Buffer::allocate(t.size() * sizeof(ArchiveObject));
for (size_t i = 0; i < t.size(); ++i)
{
auto* object = new(buffer.as<ArchiveObject*>() + i) ArchiveObject();
if constexpr (ArchiveableConcept<ValueT>)
t[i].archive(*object);
else
Archivable<ValueT>::archive(t[i], *object);
}
add_property_to_map(name, {std::move(buffer), reflection::PropertyType::object, reflection::PropertyContainerType::array, t.size()});
}
else
{
Buffer buffer = Buffer::allocate(t.size() * sizeof(ArchiveObject));
for (size_t i = 0; i < t.size(); ++i)
{
auto* object = new(buffer.as<ArchiveObject*>() + i) ArchiveObject();
object->add_property("v", t[i]);
}
constexpr auto property_type = (reflection::String<ValueT>)
? reflection::PropertyType::null_term_string
: reflection::get_property_type<ValueT>();
add_property_to_map(name, {std::move(buffer), property_type, reflection::PropertyContainerType::array, t.size()});
}
}
template <reflection::Vector T>
void add_property(const PropertyName& name, const T& t)
{
using ValueT = typename T::value_type;
Buffer buffer = Buffer::allocate(t.size() * sizeof(ArchiveObject));
constexpr auto property_type = (reflection::String<ValueT>)
? reflection::PropertyType::null_term_string
: (ArchiveableConcept<ValueT> || ExternalArchiveableConcept<ValueT> || std::same_as<ValueT, ArchiveObject>)
? reflection::PropertyType::object
: reflection::get_property_type<ValueT>();
if constexpr (ArchiveableConcept<ValueT> || ExternalArchiveableConcept<ValueT>)
{
for (size_t i = 0; i < t.size(); ++i)
{
auto* object = new(buffer.as<ArchiveObject*>() + i) ArchiveObject();
if constexpr (ArchiveableConcept<ValueT>)
t[i].archive(*object);
else
Archivable<ValueT>::archive(t[i], *object);
}
}
else if constexpr (std::same_as<ValueT, ArchiveObject>)
{
for (size_t i = 0; i < t.size(); ++i)
{
new(buffer.as<ArchiveObject*>() + i) ArchiveObject(std::move(const_cast<ValueT&>(t[i])));
}
}
else
{
for (size_t i = 0; i < t.size(); ++i)
{
auto* object = new(buffer.as<ArchiveObject*>() + i) ArchiveObject();
object->add_property("v", t[i]);
}
}
add_property_to_map(name, {std::move(buffer), property_type, reflection::PropertyContainerType::array, t.size()});
}
template <reflection::String T>
void add_property(const PropertyName& name, const T& t)
{
add_property_to_map(
name,
{
Buffer::copy(t.data(), t.size() + 1),
reflection::PropertyType::character,
reflection::PropertyContainerType::null_term_string,
t.size() + 1
}
);
}
template <reflection::IsVec T>
void add_property(const PropertyName& name, const T& t)
{
constexpr auto element_number = T::length();
add_property_to_map(
name,
{Buffer::create<T>(t), reflection::get_property_type<typename T::value_type>(), reflection::PropertyContainerType::vector, element_number}
);
}
template <reflection::IsMatrix T>
void add_property(const PropertyName& name, const T& t)
{
constexpr auto element_number = T::length() * T::col_type::length();
add_property_to_map(
name,
{Buffer::create<T>(t), reflection::get_property_type<typename T::value_type>(), reflection::PropertyContainerType::matrix, element_number}
);
}
template <reflection::Map T> requires std::is_convertible_v<typename T::key_type, PropertyName>
void add_property(const PropertyName& name, const T& t)
{
auto* child = create_child(name);
for (auto& [key, value] : t)
{
child->add_property(key, value);
}
}
template <typename T> requires std::is_enum_v<T>
void add_property(const PropertyName& name, const T& e)
{
add_property(name, portal::to_string(e));
}
void add_property(const PropertyName& name, const char* t);
void add_property(const PropertyName& name, const std::filesystem::path& t);
void add_property(const PropertyName& name, const StringId& string_id);
void add_binary_block(const PropertyName& name, const std::vector<std::byte>& data) { add_binary_block(name, {data.data(), data.size()}); }
void add_binary_block(const PropertyName& name, const Buffer& buffer)
{
add_property_to_map(name, {Buffer::copy(buffer), reflection::PropertyType::binary, reflection::PropertyContainerType::array, buffer.size});
}
template <ArchiveableConcept T>
void add_property(const PropertyName& name, const T& t)
{
auto* child = create_child(name);
t.archive(*child);
}
template <ExternalArchiveableConcept T>
void add_property(const PropertyName& name, const T& t)
{
auto* child = create_child(name);
Archivable<std::remove_cvref_t<T>>::archive(t, *child);
}
template <typename T>
void add_property(const PropertyName& name, const T& t)
{
constexpr auto N = glz::reflect<T>::size;
if constexpr (N > 0)
{
auto* child = create_child(name);
glz::for_each<N>(
[&]<size_t I>()
{
child->add_property(glz::reflect<T>::keys[I], glz::get_member(t, glz::get<I>(glz::to_tie(t))));
}
);
}
}
template <typename T>
void add_property(const PropertyName& name, const std::optional<T>& optional_t)
{
if (optional_t.has_value())
{
add_property(name, optional_t.value());
}
}
template <typename T> requires std::is_enum_v<T>
bool get_property(const PropertyName& name, T& out)
{
std::string out_string;
if (!get_property<std::string>(name, out_string))
return false;
out = portal::from_string<T>(out_string);
return true;
}
template <typename T> requires(std::integral<T>) && (!std::is_same_v<T, bool>)
bool get_property(const PropertyName& name, T& out)
{
const auto& property = get_property_from_map(name);
if (property.type == reflection::PropertyType::invalid)
return false;
out = *property.value.as<T*>();
return true;
}
template <typename T> requires std::floating_point<T>
bool get_property(const PropertyName& name, T& out)
{
const auto& property = get_property_from_map(name);
if (property.type == reflection::PropertyType::invalid)
return false;
if (property.type != reflection::get_property_type<T>())
{
if constexpr (std::is_same_v<T, float>)
{
out = static_cast<T>(*property.value.as<double*>());
}
else if constexpr (std::is_same_v<T, double>)
{
out = static_cast<T>(*property.value.as<float*>());
}
else
{
LOG_ERROR_TAG("Serialization", "Property {} type mismatch", name);
return false;
}
}
else
{
out = *property.value.as<T*>();
}
return true;
}
template <reflection::Vector T>
bool get_property(const PropertyName& name, T& out)
{
out.clear();
const auto& prop = get_property_from_map(name);
if (prop.container_type == reflection::PropertyContainerType::invalid)
return false;
PORTAL_ASSERT(prop.container_type == reflection::PropertyContainerType::array, "Property {} container type mismatch", name);
return format_array<T, typename T::value_type>(name, prop, out);
}
template <reflection::SmallVector T>
bool get_property(const PropertyName& name, T& out)
{
out.clear();
const auto& prop = get_property_from_map(name);
if (prop.container_type == reflection::PropertyContainerType::invalid)
return false;
PORTAL_ASSERT(prop.container_type == reflection::PropertyContainerType::array, "Property {} container type mismatch", name);
return format_array<T, typename T::ValueParamT>(name, prop, out);
}
template <reflection::String T>
bool get_property(const PropertyName& name, T& out)
{
const auto& [value, type, container_type, elements_number] = get_property_from_map(name);
if (type == reflection::PropertyType::invalid)
return false;
size_t string_length;
if (container_type == reflection::PropertyContainerType::null_term_string)
string_length = elements_number - 1;
else if (container_type == reflection::PropertyContainerType::string)
string_length = elements_number;
else
{
LOG_ERROR_TAG("Serialization", "Property {} container type mismatch", name);
string_length = 0;
}
const auto* data = value.as<const typename T::value_type*>();
out = T(data, string_length);
return true;
}
template <reflection::IsVec T>
bool get_property(const PropertyName& name, T& out)
{
[[maybe_unused]] constexpr auto element_number = T::length();
const auto& property = get_property_from_map(name);
if (property.type == reflection::PropertyType::invalid)
return false;
// Native storage (from add_property<IsVec>)
if (property.container_type == reflection::PropertyContainerType::vector)
{
PORTAL_ASSERT(property.elements_number == element_number, "Property {} elements number mismatch", name);
out = T(*property.value.as<T*>());
return true;
}
// Array storage (from JSON deserialization)
if (property.container_type == reflection::PropertyContainerType::array)
{
return format_vec<T>(name, property, out);
}
LOG_ERROR_TAG("Serialization", "Property {} container type mismatch (expected vector or array)", name);
return false;
}
template <reflection::IsMatrix T>
bool get_property(const PropertyName& name, T& out)
{
constexpr auto element_number = T::length() * T::col_type::length();
const auto& property = get_property_from_map(name);
if (property.type == reflection::PropertyType::invalid)
return false;
// Native storage (from add_property<IsMatrix>)
if (property.container_type == reflection::PropertyContainerType::matrix)
{
PORTAL_ASSERT(property.elements_number == element_number, "Property {} elements number mismatch", name);
out = T(*property.value.as<T*>());
return true;
}
// Array storage (from JSON deserialization)
if (property.container_type == reflection::PropertyContainerType::array)
{
return format_mat<T>(name, property, out);
}
LOG_ERROR_TAG("Serialization", "Property {} container type mismatch (expected matrix or array)", name);
return false;
}
template <reflection::Map T> requires std::is_convertible_v<typename T::key_type, PropertyName>
bool get_property(const PropertyName& name, T& out)
{
using ValueType = T::mapped_type;
out.clear();
auto* child = get_object(name);
if (!child)
return false;
for (const auto& [key, property] : child->property_map)
{
if constexpr (DearchiveableConcept<typename T::mapped_type>)
{
auto* value_child = child->get_object(key);
if (value_child)
{
out[std::string(key)] = ValueType::dearchive(*value_child);
}
}
else
{
out[std::string(key)] = *property.value.as<typename T::mapped_type*>();
}
}
return true;
}
template <ArchiveableConcept T>
bool get_property(const PropertyName& name, T& out)
{
auto* child = get_object(name);
if (!child)
return false;
out = T::dearchive(*child);
return true;
}
template <ExternalDearchiveableConcept T>
bool get_property(const PropertyName& name, T& out)
{
auto* child = get_object(name);
if (!child)
return false;
out = Archivable<std::remove_cvref_t<T>>::dearchive(*child);
return true;
}
bool get_property(const PropertyName& name, std::filesystem::path& out);
bool get_property(const PropertyName& name, StringId& out);
bool get_binary_block(const PropertyName& name, Buffer& buffer)
{
const auto& property = get_property_from_map(name);
if (property.type == reflection::PropertyType::invalid)
return false;
PORTAL_ASSERT(property.type == reflection::PropertyType::binary, "Property {} type mismatch", name);
PORTAL_ASSERT(property.container_type == reflection::PropertyContainerType::array, "Property {} container type mismatch", name);
buffer = Buffer::copy(property.value);
return true;
}
bool get_binary_block(const PropertyName& name, std::vector<std::byte>& data)
{
const auto& property = get_property_from_map(name);
if (property.type == reflection::PropertyType::invalid)
return false;
PORTAL_ASSERT(property.type == reflection::PropertyType::binary, "Property {} type mismatch", name);
PORTAL_ASSERT(property.container_type == reflection::PropertyContainerType::array, "Property {} container type mismatch", name);
data.assign(property.value.as<const std::byte*>(), property.value.as<const std::byte*>() + property.value.size);
return true;
}
template <typename T>
bool get_property(const PropertyName& name, T& t)
{
constexpr auto N = glz::reflect<T>::size;
if constexpr (N > 0)
{
auto* child = get_object(name);
if (child == nullptr)
return false;
glz::for_each<N>(
[&]<size_t I>()
{
child->get_property(glz::reflect<T>::keys[I], glz::get_member(t, glz::get<I>(glz::to_tie(t))));
}
);
return true;
}
else
{
return false;
}
}
template <typename T>
void get_property(const PropertyName& name, std::optional<T>& optional_t)
{
T value;
const bool found = get_property(name, value);
if (found)
optional_t = std::optional<T>{std::move(value)};
else
optional_t = std::nullopt;
}
virtual ArchiveObject* create_child(PropertyName name);
virtual ArchiveObject* get_object(PropertyName name) const;
// Iterator support for range-based for loops
auto begin() { return property_map.begin(); }
auto end() { return property_map.end(); }
auto begin() const { return property_map.begin(); }
auto end() const { return property_map.end(); }
protected:
template <typename T, typename ValueType> requires(reflection::Vector<T> || reflection::SmallVector<T>)
bool format_array(const PropertyName& name, const reflection::Property& prop, T& out) const
{
const auto& [value, type, container_type, elements_number] = prop;
if constexpr (ArchiveableConcept<ValueType> || ExternalDearchiveableConcept<ValueType>)
{
auto* objects = value.as<ArchiveObject*>();
for (size_t i = 0; i < elements_number; ++i)
{
if constexpr (ArchiveableConcept<ValueType>)
out.push_back(ValueType::dearchive(objects[i]));
else
out.push_back(Archivable<ValueType>::dearchive(objects[i]));
}
}
else if constexpr (std::same_as<ValueType, ArchiveObject>)
{
for (size_t i = 0; i < elements_number; ++i)
{
auto* objects = value.as<ArchiveObject*>();
out.emplace_back(std::move(objects[i]));
}
}
else
{
auto* objects = value.as<ArchiveObject*>();
for (size_t i = 0; i < elements_number; ++i)
{
ValueType v;
if (!objects[i].get_property("v", v))
{
LOG_ERROR_TAG("Serialization", "Failed to get property from ArchiveObject '{}'", name);
return false;
}
out.push_back(static_cast<ValueType>(v));
}
}
return true;
}
template <reflection::IsVec T>
bool format_vec(const PropertyName& name, const reflection::Property& prop, T& out) const
{
constexpr auto element_number = T::length();
const auto& [value, type, container_type, elements_number] = prop;
if (elements_number != element_number)
{
LOG_ERROR_TAG("Serialization", "Property {} array size {} does not match vec size {}", name, elements_number, element_number);
return false;
}
auto* objects = value.as<ArchiveObject*>();
for (size_t i = 0; i < element_number; ++i)
{
typename T::value_type v;
if (!objects[i].get_property("v", v))
{
LOG_ERROR_TAG("Serialization", "Failed to get element {} from array for vec property {}", i, name);
return false;
}
out[static_cast<typename T::length_type>(i)] = v;
}
return true;
}
template <reflection::IsMatrix T>
bool format_mat(const PropertyName& name, const reflection::Property& prop, T& out) const
{
constexpr auto cols = T::length();
constexpr auto rows = T::col_type::length();
constexpr auto element_number = cols * rows;
const auto& [value, type, container_type, elements_number] = prop;
if (elements_number != element_number)
{
LOG_ERROR_TAG("Serialization", "Property {} array size {} does not match matrix size {}", name, elements_number, element_number);
return false;
}
auto* objects = value.as<ArchiveObject*>();
for (typename T::length_type col = 0; col < cols; ++col)
{
for (typename T::length_type row = 0; row < rows; ++row)
{
typename T::value_type v;
if (!objects[static_cast<size_t>(col * rows + row)].get_property("v", v))
{
LOG_ERROR_TAG("Serialization", "Failed to get element [{},{}] from array for matrix property {}", col, row, name);
return false;
}
out[col][row] = v;
}
}
return true;
}
[[nodiscard]] virtual reflection::Property& get_property_from_map(PropertyName name);
[[nodiscard]] virtual const reflection::Property& get_property_from_map(PropertyName name) const;
virtual reflection::Property& add_property_to_map(PropertyName name, reflection::Property&& property);
#ifdef PORTAL_DEBUG
std::unordered_map<std::string, reflection::Property> property_map;
#else
llvm::StringMap<reflection::Property> property_map;
#endif
friend class JsonArchive;
};
template <>
inline void ArchiveObject::add_property<bool>(const PropertyName& name, const bool& b)
{
add_property_to_map(name, {Buffer::create<bool>(b), reflection::PropertyType::boolean, reflection::PropertyContainerType::scalar, 1});
}
template <>
inline void ArchiveObject::add_property<uint128_t>(const PropertyName& name, const uint128_t& t)
{
add_property_to_map(name, {Buffer::create<uint128_t>(t), reflection::PropertyType::integer128, reflection::PropertyContainerType::scalar, 1});
}
template <>
inline bool ArchiveObject::get_property<bool>(const PropertyName& name, bool& out)
{
const auto& property = get_property_from_map(name);
if (property.type == reflection::PropertyType::invalid)
return false;
out = *property.value.as<bool*>();
return true;
}
template <>
inline bool ArchiveObject::get_property<uint128_t>(const PropertyName& name, uint128_t& out)
{
const auto& property = get_property_from_map(name);
if (property.type == reflection::PropertyType::invalid)
return false;
out = *property.value.as<uint128_t*>();
return true;
}
} // namespace portal