#pragma once #include "util/types.hpp" #include namespace stx { template struct exact_t { T obj; exact_t(T&& _obj) : obj(std::forward(_obj)) {} // TODO: More conversions template requires (std::is_same_v) operator U&() const { return obj; }; }; } namespace utils { template concept FastRandomAccess = requires (T& obj) { std::data(obj)[0]; }; template concept Reservable = requires (T& obj) { obj.reserve(0); }; template concept Bitcopy = (std::is_arithmetic_v) || (std::is_enum_v) || Integral || requires (T& obj) { std::enable_if_t(std::remove_cv_t::enable_bitcopy)>(); }; template concept TupleAlike = requires () { std::tuple_size>::value; }; template concept ListAlike = requires (T& obj) { obj.insert(obj.end(), std::declval()); }; struct serial { std::vector data; usz pos = umax; // Checks if this strcuture is currently used for serialization bool is_writing() const { return pos == umax; } // Reserve memory for serialization void reserve(usz size) { // Is a NO-OP for deserialization in order to allow usage from serialization specializations regardless if (is_writing()) { data.reserve(data.size() + size); } } bool raw_serialize(const void* ptr, usz size) { if (is_writing()) { data.insert(data.end(), static_cast(ptr), static_cast(ptr) + size); return true; } ensure(data.size() - pos >= size); std::memcpy(const_cast(ptr), data.data() + pos, size); pos += size; return true; } template requires Integral bool serialize_vle(T&& value) { for (auto i = value;;) { const auto i_old = std::exchange(i, i >> 7); const u8 to_write = static_cast((static_cast(i_old) % 0x80) | (i ? 0x80 : 0)); raw_serialize(&to_write, 1); if (!i) { break; } } return true; } template requires Integral bool deserialize_vle(T& value) { value = {}; for (u32 i = 0;; i += 7) { u8 byte_data = 0; if (!raw_serialize(&byte_data, 1)) { return false; } value |= static_cast(byte_data % 0x80) << i; if (!(byte_data & 0x80)) { break; } } return true; } // (De)serialization function template bool serialize(T& obj) { // Fallback to global overload return ::serialize(*this, obj); } // Enabled for fundamental types, enumerations and if specfied explicitly that type can be saved in pure bitwise manner template requires Bitcopy bool serialize(T& obj) { return raw_serialize(std::addressof(obj), sizeof(obj)); } // std::vector, std::basic_string template requires FastRandomAccess && ListAlike bool serialize(T& obj) { if (is_writing()) { serialize_vle(obj.size()); if constexpr (Bitcopy::value_type>) { raw_serialize(obj.data(), sizeof(obj[0]) * obj.size()); } else { for (auto&& value : obj) { if (!serialize(value)) { return false; } } } return true; } obj.clear(); usz size = 0; if (!deserialize_vle(size)) { return false; } obj.resize(size); if constexpr (Bitcopy) { if (!raw_serialize(obj.data(), sizeof(obj[0]) * size)) { obj.clear(); return false; } } else { for (auto&& value : obj) { if (!serialize(value)) { obj.clear(); return false; } } } return true; } // C-array, std::array, std::span (span must be supplied with size and address, this function does not modify it) template requires FastRandomAccess && (!ListAlike) && (!Bitcopy) bool serialize(T& obj) { if constexpr (Bitcopy()[0])>>) { return raw_serialize(std::data(obj), sizeof(obj[0]) * std::size(obj)); } else { for (auto&& value : obj) { if (!serialize(value)) { return false; } } return true; } } // std::deque, std::list, std::(unordered_)set, std::(unordered_)map, std::(unordered_)multiset, std::(unordered_)multimap template requires (!FastRandomAccess) && ListAlike bool serialize(T& obj) { if (is_writing()) { serialize_vle(obj.size()); for (auto&& value : obj) { if (!serialize(value)) { return false; } } return true; } obj.clear(); usz size = 0; if (!deserialize_vle(size)) { return false; } if constexpr (Reservable) { obj.reserve(size); } for (usz i = 0; i < size; i++) { obj.insert(obj.end(), static_cast(*this)); if (!is_valid()) { obj.clear(); return false; } } return true; } template bool serialize_tuple(T& obj) { const bool res = serialize(std::get(obj)); constexpr usz next_i = std::min(i + 1, std::tuple_size_v - 1); if constexpr (next_i == i) { return res; } else { return res && serialize_tuple(obj); } } // std::pair, std::tuple template requires TupleAlike && (!FastRandomAccess) bool serialize(T& obj) { return serialize_tuple(obj); } // Wrapper for serialize(T&), allows to pass multiple objects at once template bool operator()(Args&&... args) { return ((AUDIT(!std::is_const_v> || is_writing()) , serialize(const_cast&>(static_cast(args)))), ...); } // Convert serialization manager to deserializion manager (can't go the other way) // If no arg provided reuse saved buffer void set_reading_state(std::vector&& _data = std::vector{}) { if (!_data.empty()) { data = std::move(_data); } pos = 0; } template requires (std::is_copy_constructible_v>) && (std::is_constructible_v> || Bitcopy> || std::is_constructible_v, stx::exact_t> || TupleAlike>) operator T() { AUDIT(!is_writing()); using type = std::remove_const_t; if constexpr (Bitcopy) { u8 buf[sizeof(type)]{}; ensure(raw_serialize(buf, sizeof(buf))); return std::bit_cast(buf); } else if constexpr (std::is_constructible_v>) { return type(stx::exact_t(*this)); } else if constexpr (std::is_constructible_v) { type value{}; ensure(serialize(value)); return value; } else if constexpr (TupleAlike) { static_assert(std::tuple_size_v == 2, "Unimplemented tuple serialization!"); return type{ operator std::remove_cvref_t(std::declval()))> , operator std::remove_cvref_t(std::declval()))> }; } } // Returns true if writable or readable and valid bool is_valid() const { return is_writing() || pos < data.size(); } }; }