220 lines
		
	
	
	
		
			8.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			220 lines
		
	
	
	
		
			8.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Licensed under the Boost License <https://opensource.org/licenses/BSL-1.0>.
 | |
| // SPDX-License-Identifier: BSL-1.0
 | |
| #pragma once
 | |
| #include <array>
 | |
| #include <ostream>
 | |
| #include <cstring>
 | |
| #include <string>
 | |
| #include <string_view>
 | |
| #include <type_traits>
 | |
| #include <tuple>
 | |
| #include <locale>
 | |
| #include <codecvt>
 | |
| 
 | |
| 
 | |
| namespace tser{
 | |
|   //implementation details for C++20 is_detected
 | |
|   namespace detail {
 | |
|     struct ns {
 | |
|       ~ns() = delete;
 | |
|       ns(ns const&) = delete;
 | |
|     };
 | |
| 
 | |
|     template <class Default, class AlwaysVoid, template<class...> class Op, class... Args>
 | |
|       struct detector {
 | |
|         using value_t = std::false_type;
 | |
|         using type = Default;
 | |
|       };
 | |
| 
 | |
|     template <class Default, template<class...> class Op, class... Args>
 | |
|       struct detector<Default, std::void_t<Op<Args...>>, Op, Args...> {
 | |
|         using value_t = std::true_type;
 | |
|         using type = Op<Args...>;
 | |
|       };
 | |
| 
 | |
|     template<class T>
 | |
|       struct is_array : std::is_array<T> {};
 | |
| 
 | |
|     template<template<typename, size_t> class TArray, typename T, size_t N>
 | |
|       struct is_array<TArray<T, N>> : 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 <template<class...> class Op, class... Args>
 | |
|     constexpr bool is_detected_v = detail::detector<detail::ns, void, Op, Args...>::value_t::value;
 | |
| 
 | |
|   class BinaryArchive;
 | |
|   template<class T> using has_begin_t = decltype(*std::begin(std::declval<T>()));
 | |
| 
 | |
|   template<class T> using has_members_t = decltype(std::declval<T>().members());
 | |
| 
 | |
|   template<class T> using has_smaller_t = decltype(std::declval<T>() < std::declval<T>());
 | |
| 
 | |
|   template<class T> using has_equal_t = decltype(std::declval<T>() == std::declval<T>());
 | |
| 
 | |
|   template<class T> using has_nequal_t = decltype(std::declval<T>() != std::declval<T>());
 | |
| 
 | |
|   template<class T> using has_outstream_op_t = decltype(std::declval<std::ostream>() << std::declval<T>());
 | |
| 
 | |
|   template<class T> using has_tuple_t = std::tuple_element_t<0, T>;
 | |
| 
 | |
|   template<class T> using has_optional_t = decltype(std::declval<T>().has_value());
 | |
| 
 | |
|   template<class T> using has_element_t = typename T::element_type;
 | |
| 
 | |
|   template<class T> using has_mapped_t = typename T::mapped_type;
 | |
| 
 | |
|   template<class T> using has_custom_save_t = decltype(std::declval<T>().save(std::declval<BinaryArchive&>()));
 | |
| 
 | |
|   template<class T> using has_free_save_t = decltype(std::declval<const T&>() << std::declval<BinaryArchive&>());
 | |
| 
 | |
|   template<class T> constexpr bool is_container_v = is_detected_v<has_begin_t, T>;
 | |
| 
 | |
|   template<class T> constexpr bool is_tuple_v = is_detected_v<has_tuple_t, T>;
 | |
| 
 | |
|   template<class T> constexpr bool is_tser_t_v = is_detected_v<has_members_t, T>;
 | |
| 
 | |
|   template<class T> constexpr bool is_pointer_like_v = std::is_pointer_v<T> || is_detected_v<has_element_t, T> || is_detected_v<has_optional_t, T>;
 | |
| 
 | |
|   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<typename T>
 | |
|       explicit BinaryArchive(const T& t) { save(t); }
 | |
| 
 | |
|     template<typename T>
 | |
|       void save(const T& t) {
 | |
|         if constexpr (is_detected_v<has_free_save_t, T>) {
 | |
|           operator<<(t,*this);
 | |
|         } else if constexpr (is_detected_v<has_custom_save_t, T>) {
 | |
|           t.save(*this);
 | |
|         } else if constexpr(is_tser_t_v<T>) {
 | |
|           std::apply([&](auto& ... mVal) { (save(mVal), ...); }, t.members());
 | |
|         } else if constexpr(is_tuple_v<T>) {
 | |
|           std::apply([&](auto& ... tVal) { (save(tVal), ...); }, t);
 | |
|         } else if constexpr (is_pointer_like_v<T>) {
 | |
|           save(static_cast<bool>(t));
 | |
|           if (t)
 | |
|             save(*t);
 | |
|         } else if constexpr (is_container_v<T>) {
 | |
|           if constexpr (!detail::is_array<T>::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<typename T>
 | |
|       void load(T& t) {
 | |
|         using V = std::decay_t<T>;
 | |
|         if constexpr (is_detected_v<has_free_save_t, V>) {
 | |
|           operator>>(t, *this);
 | |
|         } else if constexpr (is_detected_v<has_custom_save_t, T>) {
 | |
|           t.load(*this);
 | |
|         } else if constexpr (is_tser_t_v<T>) {
 | |
|           std::apply([&](auto& ... mVal) { (load(mVal), ...); }, t.members());
 | |
|         } else if constexpr (is_tuple_v<V>) {
 | |
|           std::apply([&](auto& ... tVal) { (load(tVal), ...); }, t);
 | |
|         } else if constexpr (is_pointer_like_v<T>) {
 | |
|           if constexpr (std::is_pointer_v<T>) {
 | |
|             t = load<bool>() ? (t = new std::remove_pointer_t<T>(), load(*t), t) : nullptr;
 | |
|           } else if constexpr (is_detected_v<has_optional_t, T>) {
 | |
|             t = load<bool>() ? T(load<typename V::value_type>()) : T();
 | |
|           } else { //smart pointer
 | |
|             t = T(load<has_element_t<V>*>());
 | |
|           }
 | |
|         } else if constexpr (is_container_v<T>) {
 | |
|           if constexpr (!detail::is_array<T>::value) {
 | |
|             const auto size = load<decltype(t.size())>();
 | |
|             using VT = typename V::value_type;
 | |
|             for (size_t i = 0; i < size; ++i) {
 | |
|               if constexpr (!is_detected_v<has_mapped_t, V>) {
 | |
|                 t.insert(t.end(), load<VT>());
 | |
|               } else {
 | |
|                 //we have to special case map, because of the const key
 | |
|                 t.emplace(VT{ load<typename V::key_type>(), load<typename V::mapped_type>() });
 | |
|               }
 | |
|             }
 | |
|           } else {
 | |
|             for (auto& val : t) load(val);
 | |
|           }
 | |
|         } else {
 | |
|           std::memcpy(&t, m_bytes.data() + m_readOffset, sizeof(T));
 | |
|           m_readOffset += sizeof(T);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|     template<typename T>
 | |
|       T load() {
 | |
|         std::remove_const_t<T> t{}; load(t); return t;
 | |
|       }
 | |
| 
 | |
|     template<typename T>
 | |
|       friend BinaryArchive& operator<<(BinaryArchive& ba, const T& t) {
 | |
|         ba.save(t); return ba;
 | |
|       }
 | |
| 
 | |
|     template<typename T>
 | |
|       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<class Base, typename Derived>
 | |
|     std::conditional_t<std::is_const_v<Derived>, const Base, Base>& base(Derived* thisPtr) { return *thisPtr; }
 | |
| 
 | |
|   template<typename T>
 | |
|     auto load(std::string_view encoded) { BinaryArchive ba(encoded); return ba.load<T>(); }
 | |
| }
 | |
| 
 | |
| //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<char, tser::detail::str_size(#__VA_ARGS__)> _memberNameData = [](){ \
 | |
| std::array<char, tser::detail::str_size(#__VA_ARGS__)> 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<const char*, tser::detail::n_args(#__VA_ARGS__)> _memberNames = \
 | |
| [](){ std::array<const char*, tser::detail::n_args(#__VA_ARGS__)> 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;}();
 | 
