mirror of
https://github.com/RPCSX/rpcsx.git
synced 2025-12-06 07:12:14 +01:00
rx: add simple serialization utility
This commit is contained in:
parent
2df7b3871c
commit
2f70a5b6dd
494
rx/include/rx/Serializer.hpp
Normal file
494
rx/include/rx/Serializer.hpp
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
#pragma once
|
||||
|
||||
#include "rx/align.hpp"
|
||||
#include "rx/refl.hpp"
|
||||
#include <cassert>
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace rx {
|
||||
struct Serializer;
|
||||
struct Deserializer;
|
||||
|
||||
template <typename T> struct TypeSerializer;
|
||||
|
||||
namespace detail {
|
||||
// TODO: replace with std::is_trivially_relocatable once available
|
||||
template <typename T>
|
||||
concept TriviallyRelocatable =
|
||||
std::is_trivially_copyable_v<std::remove_cvref_t<T>> &&
|
||||
std::is_trivially_default_constructible_v<std::remove_cvref_t<T>> &&
|
||||
!std::is_pointer_v<T> && !std::is_reference_v<T>;
|
||||
|
||||
template <typename T>
|
||||
concept TypeSerializable = requires(Serializer &s, const T &value) {
|
||||
TypeSerializer<std::remove_cvref_t<T>>::serialize(s, value);
|
||||
} && (requires(Deserializer &d) {
|
||||
{
|
||||
TypeSerializer<std::remove_cvref_t<T>>::deserialize(d)
|
||||
} -> std::same_as<std::remove_cvref_t<T>>;
|
||||
} || requires(Deserializer &d, T &value) {
|
||||
TypeSerializer<std::remove_cvref_t<T>>::deserialize(d, value);
|
||||
});
|
||||
|
||||
template <typename T>
|
||||
concept IsRange = requires(T &object) {
|
||||
object.size();
|
||||
*object.begin();
|
||||
object.begin() != object.end();
|
||||
};
|
||||
|
||||
struct StructSerializerField {
|
||||
std::size_t offset;
|
||||
std::size_t alignment;
|
||||
std::size_t size;
|
||||
void (*serialize)(rx::Serializer &s, const void *object);
|
||||
void (*deserialize)(rx::Deserializer &s, void *object);
|
||||
};
|
||||
|
||||
// try to call free function
|
||||
template <typename T>
|
||||
void callSerializeFunction(Serializer &s, const T &value)
|
||||
requires requires { serialize(s, value); }
|
||||
{
|
||||
serialize(s, value);
|
||||
}
|
||||
|
||||
// try to call free function
|
||||
template <typename T>
|
||||
void callDeserializeFunction(Deserializer &s, T &value)
|
||||
requires requires { deserialize(s, value); }
|
||||
{
|
||||
deserialize(s, value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T callDeserializeFunction(Deserializer &s)
|
||||
requires requires(T &value) { deserialize<T>(s); }
|
||||
{
|
||||
return deserialize<T>(s);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
concept SerializableImpl = requires(Serializer &s, const T &value) {
|
||||
value.serialize(s);
|
||||
} || requires(Serializer &s, const T &value) {
|
||||
callSerializeFunction(s, value);
|
||||
} || requires(Serializer &s, const T &value) {
|
||||
TypeSerializer<std::remove_cvref_t<T>>::serialize(s, value);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept DeserializableImpl = requires(Deserializer &d, T &value) {
|
||||
value.deserialize(d);
|
||||
} || requires(Deserializer &d, T &value) {
|
||||
value = std::remove_cvref_t<T>::deserialize(d);
|
||||
} || requires(Deserializer &d, T &value) {
|
||||
callDeserializeFunction(d, value);
|
||||
} || requires(Deserializer &d, T &value) {
|
||||
value = callDeserializeFunction<std::remove_cvref_t<T>>(d);
|
||||
} || requires(Deserializer &d) {
|
||||
{
|
||||
TypeSerializer<std::remove_cvref_t<T>>::deserialize(d)
|
||||
} -> std::same_as<std::remove_cvref_t<T>>;
|
||||
} || requires(Deserializer &d, T &value) {
|
||||
TypeSerializer<std::remove_cvref_t<T>>::deserialize(d, value);
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <typename T>
|
||||
concept Serializable =
|
||||
detail::SerializableImpl<T> && detail::DeserializableImpl<T>;
|
||||
|
||||
namespace detail {
|
||||
struct SerializableFieldTest {
|
||||
template <Serializable FieldT>
|
||||
requires(std::is_default_constructible_v<FieldT>)
|
||||
constexpr operator FieldT();
|
||||
};
|
||||
|
||||
struct SerializableAnyFieldTest {
|
||||
template <typename FieldT> constexpr operator FieldT();
|
||||
};
|
||||
|
||||
template <typename T, std::size_t I> constexpr bool isSerializableField() {
|
||||
auto impl = []<std::size_t... Before, std::size_t... After>(
|
||||
std::index_sequence<Before...>,
|
||||
std::index_sequence<After...>) {
|
||||
return requires {
|
||||
T{(Before, SerializableAnyFieldTest{})..., SerializableFieldTest{},
|
||||
(After, SerializableAnyFieldTest{})...};
|
||||
};
|
||||
};
|
||||
|
||||
return impl(std::make_index_sequence<I>{},
|
||||
std::make_index_sequence<rx::fieldCount<T> - I - 1>{});
|
||||
}
|
||||
|
||||
template <typename T, std::size_t N = rx::fieldCount<T>>
|
||||
constexpr bool isSerializableFields() {
|
||||
auto impl = []<std::size_t... I>(std::index_sequence<I...>) {
|
||||
return requires { T{(I, SerializableFieldTest{})...}; };
|
||||
};
|
||||
|
||||
return impl(std::make_index_sequence<N>{});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
concept SerializableClass = !detail::IsRange<T> && std::is_class_v<T> &&
|
||||
rx::fieldCount<T> > 0 && isSerializableFields<T>();
|
||||
} // namespace detail
|
||||
|
||||
struct Serializer {
|
||||
virtual ~Serializer() = default;
|
||||
virtual void write(std::span<const std::byte> data) = 0;
|
||||
|
||||
template <Serializable T> void serialize(const T &value) {
|
||||
if constexpr (requires { value.serialize(*this); }) {
|
||||
value.serialize(*this);
|
||||
} else if constexpr (requires { callSerializeFunction(*this, value); }) {
|
||||
callSerializeFunction(*this, value);
|
||||
} else {
|
||||
TypeSerializer<std::remove_cvref_t<T>>::serialize(*this, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct Deserializer {
|
||||
virtual ~Deserializer() = default;
|
||||
virtual void read(std::span<std::byte> data) = 0;
|
||||
|
||||
template <Serializable T> [[nodiscard]] std::remove_cvref_t<T> deserialize() {
|
||||
using type = std::remove_cvref_t<T>;
|
||||
if constexpr (requires {
|
||||
{ type::deserialize(*this) } -> std::convertible_to<type>;
|
||||
}) {
|
||||
return T::deserialize(*this);
|
||||
} else if constexpr (requires(type &result) {
|
||||
type::deserialize(*this, result);
|
||||
}) {
|
||||
type result;
|
||||
T::deserialize(*this, result);
|
||||
return result;
|
||||
} else if constexpr (requires(type &result) {
|
||||
{
|
||||
result.deserialize(*this)
|
||||
} -> std::convertible_to<type>;
|
||||
}) {
|
||||
T result;
|
||||
result.deserialize(*this);
|
||||
return result;
|
||||
} else if constexpr (requires(type &result) {
|
||||
type::deserialize(*this, result);
|
||||
}) {
|
||||
type result;
|
||||
T::deserialize(*this, result);
|
||||
return result;
|
||||
} else if constexpr (requires {
|
||||
detail::callDeserializeFunction<type>(*this);
|
||||
}) {
|
||||
return detail::callDeserializeFunction<type>(*this);
|
||||
} else if constexpr (requires(type &value) {
|
||||
detail::callDeserializeFunction(*this, value);
|
||||
}) {
|
||||
|
||||
type result;
|
||||
detail::callDeserializeFunction(*this, result);
|
||||
return result;
|
||||
} else if constexpr (requires {
|
||||
TypeSerializer<type>::deserialize(*this);
|
||||
}) {
|
||||
return TypeSerializer<type>::deserialize(*this);
|
||||
} else {
|
||||
type result;
|
||||
TypeSerializer<type>::deserialize(*this, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
template <Serializable T> void deserialize(T &result) {
|
||||
if constexpr (requires { T::deserialize(*this, result); }) {
|
||||
T::deserialize(*this, result);
|
||||
} else if constexpr (requires { result.deserialize(*this); }) {
|
||||
result.deserialize(*this);
|
||||
} else if constexpr (requires {
|
||||
{ T::deserialize(*this) } -> std::convertible_to<T>;
|
||||
}) {
|
||||
result = T::deserialize(*this);
|
||||
} else if constexpr (requires {
|
||||
detail::callDeserializeFunction(*this, result);
|
||||
}) {
|
||||
detail::callDeserializeFunction(*this, result);
|
||||
} else if constexpr (requires {
|
||||
detail::callDeserializeFunction<T>(*this);
|
||||
}) {
|
||||
result = detail::callDeserializeFunction<T>(*this);
|
||||
} else if constexpr (requires {
|
||||
TypeSerializer<T>::deserialize(*this, result);
|
||||
}) {
|
||||
TypeSerializer<T>::deserialize(*this, result);
|
||||
} else {
|
||||
result = TypeSerializer<T>::deserialize(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void setFailure() { mFailure = true; }
|
||||
[[nodiscard]] bool failure() const { return mFailure; }
|
||||
|
||||
private:
|
||||
bool mFailure = false;
|
||||
};
|
||||
|
||||
template <detail::TriviallyRelocatable T> struct TypeSerializer<T> {
|
||||
static void serialize(Serializer &s, const T &t) {
|
||||
std::byte rawBytes[sizeof(T)];
|
||||
std::memcpy(rawBytes, &t, sizeof(T));
|
||||
s.write(rawBytes);
|
||||
}
|
||||
|
||||
static T deserialize(Deserializer &s) {
|
||||
alignas(T) std::byte rawBytes[sizeof(T)];
|
||||
s.read(rawBytes);
|
||||
|
||||
return std::move(std::bit_cast<T>(rawBytes));
|
||||
}
|
||||
};
|
||||
|
||||
template <Serializable A, Serializable B>
|
||||
struct TypeSerializer<std::pair<A, B>> {
|
||||
static void serialize(Serializer &s, const std::pair<A, B> &t) {
|
||||
s.serialize(t.first);
|
||||
s.serialize(t.second);
|
||||
}
|
||||
static std::pair<A, B> deserialize(Deserializer &s) {
|
||||
auto a = s.deserialize<A>();
|
||||
auto b = s.deserialize<B>();
|
||||
|
||||
return {
|
||||
std::move(a),
|
||||
std::move(b),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
template <Serializable... T> struct TypeSerializer<std::tuple<T...>> {
|
||||
static void serialize(Serializer &s, const std::tuple<T...> &t) {
|
||||
std::apply([&s](auto &value) { s.serialize(value); }, t);
|
||||
}
|
||||
|
||||
static std::tuple<T...> deserialize(Deserializer &s) {
|
||||
return std::tuple<T...>{s.deserialize<T>()...};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
requires std::is_default_constructible_v<T> &&
|
||||
requires(Serializer &s, T &object) {
|
||||
s.serialize(object.size());
|
||||
s.serialize(*object.begin());
|
||||
object.resize(1);
|
||||
object.begin() != object.end();
|
||||
}
|
||||
struct TypeSerializer<T> {
|
||||
using item_type = std::remove_cvref_t<decltype(*std::declval<T>().begin())>;
|
||||
|
||||
static void serialize(Serializer &s, const T &t) {
|
||||
s.serialize(static_cast<std::uint32_t>(t.size()));
|
||||
|
||||
if constexpr (detail::TriviallyRelocatable<item_type> &&
|
||||
requires { reinterpret_cast<const std::byte *>(t.data()); }) {
|
||||
s.write({reinterpret_cast<const std::byte *>(t.data()),
|
||||
t.size() * sizeof(item_type)});
|
||||
} else {
|
||||
for (auto &item : t) {
|
||||
s.serialize(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static T deserialize(Deserializer &s) {
|
||||
auto size = s.deserialize<std::uint32_t>();
|
||||
|
||||
T t;
|
||||
t.resize(size);
|
||||
|
||||
if constexpr (detail::TriviallyRelocatable<item_type> &&
|
||||
requires { reinterpret_cast<const std::byte *>(t.data()); }) {
|
||||
s.read({reinterpret_cast<std::byte *>(t.data()),
|
||||
t.size() * sizeof(item_type)});
|
||||
} else {
|
||||
for (auto &item : t) {
|
||||
s.deserialize(item);
|
||||
}
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
};
|
||||
|
||||
template <detail::IsRange T>
|
||||
requires(
|
||||
std::is_default_constructible_v<T> &&
|
||||
requires(Serializer &s, T &object) {
|
||||
s.serialize(object.size());
|
||||
s.serialize(*object.begin());
|
||||
object.insert(std::move(*object.begin()));
|
||||
object.begin() != object.end();
|
||||
} && !requires(Serializer &s, T &object) { object.resize(1); })
|
||||
struct TypeSerializer<T> {
|
||||
using item_type = std::remove_cvref_t<decltype(*std::declval<T>().begin())>;
|
||||
|
||||
static void serialize(Serializer &s, const T &t) {
|
||||
s.serialize(static_cast<std::uint32_t>(t.size()));
|
||||
|
||||
for (auto &item : t) {
|
||||
s.serialize(item);
|
||||
}
|
||||
}
|
||||
|
||||
static T deserialize(Deserializer &s) {
|
||||
auto size = s.deserialize<std::uint32_t>();
|
||||
|
||||
if (s.failure()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
T result;
|
||||
|
||||
for (std::uint32_t i = 0; i < size; ++i) {
|
||||
result.insert(s.deserialize<item_type>());
|
||||
|
||||
if (s.failure()) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
template <SerializableClass T> struct StructSerializerBuilder {
|
||||
static constexpr std::array<StructSerializerField, rx::fieldCount<T>>
|
||||
build() {
|
||||
StructSerializerBuilder result;
|
||||
|
||||
auto impl = [&]<std::size_t... I>(std::index_sequence<I...>) {
|
||||
static_cast<void>(T{FieldVisitor{&result, I}...});
|
||||
};
|
||||
|
||||
impl(std::make_index_sequence<rx::fieldCount<T>>{});
|
||||
|
||||
std::size_t nextOffset = 0;
|
||||
for (auto &field : result.fields) {
|
||||
auto fieldOffset = alignUp(nextOffset, field.alignment);
|
||||
nextOffset = fieldOffset + field.size;
|
||||
field.offset = fieldOffset;
|
||||
}
|
||||
|
||||
return result.fields;
|
||||
}
|
||||
|
||||
private:
|
||||
struct FieldVisitor {
|
||||
StructSerializerBuilder *builder;
|
||||
std::size_t fieldIndex;
|
||||
|
||||
template <typename FieldT> constexpr operator FieldT() {
|
||||
builder->addField<FieldT>(fieldIndex);
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename FieldT> constexpr void addField(std::size_t index) {
|
||||
fields[index] = StructSerializerField{
|
||||
.offset = 0,
|
||||
.alignment = alignof(FieldT),
|
||||
.size = sizeof(FieldT),
|
||||
.serialize =
|
||||
+[](rx::Serializer &s, const void *object) {
|
||||
s.serialize(*static_cast<const FieldT *>(object));
|
||||
},
|
||||
.deserialize =
|
||||
+[](rx::Deserializer &s, void *object) {
|
||||
s.deserialize(*static_cast<FieldT *>(object));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
std::array<StructSerializerField, fieldCount<T>> fields;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <detail::SerializableClass T>
|
||||
requires(!requires {
|
||||
std::index_sequence<detail::StructSerializerBuilder<T>::build().size()>{};
|
||||
})
|
||||
struct TypeSerializer<T> {
|
||||
static const auto &getFields() {
|
||||
static const auto fields = detail::StructSerializerBuilder<T>::build();
|
||||
return fields;
|
||||
}
|
||||
|
||||
static void serialize(Serializer &s, const T &object) {
|
||||
s.serialize<std::uint32_t>(sizeof(object));
|
||||
auto bytes = std::bit_cast<std::byte *>(&object);
|
||||
for (auto field : getFields()) {
|
||||
field.serialize(s, bytes + field.offset);
|
||||
}
|
||||
}
|
||||
|
||||
static void deserialize(Deserializer &s, T &object) {
|
||||
if (s.deserialize<std::uint32_t>() != sizeof(object)) {
|
||||
s.setFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
auto bytes = std::bit_cast<std::byte *>(&object);
|
||||
for (auto field : getFields()) {
|
||||
field.deserialize(s, bytes + field.offset);
|
||||
if (s.failure()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// all fields are constructable at compile time overload
|
||||
template <detail::SerializableClass T>
|
||||
requires requires {
|
||||
std::index_sequence<detail::StructSerializerBuilder<T>::build().size()>{};
|
||||
}
|
||||
struct TypeSerializer<T> {
|
||||
static constexpr auto fields = detail::StructSerializerBuilder<T>::build();
|
||||
|
||||
static void serialize(Serializer &s, const T &object) {
|
||||
s.serialize<std::uint32_t>(sizeof(object));
|
||||
auto bytes = std::bit_cast<std::byte *>(&object);
|
||||
for (auto field : fields) {
|
||||
field.serialize(s, bytes + field.offset);
|
||||
}
|
||||
}
|
||||
|
||||
static void deserialize(Deserializer &s, T &object) {
|
||||
if (s.deserialize<std::uint32_t>() != sizeof(object)) {
|
||||
s.setFailure();
|
||||
return;
|
||||
}
|
||||
|
||||
auto bytes = std::bit_cast<std::byte *>(&object);
|
||||
for (auto field : fields) {
|
||||
field.deserialize(s, bytes + field.offset);
|
||||
|
||||
if (s.failure()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace rx
|
||||
Loading…
Reference in a new issue