From bf57713416eacf512ce05d240917f07e55a1b5aa Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Mon, 4 Nov 2024 09:10:27 -0500 Subject: [PATCH] Stripped tser.hpp down to the essentials so I can study it. No base64 encoding, less than comparison (wtf is that for), and I may even remove the 'json' output. --- tests/config.cpp | 41 +++++++- tser.hpp | 266 +++++++++++++++++++++++++++++++++++++++++++++++ tser.wrap | 11 ++ 3 files changed, 317 insertions(+), 1 deletion(-) create mode 100644 tser.hpp create mode 100644 tser.wrap diff --git a/tests/config.cpp b/tests/config.cpp index b4881b7..7d97c29 100644 --- a/tests/config.cpp +++ b/tests/config.cpp @@ -75,7 +75,9 @@ struct MyData std::string tiles; template - serialize(Archive &ar) { ar(x, y, z, tiles); } + void serialize(Archive &ar) { + ar(x, y, z, tiles); + } }; @@ -104,3 +106,40 @@ TEST_CASE("test using serialization", "[config]") { REQUIRE(m3.tiles == "\u2849█Ω♣"); } } + +#include +#include +#include "tser.hpp" + +enum class Item : char { + RADAR = 'R', + TRAP = 'T', + ORE = 'O' +}; + +struct Pixel { + int x = 0; + int y = 0; + + DEFINE_SERIALIZABLE(Pixel, x, y); +}; + +struct Robot { + Pixel point; + std::optional item; + + DEFINE_SERIALIZABLE(Robot, point, item); +}; + +TEST_CASE("test using tser for serialization", "[config]") { + auto robot = Robot{ Pixel{3,4}, Item::RADAR}; + std::cout << robot << '\n'; + std::cout << Robot() << '\n'; + + tser::BinaryArchive archive; + archive.save(robot); + std::string_view archive_view = archive.get_buffer(); + + auto loadedRobot = tser::load(archive_view); + REQUIRE(loadedRobot == robot); +} diff --git a/tser.hpp b/tser.hpp new file mode 100644 index 0000000..bd555c0 --- /dev/null +++ b/tser.hpp @@ -0,0 +1,266 @@ +// Licensed under the Boost License . +// SPDX-License-Identifier: BSL-1.0 +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace tser{ + //implementation details for C++20 is_detected + namespace detail { + struct ns { + ~ns() = delete; + ns(ns const&) = delete; + }; + + template class Op, class... Args> + struct detector { + using value_t = std::false_type; + using type = Default; + }; + + template class Op, class... Args> + struct detector>, Op, Args...> { + using value_t = std::true_type; + using type = Op; + }; + + template + struct is_array : std::is_array {}; + + template class TArray, typename T, size_t N> + struct is_array> : std::true_type {}; + + constexpr size_t n_args(char const* c, size_t nargs = 1) { + for (; *c; ++c) if (*c == ',') ++nargs; + return nargs; + } + + constexpr size_t str_size(char const* c, size_t strSize = 1) { + for (; *c; ++c) ++strSize; + return strSize; + } + } + + // we need a bunch of template metaprogramming for being able to differentiate between different types + template class Op, class... Args> + constexpr bool is_detected_v = detail::detector::value_t::value; + + class BinaryArchive; + template using has_begin_t = decltype(*std::begin(std::declval())); + + template using has_members_t = decltype(std::declval().members()); + + template using has_smaller_t = decltype(std::declval() < std::declval()); + + template using has_equal_t = decltype(std::declval() == std::declval()); + + template using has_nequal_t = decltype(std::declval() != std::declval()); + + template using has_outstream_op_t = decltype(std::declval() << std::declval()); + + template using has_tuple_t = std::tuple_element_t<0, T>; + + template using has_optional_t = decltype(std::declval().has_value()); + + template using has_element_t = typename T::element_type; + + template using has_mapped_t = typename T::mapped_type; + + template using has_custom_save_t = decltype(std::declval().save(std::declval())); + + template using has_free_save_t = decltype(std::declval() << std::declval()); + + template constexpr bool is_container_v = is_detected_v; + + template constexpr bool is_tuple_v = is_detected_v; + + template constexpr bool is_tser_t_v = is_detected_v; + + template constexpr bool is_pointer_like_v = std::is_pointer_v || is_detected_v || is_detected_v; + + //implementation of the recursive json printing + template + constexpr inline decltype(auto) print(std::ostream& os, T&& val) { + using V = std::decay_t; + if constexpr (std::is_constructible_v || std::is_same_v) { + os << "\"" << val << "\""; + } else if constexpr (is_container_v) { + size_t i = 0; + os << "\n["; + + for (auto& elem : val) { + os << (i++ == 0 ? "" : ",") << tser::print(os, elem); + } + + os << "]\n"; + } else if constexpr (is_tser_t_v && !is_detected_v) { + + auto pMem = [&](auto& ... memberVal) { + size_t i = 0; + (((os << (i != 0 ? ", " : "") << '\"'), os << V::_memberNames[i++] << "\" : " << tser::print(os, memberVal)), ...); + }; + + os << "{ \"" << V::_typeName << "\": {"; std::apply(pMem, val.members()); os << "}}\n"; + + } else if constexpr (std::is_enum_v &&! is_detected_v) { + os << tser::print(os, static_cast>(val)); + } else if constexpr (is_tuple_v && !is_detected_v) { + std::apply([&](auto& ... t) { + int i = 0; + os << "{"; + (((i++ != 0 ? os << ", " : os), + tser::print(os, t)), ...); // WTF + os << "}"; + }, val); + } else if constexpr (is_pointer_like_v) { + os << (val ? (os << (tser::print(os, *val)), "") : "null"); + } else { + os << val; + } + + return ""; + } + + class BinaryArchive { + std::string m_bytes = std::string(1024, '\0'); + size_t m_bufferSize = 0, m_readOffset = 0; + + public: + explicit BinaryArchive(const size_t initialSize = 1024) : m_bytes(initialSize, '\0') {} + + template + explicit BinaryArchive(const T& t) { save(t); } + + template + void save(const T& t) { + if constexpr (is_detected_v) { + operator<<(t,*this); + } else if constexpr (is_detected_v) { + t.save(*this); + } else if constexpr(is_tser_t_v) { + std::apply([&](auto& ... mVal) { (save(mVal), ...); }, t.members()); + } else if constexpr(is_tuple_v) { + std::apply([&](auto& ... tVal) { (save(tVal), ...); }, t); + } else if constexpr (is_pointer_like_v) { + save(static_cast(t)); + if (t) + save(*t); + } else if constexpr (is_container_v) { + if constexpr (!detail::is_array::value) { + save(t.size()); + } + + for (auto& val : t) save(val); + } else { + if (m_bufferSize + sizeof(T) + sizeof(T) / 4 > m_bytes.size()) { + m_bytes.resize((m_bufferSize + sizeof(T)) * 2); + } + + std::memcpy(m_bytes.data() + m_bufferSize, std::addressof(t), sizeof(T)); + m_bufferSize += sizeof(T); + } + } + + template + void load(T& t) { + using V = std::decay_t; + if constexpr (is_detected_v) { + operator>>(t, *this); + } else if constexpr (is_detected_v) { + t.load(*this); + } else if constexpr (is_tser_t_v) { + std::apply([&](auto& ... mVal) { (load(mVal), ...); }, t.members()); + } else if constexpr (is_tuple_v) { + std::apply([&](auto& ... tVal) { (load(tVal), ...); }, t); + } else if constexpr (is_pointer_like_v) { + if constexpr (std::is_pointer_v) { + t = load() ? (t = new std::remove_pointer_t(), load(*t), t) : nullptr; + } else if constexpr (is_detected_v) { + t = load() ? T(load()) : T(); + } else { //smart pointer + t = T(load*>()); + } + } else if constexpr (is_container_v) { + if constexpr (!detail::is_array::value) { + const auto size = load(); + using VT = typename V::value_type; + for (size_t i = 0; i < size; ++i) { + if constexpr (!is_detected_v) { + t.insert(t.end(), load()); + } else { + //we have to special case map, because of the const key + t.emplace(VT{ load(), load() }); + } + } + } else { + for (auto& val : t) load(val); + } + } else { + std::memcpy(&t, m_bytes.data() + m_readOffset, sizeof(T)); + m_readOffset += sizeof(T); + } + } + + template + T load() { + std::remove_const_t t{}; load(t); return t; + } + + template + friend BinaryArchive& operator<<(BinaryArchive& ba, const T& t) { + ba.save(t); return ba; + } + + template + friend BinaryArchive& operator>>(BinaryArchive& ba, T& t) { + ba.load(t); return ba; + } + + void reset() { + m_bufferSize = 0; + m_readOffset = 0; + } + + void initialize(std::string_view str) { + m_bytes = str; + m_bufferSize = str.size(); + m_readOffset = 0; + } + + std::string_view get_buffer() const { + return std::string_view(m_bytes.data(), m_bufferSize); + } + + }; + + template + std::conditional_t, const Base, Base>& base(Derived* thisPtr) { return *thisPtr; } + + template + auto load(std::string_view encoded) { BinaryArchive ba(encoded); return ba.load(); } +} + +//this macro defines printing, serialisation and comparision operators (==,!=,<) for custom types +#define DEFINE_SERIALIZABLE(Type, ...) \ +inline decltype(auto) members() const { return std::tie(__VA_ARGS__); } \ +inline decltype(auto) members() { return std::tie(__VA_ARGS__); } \ +static constexpr std::array _memberNameData = [](){ \ +std::array chars{'\0'}; size_t _idx = 0; constexpr auto* ini(#__VA_ARGS__); \ +for (char const* _c = ini; *_c; ++_c, ++_idx) if(*_c != ',' && *_c != ' ') chars[_idx] = *_c; return chars;}(); \ +static constexpr const char* _typeName = #Type; \ +static constexpr std::array _memberNames = \ +[](){ std::array out{ }; \ +for(size_t _i = 0, nArgs = 0; nArgs < tser::detail::n_args(#__VA_ARGS__) ; ++_i) { \ +while(Type::_memberNameData[_i] == '\0') _i++; out[nArgs++] = &Type::_memberNameData[_i]; \ +while(Type::_memberNameData[++_i] != '\0'); } return out;}();\ +template && !tser::is_detected_v, int> = 0>\ +friend bool operator==(const Type& lhs, const OT& rhs) { return lhs.members() == rhs.members(); }\ +template && !tser::is_detected_v, int> = 0>\ +friend bool operator!=(const Type& lhs, const OT& rhs) { return !(lhs == rhs); }\ +template && !tser::is_detected_v, int> = 0>\ +friend std::ostream& operator<<(std::ostream& os, const OT& t) { tser::print(os, t); return os; } diff --git a/tser.wrap b/tser.wrap new file mode 100644 index 0000000..85a3c70 --- /dev/null +++ b/tser.wrap @@ -0,0 +1,11 @@ +[wrap-git] +url = https://github.com/KonanM/tser.git +depth = 1 +revision = HEAD +method = cmake +# patch_filename = +# patch_hash = + + +[provide] +tser = tser_dep