diff --git a/include/ylt/struct_pack/calculate_size.hpp b/include/ylt/struct_pack/calculate_size.hpp index a89f3d1ff..93124b62e 100644 --- a/include/ylt/struct_pack/calculate_size.hpp +++ b/include/ylt/struct_pack/calculate_size.hpp @@ -45,6 +45,9 @@ constexpr size_info inline calculate_one_size(const T &item) { size_info ret{}; if constexpr (id == type_id::monostate_t) { } + else if constexpr (id == type_id::user_defined_type) { + ret.total = sp_get_needed_size(item); + } else if constexpr (detail::varint_t) { if constexpr (is_enable_fast_varint_coding(parent_tag)) { // skip it. It has been calculated in parent. @@ -105,7 +108,7 @@ constexpr size_info inline calculate_one_size(const T &item) { if (item) { if constexpr (is_base_class) { ret.total += sizeof(uint32_t); - bool is_ok; + bool is_ok = false; auto index = search_type_by_md5( item->get_struct_pack_id(), is_ok); if SP_UNLIKELY (!is_ok) { diff --git a/include/ylt/struct_pack/endian_wrapper.hpp b/include/ylt/struct_pack/endian_wrapper.hpp index 0b4037cf5..578df95e5 100644 --- a/include/ylt/struct_pack/endian_wrapper.hpp +++ b/include/ylt/struct_pack/endian_wrapper.hpp @@ -19,10 +19,12 @@ #include #include "reflection.hpp" +#include "ylt/struct_pack/error_code.hpp" #include "ylt/struct_pack/marco.h" #include "ylt/struct_pack/util.h" -namespace struct_pack::detail { +namespace struct_pack { +namespace detail { #if __cpp_lib_endian >= 201907L constexpr inline bool is_system_little_endian = (std::endian::little == std::endian::native); @@ -132,7 +134,8 @@ inline uint64_t bswap64(uint64_t raw) { }; template -void write_wrapper(writer_t& writer, const char* SP_RESTRICT data) { +STRUCT_PACK_INLINE void write_wrapper(writer_t& writer, + const char* SP_RESTRICT data) { if constexpr (is_system_little_endian || block_size == 1) { writer.write(data, block_size); } @@ -159,14 +162,16 @@ void write_wrapper(writer_t& writer, const char* SP_RESTRICT data) { } } template -void write_bytes_array(writer_t& writer, const char* data, std::size_t length) { +STRUCT_PACK_INLINE void write_bytes_array(writer_t& writer, const char* data, + std::size_t length) { if SP_UNLIKELY (length >= PTRDIFF_MAX) unreachable(); else writer.write(data, length); } template -void low_bytes_write_wrapper(writer_t& writer, const T& elem) { +STRUCT_PACK_INLINE void low_bytes_write_wrapper(writer_t& writer, + const T& elem) { static_assert(sizeof(T) >= block_size); if constexpr (is_system_little_endian) { const char* data = (const char*)&elem; @@ -194,7 +199,7 @@ void low_bytes_write_wrapper(writer_t& writer, const T& elem) { } } template -bool read_wrapper(reader_t& reader, char* SP_RESTRICT data) { +STRUCT_PACK_INLINE bool read_wrapper(reader_t& reader, char* SP_RESTRICT data) { if constexpr (is_system_little_endian || block_size == 1) { return static_cast(reader.read(data, block_size)); } @@ -225,12 +230,13 @@ bool read_wrapper(reader_t& reader, char* SP_RESTRICT data) { } } template -bool read_bytes_array(reader_t& reader, char* SP_RESTRICT data, - std::size_t length) { +STRUCT_PACK_INLINE bool read_bytes_array(reader_t& reader, + char* SP_RESTRICT data, + std::size_t length) { return static_cast(reader.read(data, length)); } template -bool low_bytes_read_wrapper(reader_t& reader, T& elem) { +STRUCT_PACK_INLINE bool low_bytes_read_wrapper(reader_t& reader, T& elem) { static_assert(sizeof(T) >= block_size); if constexpr (is_system_little_endian) { char* data = (char*)&elem; @@ -263,4 +269,170 @@ bool low_bytes_read_wrapper(reader_t& reader, T& elem) { } } } -}; // namespace struct_pack::detail \ No newline at end of file +} // namespace detail +template +STRUCT_PACK_INLINE void write(Writer& writer, const T& t) { + if constexpr (std::is_fundamental_v) { + detail::write_wrapper(writer, (const char*)&t); + } + else if constexpr (detail::array) { + if constexpr (detail::is_little_endian_copyable && + std::is_fundamental_v) { + writer_bytes_array(writer, (const char*)&t.data(), sizeof(T)); + } + else { + for (auto& e : t) write(writer, e); + } + } + else if constexpr (detail::string || detail::container) { + std::uint64_t len = t.size(); + detail::write_wrapper(writer, (char*)&len); + if constexpr (detail::continuous_container && + detail::is_little_endian_copyable) { + writer_bytes_array(writer, (const char*)&t.data(), len * sizeof(t[0])); + } + else { + for (auto& e : t) write(writer, e); + } + } + else { + static_assert(!sizeof(T), "not support type"); + } +} +template +STRUCT_PACK_INLINE void write(Writer& writer, const T* t, std::size_t length) { + if constexpr (std::is_fundamental_v) { + if constexpr (detail::is_little_endian_copyable) { + write_bytes_array(writer, (const char*)t, sizeof(T) * length); + } + else { + for (std::size_t i = 0; i < length; ++i) write(writer, t[i]); + } + } + else { + static_assert(!sizeof(T), "not support type"); + } +} +template +STRUCT_PACK_INLINE constexpr std::size_t get_write_size(const T& t) { + if constexpr (std::is_fundamental_v) { + return sizeof(T); + } + else if constexpr (detail::array) { + if constexpr (std::is_fundamental_v) { + return sizeof(T); + } + else { + std::size_t ret = 0; + for (auto& e : t) ret += get_write_size(e); + return ret; + } + } + else if constexpr (detail::string || detail::container) { + std::size_t ret = 8; + if constexpr (detail::continuous_container && + detail::is_little_endian_copyable) { + ret += t.size() * sizeof(t[0]); + } + else { + for (auto& e : t) ret += write(e); + } + return ret; + } + else { + static_assert(!sizeof(T), "not support type"); + } +} +template +STRUCT_PACK_INLINE constexpr std::size_t get_write_size(const T* t, + std::size_t length) { + return sizeof(T) * length; +} +template +STRUCT_PACK_INLINE struct_pack::errc read(Reader& reader, T& t) { + if constexpr (std::is_fundamental_v) { + if (!detail::read_wrapper(reader, (char*)&t)) { + return struct_pack::errc::no_buffer_space; + } + else { + return {}; + } + } + else if constexpr (detail::array) { + if constexpr (std::is_fundamental_v && + detail::is_little_endian_copyable) { + return read_bytes_array(reader, (char*)&t.data(), sizeof(T)); + } + else { + struct_pack::errc ec; + for (auto& e : t) { + ec = read(reader, e); + if SP_UNLIKELY (ec != struct_pack::errc{}) { + return ec; + } + } + return struct_pack::errc{}; + } + } + else if constexpr (detail::string || detail::container) { + std::uint64_t sz; + auto ec = read(reader, sz); + if SP_UNLIKELY (ec != struct_pack::errc{}) { + return ec; + } + if constexpr (detail::continuous_container && + std::is_fundamental_v && + detail::is_little_endian_copyable && + checkable_reader_t) { + if SP_UNLIKELY (sz > UINT64_MAX / sizeof(t[0]) || sz > SIZE_MAX) { + return struct_pack::errc::invalid_buffer; + } + std::size_t mem_size = sz * sizeof(t[0]); + if SP_UNLIKELY (!reader.check(mem_size)) { + return struct_pack::errc::no_buffer_space; + } + detail::resize(t, mem_size); + return read_bytes_array(reader, (char*)&t.data(), mem_size); + } + else { + for (std::size_t i = 0; i < sz; ++i) { + t.emplace_back(); + ec = read(reader, t.back()); + if SP_UNLIKELY (ec != struct_pack::errc{}) { + return ec; + } + } + return struct_pack::errc{}; + } + } + else { + static_assert(!sizeof(T), "not support type"); + } +} +template +struct_pack::errc read(Reader& reader, T* t, std::size_t length) { + if constexpr (std::is_fundamental_v) { + if constexpr (detail::is_little_endian_copyable) { + if (!read_bytes_array(reader, (char*)t, sizeof(T) * length)) { + return struct_pack::errc::no_buffer_space; + } + else { + return {}; + } + } + else { + struct_pack::errc ec{}; + for (std::size_t i = 0; i < length; ++i) { + ec = read(reader, t[i]); + if SP_UNLIKELY (ec != struct_pack::errc{}) { + return ec; + } + }; + return ec; + } + } + else { + static_assert(!sizeof(T), "not support type"); + } +} +}; // namespace struct_pack \ No newline at end of file diff --git a/include/ylt/struct_pack/md5_constexpr.hpp b/include/ylt/struct_pack/md5_constexpr.hpp index 40e328b97..cf353f825 100644 --- a/include/ylt/struct_pack/md5_constexpr.hpp +++ b/include/ylt/struct_pack/md5_constexpr.hpp @@ -27,7 +27,12 @@ namespace struct_pack { template struct string_literal { constexpr string_literal() = default; - + constexpr string_literal(std::string_view str) : ar{} { + for (size_t i = 0; i < Size; ++i) { + ar[i] = str[i]; + } + ar[Size] = '\0'; + } constexpr string_literal(const CharType (&value)[Size + 1]) : ar{} { for (size_t i = 0; i <= Size; ++i) { ar[i] = value[i]; diff --git a/include/ylt/struct_pack/packer.hpp b/include/ylt/struct_pack/packer.hpp index c3d996dde..7ee110b52 100644 --- a/include/ylt/struct_pack/packer.hpp +++ b/include/ylt/struct_pack/packer.hpp @@ -22,6 +22,7 @@ #include "calculate_size.hpp" #include "endian_wrapper.hpp" #include "reflection.hpp" +#include "ylt/struct_pack/type_id.hpp" #include "ylt/struct_pack/util.h" #include "ylt/struct_pack/varint.hpp" namespace struct_pack::detail { @@ -248,6 +249,9 @@ class packer { else if constexpr (std::is_same_v) { // do nothing } + else if constexpr (id == type_id::user_defined_type) { + sp_serialize_to(writer_, item); + } else if constexpr (detail::varint_t) { if constexpr (is_enable_fast_varint_coding(parent_tag)) { // do nothing diff --git a/include/ylt/struct_pack/reflection.hpp b/include/ylt/struct_pack/reflection.hpp index e45719dbb..482e0135e 100644 --- a/include/ylt/struct_pack/reflection.hpp +++ b/include/ylt/struct_pack/reflection.hpp @@ -36,6 +36,10 @@ #include "marco.h" #include "util.h" +#if __cpp_concepts >= 201907L +#include +#endif + namespace struct_pack { enum sp_config : uint64_t { @@ -146,11 +150,6 @@ constexpr bool view_reader_t = reader_t &&view_reader_t_impl::value; #if __cpp_concepts >= 201907L -template -concept check_reader_t = reader_t && requires(T t) { - t.check(std::size_t{}); -}; - template concept can_reserve = requires(T t) { t.reserve(std::size_t{}); @@ -163,17 +162,6 @@ concept can_shrink_to_fit = requires(T t) { #else -template -struct check_reader_t_impl : std::false_type {}; - -template -struct check_reader_t_impl< - T, std::void_t().check(std::size_t{}))>> - : std::true_type {}; - -template -constexpr bool check_reader_t = reader_t &&check_reader_t_impl::value; - template struct can_reserve_impl : std::false_type {}; @@ -614,6 +602,46 @@ template constexpr bool user_defined_config = user_defined_config_impl::value; #endif +struct memory_reader; + +#if __cpp_concepts >= 201907L + template + concept user_defined_serialization = requires (Type& t) { + sp_serialize_to(std::declval(),(const Type&)t); + {sp_deserialize_to(std::declval(),t)} -> std::same_as; + {sp_get_needed_size((const Type&)t)}->std::same_as; + }; + template + concept user_defined_type_name = requires { + { sp_set_type_name((Type*)nullptr) } -> std::same_as; + }; +#else + + template + struct user_defined_serialization_impl : std::false_type {}; + + template + struct user_defined_serialization_impl(),std::declval())), + std::enable_if(),std::declval())), struct_pack::errc>, + std::enable_if())), std::string_view>>>>> + : std::true_type {}; + + template + constexpr bool user_defined_serialization = user_defined_serialization_impl::value; + + template + struct user_defined_type_name_impl : std::false_type {}; + + template + struct user_defined_type_name_impl>>> + : std::true_type {}; + + template + constexpr bool user_defined_type_name = user_defined_type_name_impl::value; +#endif + #if __cpp_concepts >= 201907L template concept tuple_size = requires(Type tuple) { @@ -810,7 +838,10 @@ template ...); } static constexpr bool solve() { - if constexpr (std::is_same_v) { + if constexpr (user_defined_serialization) { + return false; + } + else if constexpr (std::is_same_v) { return true; } else if constexpr (std::is_abstract_v) { @@ -2218,6 +2249,26 @@ constexpr decltype(auto) STRUCT_PACK_INLINE template_switch(std::size_t index, } } // namespace detail } // namespace detail +#if __cpp_concepts >= 201907L + +template +concept checkable_reader_t = reader_t && requires(T t) { + t.check(std::size_t{}); +}; + +#else + +template +struct checkable_reader_t_impl : std::false_type {}; + +template +struct checkable_reader_t_impl< + T, std::void_t().check(std::size_t{}))>> + : std::true_type {}; + +template +constexpr bool checkable_reader_t = reader_t &&checkable_reader_t_impl::value; +#endif } // namespace struct_pack // clang-format off diff --git a/include/ylt/struct_pack/type_calculate.hpp b/include/ylt/struct_pack/type_calculate.hpp index 86c93d23c..3991bd857 100644 --- a/include/ylt/struct_pack/type_calculate.hpp +++ b/include/ylt/struct_pack/type_calculate.hpp @@ -187,6 +187,22 @@ constexpr decltype(auto) get_type_literal() { if constexpr (is_trivial_view_v) { return get_type_literal(); } + else if constexpr (user_defined_serialization) { + constexpr auto begin = string_literal{ + {static_cast(type_id::user_defined_type)}}; + constexpr auto end = + string_literal{{static_cast(type_id::type_end_flag)}}; + if constexpr (user_defined_type_name) { + constexpr auto type_name = sp_set_type_name((Arg *)nullptr); + string_literal ret{type_name}; + return begin + ret + end; + } + else { + constexpr auto type_name = type_string(); + string_literal ret{type_name}; + return begin + ret + end; + } + } else { constexpr std::size_t has_cycle = check_circle(); if constexpr (has_cycle != 0) { @@ -307,6 +323,22 @@ constexpr decltype(auto) get_types_literal() { if constexpr (is_trivial_view_v) { return get_types_literal(); } + else if constexpr (user_defined_serialization) { + constexpr auto begin = string_literal{ + {static_cast(type_id::user_defined_type)}}; + constexpr auto end = + string_literal{{static_cast(type_id::type_end_flag)}}; + if constexpr (user_defined_type_name) { + constexpr auto type_name = sp_set_type_name((T *)nullptr); + string_literal ret{type_name}; + return begin + ret + end; + } + else { + constexpr auto type_name = type_string(); + string_literal ret{type_name}; + return begin + ret + end; + } + } else { constexpr auto root_id = get_type_id>(); if constexpr (root_id == type_id::struct_t) { @@ -835,13 +867,17 @@ constexpr bool check_has_metainfo() { template constexpr auto get_types() { using T = remove_cvref_t; - if constexpr (std::is_fundamental_v || std::is_enum_v || varint_t || - string || container || optional || unique_ptr || - is_variant_v || expected || array || c_array || - std::is_same_v || bitset + if constexpr (user_defined_serialization) { + return declval>(); + } + else if constexpr (std::is_fundamental_v || std::is_enum_v || + varint_t || string || container || optional || + unique_ptr || is_variant_v || expected || + array || c_array || + std::is_same_v || bitset #if (__GNUC__ || __clang__) && defined(STRUCT_PACK_ENABLE_INT128) - || std::is_same_v<__int128, T> || - std::is_same_v + || std::is_same_v<__int128, T> || + std::is_same_v #endif ) { return declval>(); diff --git a/include/ylt/struct_pack/type_id.hpp b/include/ylt/struct_pack/type_id.hpp index a25701774..a518b4629 100644 --- a/include/ylt/struct_pack/type_id.hpp +++ b/include/ylt/struct_pack/type_id.hpp @@ -66,6 +66,8 @@ enum class type_id { expected_t, bitset_t, polymorphic_unique_ptr_t, + // flag for user-defined type + user_defined_type = 249, // monostate, or void monostate_t = 250, // circle_flag @@ -274,7 +276,10 @@ template constexpr type_id get_type_id() { static_assert(CHAR_BIT == 8); // compatible member, which should be ignored in MD5 calculated. - if constexpr (optional && is_compatible_v) { + if constexpr (user_defined_serialization) { + return type_id::user_defined_type; + } + else if constexpr (optional && is_compatible_v) { return type_id::compatible_t; } else if constexpr (detail::varint_t) { diff --git a/include/ylt/struct_pack/unpacker.hpp b/include/ylt/struct_pack/unpacker.hpp index a2d92e5f8..751b3df6d 100644 --- a/include/ylt/struct_pack/unpacker.hpp +++ b/include/ylt/struct_pack/unpacker.hpp @@ -778,6 +778,14 @@ class unpacker { else if constexpr (std::is_same_v) { // do nothing } + else if constexpr (id == type_id::user_defined_type) { + if constexpr (NotSkip) { + sp_deserialize_to(reader_, item); + } + else { + sp_deserialize_to_with_skip(reader_, item); + } + } else if constexpr (detail::varint_t) { if constexpr (is_enable_fast_varint_coding(parent_tag)) { // do nothing, we have deserialized it in parent. @@ -1002,7 +1010,7 @@ class unpacker { item = {(value_type *)(view), (std::size_t)size}; } else { - if constexpr (check_reader_t) { + if constexpr (checkable_reader_t) { if SP_UNLIKELY (!reader_.check(mem_sz)) { return struct_pack::errc::no_buffer_space; } diff --git a/include/ylt/struct_pack/util.h b/include/ylt/struct_pack/util.h index 87d86321e..25cc12c56 100644 --- a/include/ylt/struct_pack/util.h +++ b/include/ylt/struct_pack/util.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -24,6 +25,41 @@ namespace struct_pack::detail { +template +constexpr std::string_view get_raw_name() { +#ifdef _MSC_VER + return __FUNCSIG__; +#else + return __PRETTY_FUNCTION__; +#endif +} + +template +constexpr std::string_view get_raw_name() { +#ifdef _MSC_VER + return __FUNCSIG__; +#else + return __PRETTY_FUNCTION__; +#endif +} + +template +inline constexpr std::string_view type_string() { + constexpr std::string_view sample = get_raw_name(); + constexpr size_t pos = sample.find("int"); + constexpr std::string_view str = get_raw_name(); + constexpr auto next1 = str.rfind(sample[pos + 3]); +#if defined(_MSC_VER) + constexpr std::size_t npos = str.find_first_of(" ", pos); + if (npos != std::string_view::npos) + return str.substr(npos + 1, next1 - npos - 1); + else + return str.substr(pos, next1 - pos); +#else + return str.substr(pos, next1 - pos); +#endif +} + #if __cpp_concepts >= 201907L constexpr bool is_string_reserve_shrink = requires { std::string{}.reserve(); }; #else diff --git a/include/ylt/thirdparty/iguana/enum_reflection.hpp b/include/ylt/thirdparty/iguana/enum_reflection.hpp index 1e0d2ebe6..2f5ea98d1 100644 --- a/include/ylt/thirdparty/iguana/enum_reflection.hpp +++ b/include/ylt/thirdparty/iguana/enum_reflection.hpp @@ -31,11 +31,14 @@ inline constexpr std::string_view type_string() { constexpr std::string_view str = get_raw_name(); constexpr auto next1 = str.rfind(sample[pos + 3]); #if defined(_MSC_VER) - constexpr auto s1 = str.substr(pos + 6, next1 - pos - 6); + constexpr std::size_t npos = str.find_first_of(" ", pos); + if (npos != std::string_view::npos) + return str.substr(npos + 1, next1 - npos - 1); + else + return str.substr(pos, next1 - pos); #else - constexpr auto s1 = str.substr(pos, next1 - pos); + return str.substr(pos, next1 - pos); #endif - return s1; } template diff --git a/src/struct_pack/examples/BUILD.bazel b/src/struct_pack/examples/BUILD.bazel index 0cd5740f2..4b535caad 100644 --- a/src/struct_pack/examples/BUILD.bazel +++ b/src/struct_pack/examples/BUILD.bazel @@ -2,7 +2,7 @@ load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") cc_binary( name = "serialize_example", - srcs = ["basic_usage.cpp","main.cpp","non_aggregated_type.cpp"], + srcs = ["basic_usage.cpp","main.cpp","non_aggregated_type.cpp","serialize_config.cpp","user_defined_serialization.cpp"], copts = ["-std=c++20"], deps = [ "//:ylt" diff --git a/src/struct_pack/examples/CMakeLists.txt b/src/struct_pack/examples/CMakeLists.txt index dafe28f08..89d30df93 100644 --- a/src/struct_pack/examples/CMakeLists.txt +++ b/src/struct_pack/examples/CMakeLists.txt @@ -18,4 +18,4 @@ else() # include_directories(include) # include_directories(include/ylt/thirdparty) endif() -add_executable(struct_pack_example basic_usage.cpp non_aggregated_type.cpp serialize_config.cpp main.cpp) \ No newline at end of file +add_executable(struct_pack_example basic_usage.cpp non_aggregated_type.cpp serialize_config.cpp user_defined_serialization.cpp main.cpp) \ No newline at end of file diff --git a/src/struct_pack/examples/main.cpp b/src/struct_pack/examples/main.cpp index d43e44c55..bacc07834 100644 --- a/src/struct_pack/examples/main.cpp +++ b/src/struct_pack/examples/main.cpp @@ -17,9 +17,11 @@ void basic_usage(); void non_aggregated_type(); void serialize_config(); - +void user_defined_serialization(); int main() { basic_usage(); non_aggregated_type(); + serialize_config(); + user_defined_serialization(); return 0; } diff --git a/src/struct_pack/examples/user_defined_serialization.cpp b/src/struct_pack/examples/user_defined_serialization.cpp new file mode 100644 index 000000000..6e5d17990 --- /dev/null +++ b/src/struct_pack/examples/user_defined_serialization.cpp @@ -0,0 +1,93 @@ +#include + +#include "ylt/struct_pack.hpp" +namespace my_name_space { + +struct array2D { + unsigned int x; + unsigned int y; + float* p; + array2D(unsigned int x, unsigned int y) : x(x), y(y) { + p = (float*)calloc(1ull * x * y, sizeof(float)); + } + array2D(const array2D&) = delete; + array2D(array2D&& o) : x(o.x), y(o.y), p(o.p) { o.p = nullptr; }; + array2D& operator=(const array2D&) = delete; + array2D& operator=(array2D&& o) { + x = o.x; + y = o.y; + p = o.p; + o.p = nullptr; + return *this; + } + float& operator()(std::size_t i, std::size_t j) { return p[i * y + j]; } + bool operator==(const array2D& o) const { + return x == o.x && y == o.y && + memcmp(p, o.p, 1ull * x * y * sizeof(float)) == 0; + } + array2D() : x(0), y(0), p(nullptr) {} + ~array2D() { free(p); } +}; + +// If you want to add a new type for struct_pack which is not in the struct_pack +// type system. You need add those functions: + +// 1. sp_get_needed_size: calculate length of serialization +std::size_t sp_get_needed_size(const array2D& ar) { + return 2 * struct_pack::get_write_size(ar.x) + + struct_pack::get_write_size(ar.p, 1ull * ar.x * ar.y); +} +// 2. sp_serialize_to: serilize object to writer +template +void sp_serialize_to(Writer& writer, const array2D& ar) { + struct_pack::write(writer, ar.x); + struct_pack::write(writer, ar.y); + struct_pack::write(writer, ar.p, 1ull * ar.x * ar.y); +} +// 3. sp_deserialize_to: deserilize object from reader +template +struct_pack::errc sp_deserialize_to(Reader& reader, array2D& ar) { + if (auto ec = struct_pack::read(reader, ar.x); ec != struct_pack::errc{}) { + return ec; + } + if (auto ec = struct_pack::read(reader, ar.y); ec != struct_pack::errc{}) { + return ec; + } + auto length = 1ull * ar.x * ar.y * sizeof(float); + if constexpr (struct_pack::checkable_reader_t) { + if (!reader.check(length)) { // some input(such as memory) allow us check + // length before read data. + return struct_pack::errc::no_buffer_space; + } + } + ar.p = (float*)malloc(length); + auto ec = struct_pack::read(reader, ar.p, 1ull * ar.x * ar.y); + if (ec != struct_pack::errc{}) { + free(ar.p); + } + return ec; +} + +// 4. The default name for type checking is it's literal type name. You can also +// config it by: + +// constexpr std::string_view sp_set_type_name(test*) { return "myarray2D"; } + +// 5. If you want to use struct_pack::get_field/struct_pack::get_field_to, you +// need also define this function to skip deserialization of your type: + +// template +// struct_pack::errc sp_deserialize_to_with_skip(Reader& reader, array2D& ar); +} // namespace my_name_space + +void user_defined_serialization() { + std::vector ar; + ar.emplace_back(11, 22); + ar.emplace_back(114, 514); + ar[0](1, 6) = 3.14; + ar[1](87, 111) = 2.71; + auto buffer = struct_pack::serialize(ar); + auto result = struct_pack::deserialize(buffer); + assert(result.has_value()); + assert(ar == result); +} \ No newline at end of file diff --git a/src/struct_pack/tests/test_user_defined_type.cpp b/src/struct_pack/tests/test_user_defined_type.cpp new file mode 100644 index 000000000..51e1effcb --- /dev/null +++ b/src/struct_pack/tests/test_user_defined_type.cpp @@ -0,0 +1,180 @@ +#include +#include + +#include "doctest.h" +#include "ylt/struct_pack.hpp" +#include "ylt/struct_pack/error_code.hpp" +#include "ylt/struct_pack/type_id.hpp" +namespace my_name_space { + +struct array2D { + unsigned int x; + unsigned int y; + float* p; + array2D(unsigned int x, unsigned int y) : x(x), y(y) { + p = (float*)calloc(1ull * x * y, sizeof(float)); + } + array2D(const array2D&) = delete; + array2D(array2D&& o) : x(o.x), y(o.y), p(o.p) { o.p = nullptr; }; + array2D& operator=(const array2D&) = delete; + array2D& operator=(array2D&& o) { + x = o.x; + y = o.y; + p = o.p; + o.p = nullptr; + return *this; + } + float& operator()(std::size_t i, std::size_t j) { return p[i * y + j]; } + bool operator==(const array2D& o) const { + return x == o.x && y == o.y && + memcmp(p, o.p, 1ull * x * y * sizeof(float)) == 0; + } + array2D() : x(0), y(0), p(nullptr) {} + ~array2D() { free(p); } +}; + +std::size_t sp_get_needed_size(const array2D& ar) { + return 2 * struct_pack::get_write_size(ar.x) + + struct_pack::get_write_size(ar.p, 1ull * ar.x * ar.y); +} +template +void sp_serialize_to(Writer& writer, const array2D& ar) { + struct_pack::write(writer, ar.x); + struct_pack::write(writer, ar.y); + struct_pack::write(writer, ar.p, 1ull * ar.x * ar.y); +} + +template +struct_pack::errc sp_deserialize_to(Reader& reader, array2D& ar) { + if (auto ec = struct_pack::read(reader, ar.x); ec != struct_pack::errc{}) { + return ec; + } + if (auto ec = struct_pack::read(reader, ar.y); ec != struct_pack::errc{}) { + return ec; + } + auto length = 1ull * ar.x * ar.y * sizeof(float); + if constexpr (struct_pack::checkable_reader_t) { + if (!reader.check(length)) { // some input(such as memory) allow us check + // length before read data. + return struct_pack::errc::no_buffer_space; + } + } + ar.p = (float*)malloc(length); + auto ec = struct_pack::read(reader, ar.p, 1ull * ar.x * ar.y); + if (ec != struct_pack::errc{}) { + free(ar.p); + } + return ec; +} + +template +struct_pack::errc sp_deserialize_to_with_skip(Reader& reader, array2D& ar) { + if (auto ec = struct_pack::read(reader, ar.x); ec != struct_pack::errc{}) { + return ec; + } + if (auto ec = struct_pack::read(reader, ar.y); ec != struct_pack::errc{}) { + return ec; + } + auto length = 1ull * ar.x * ar.y * sizeof(float); + if (!reader.ignore(length)) { + return struct_pack::errc::no_buffer_space; + } + return {}; +} + +} // namespace my_name_space + +TEST_CASE("test user-defined_type") { + my_name_space::array2D ar(114, 514); + ar(1, 6) = 3.14; + ar(87, 111) = 2.71; + auto buffer = struct_pack::serialize(ar); + auto result = struct_pack::deserialize(buffer); + CHECK(result.has_value()); + auto& ar2 = result.value(); + CHECK(ar == result.value()); +} + +TEST_CASE("test user-defined_type nested") { + std::vector ar; + ar.emplace_back(11, 22); + ar.emplace_back(114, 514); + ar[0](1, 6) = 3.14; + ar[1](87, 111) = 2.71; + auto buffer = struct_pack::serialize(ar); + auto result = struct_pack::deserialize(buffer); + CHECK(result.has_value()); + CHECK(ar == result); +} + +TEST_CASE("test user-defined_type get_field") { + std::pair o; + o.first = {114, 514}; + o.first(1, 6) = 3.14; + o.first(87, 111) = 2.71; + o.second = 114514; + auto buffer = struct_pack::serialize(o); + auto result = struct_pack::get_field(buffer); + CHECK(result.has_value()); + auto& i = result.value(); + CHECK(o.second == i); +} + +TEST_CASE("test auto type name") { + { + auto name = struct_pack::get_type_literal(); + std::string_view sv{name.data(), name.size()}; + CHECK(sv == (char)struct_pack::detail::type_id::user_defined_type + + std::string{"my_name_space::array2D"} + + (char)struct_pack::detail::type_id::type_end_flag); + } + { + auto name = + struct_pack::get_type_literal>(); + std::string_view sv{name.data(), name.size()}; + CHECK(sv == std::string{(char)struct_pack::detail::type_id::container_t} + + (char)struct_pack::detail::type_id::user_defined_type + + std::string{"my_name_space::array2D"} + + (char)struct_pack::detail::type_id::type_end_flag); + } +} + +namespace my_name_space { + +struct test { + unsigned int x; +}; + +std::size_t sp_get_needed_size(const test& t) { + return struct_pack::get_write_size(t.x); +} +template +void sp_serialize_to(Writer& writer, const test& t) { + struct_pack::write(writer, t.x); +} + +template +struct_pack::errc sp_deserialize_to(Reader& reader, test& ar) { + return struct_pack::read(reader, ar.x); +} +constexpr std::string_view sp_set_type_name(test*) { return "myint"; } +} // namespace my_name_space + +TEST_CASE("test user-defined type name") { + { + auto name = struct_pack::get_type_literal(); + std::string_view sv{name.data(), name.size()}; + CHECK(sv == (char)struct_pack::detail::type_id::user_defined_type + + std::string{"myint"} + + (char)struct_pack::detail::type_id::type_end_flag); + } + { + auto name = + struct_pack::get_type_literal>(); + std::string_view sv{name.data(), name.size()}; + CHECK(sv == std::string{(char)struct_pack::detail::type_id::container_t} + + (char)struct_pack::detail::type_id::user_defined_type + + std::string{"myint"} + + (char)struct_pack::detail::type_id::type_end_flag); + } +} diff --git a/website/docs/en/struct_pack/struct_pack_intro.md b/website/docs/en/struct_pack/struct_pack_intro.md index 47935e4de..27e22f5a0 100644 --- a/website/docs/en/struct_pack/struct_pack_intro.md +++ b/website/docs/en/struct_pack/struct_pack_intro.md @@ -252,7 +252,9 @@ The member function registed must return a reference, and this function must hav ### custom type -In addition, struct_pack supports serialization and deserialization on custom containers, as below: +#### The type cannot be abstracted to a type already in the type system + +For example, let's say we need to serialise a map type from a third-party library (absl, boost...): we just need to make sure that it conforms to the constraints of the struct_pack type system for maps: ```cpp // We should not inherit from stl container, this case just for testing. @@ -272,6 +274,105 @@ auto buffer2 = serialize(map2); For more detail, See [struct_pack type system](https://alibaba.github.io/yalantinglibs/en/struct_pack/struct_pack_type_system.html) +#### The type cannot be abstracted to a type already in the type system + +At this time, we also support custom serialisation, the user only needs to customise the following three functions: + +1. sp_get_needed_size +2. sp_serialize_to +3. sp_deserialize_to + +For example, the following is an example of support for serialisation/deserialisation of a custom 2D array type. + +```cpp +struct array2D { + unsigned int x; + unsigned int y; + float* p; + array2D(unsigned int x, unsigned int y) : x(x), y(y) { + p = (float*)calloc(1ull * x * y, sizeof(float)); + } + array2D(const array2D&) = delete; + array2D(array2D&& o) : x(o.x), y(o.y), p(o.p) { o.p = nullptr; }; + array2D& operator=(const array2D&) = delete; + array2D& operator=(array2D&& o) { + x = o.x; + y = o.y; + p = o.p; + o.p = nullptr; + return *this; + } + float& operator()(std::size_t i, std::size_t j) { return p[i * y + j]; } + bool operator==(const array2D& o) const { + return x == o.x && y == o.y && + memcmp(p, o.p, 1ull * x * y * sizeof(float)) == 0; + } + array2D() : x(0), y(0), p(nullptr) {} + ~array2D() { free(p); } +}; + +// You need add those functions: + +// 1. sp_get_needed_size: calculate length of serialization +std::size_t sp_get_needed_size(const array2D& ar) { + return 2 * struct_pack::get_write_size(ar.x) + + struct_pack::get_write_size(ar.p, 1ull * ar.x * ar.y); +} +// 2. sp_serialize_to: serilize object to writer +template +void sp_serialize_to(Writer& writer, const array2D& ar) { + struct_pack::write(writer, ar.x); + struct_pack::write(writer, ar.y); + struct_pack::write(writer, ar.p, 1ull * ar.x * ar.y); +} +// 3. sp_deserialize_to: deserilize object from reader +template +struct_pack::errc sp_deserialize_to(Reader& reader, array2D& ar) { + if (auto ec = struct_pack::read(reader, ar.x); ec != struct_pack::errc{}) { + return ec; + } + if (auto ec = struct_pack::read(reader, ar.y); ec != struct_pack::errc{}) { + return ec; + } + auto length = 1ull * ar.x * ar.y * sizeof(float); + if constexpr (struct_pack::checkable_reader_t) { + if (!reader.check(length)) { // some input(such as memory) allow us check + // length before read data. + return struct_pack::errc::no_buffer_space; + } + } + ar.p = (float*)malloc(length); + auto ec = struct_pack::read(reader, ar.p, 1ull * ar.x * ar.y); + if (ec != struct_pack::errc{}) { + free(ar.p); + } + return ec; + +// 4. The default name for type checking is it's literal type name. You can also +// config it by: + +// constexpr std::string_view sp_set_type_name(test*) { return "myarray2D"; } + +// 5. If you want to use struct_pack::get_field/struct_pack::get_field_to, you +// need also define this function to skip deserialization of your type: + +// template +// struct_pack::errc sp_deserialize_to_with_skip(Reader& reader, array2D& ar); + +} + +void user_defined_serialization() { + std::vector ar; + ar.emplace_back(11, 22); + ar.emplace_back(114, 514); + ar[0](1, 6) = 3.14; + ar[1](87, 111) = 2.71; + auto buffer = struct_pack::serialize(ar); + auto result = struct_pack::deserialize(buffer); + assert(result.has_value()); + assert(ar == result); +} + ### custom output stream Except std::ostream/std::sstream struct_pack also support serialize to custom output stream. diff --git a/website/docs/zh/struct_pack/struct_pack_intro.md b/website/docs/zh/struct_pack/struct_pack_intro.md index c5ba233ff..3a82ef7d3 100644 --- a/website/docs/zh/struct_pack/struct_pack_intro.md +++ b/website/docs/zh/struct_pack/struct_pack_intro.md @@ -259,6 +259,10 @@ STRUCT_PACK_REFL(person, age(), name()); struct_pack支持序列化自定义类型。 +#### 该类型可以被抽象为类型系统中已有的类型 + +例如,假如我们需要序列化一个第三方库的map类型(absl , boost...):我们只需要保证其符合struct_pack类型系统中对于map的约束即可。 + ```cpp // We should not inherit from stl container, this case just for testing. template @@ -279,6 +283,104 @@ auto buffer2 = serialize(map2); [struct_pack的类型系统](https://alibaba.github.io/yalantinglibs/zh/struct_pack/struct_pack_type_system.html) +#### 该类型不能被抽象为类型系统中已有的类型 + +此时,我们也支持自定义的序列化,用户只需要自定义以下三个函数即可: + +1. sp_get_needed_size +2. sp_serialize_to +3. sp_deserialize_to + +例如,下面是一个支持自定义二维数组类型的序列化/反序列化的例子。 + +```cpp +struct array2D { + unsigned int x; + unsigned int y; + float* p; + array2D(unsigned int x, unsigned int y) : x(x), y(y) { + p = (float*)calloc(1ull * x * y, sizeof(float)); + } + array2D(const array2D&) = delete; + array2D(array2D&& o) : x(o.x), y(o.y), p(o.p) { o.p = nullptr; }; + array2D& operator=(const array2D&) = delete; + array2D& operator=(array2D&& o) { + x = o.x; + y = o.y; + p = o.p; + o.p = nullptr; + return *this; + } + float& operator()(std::size_t i, std::size_t j) { return p[i * y + j]; } + bool operator==(const array2D& o) const { + return x == o.x && y == o.y && + memcmp(p, o.p, 1ull * x * y * sizeof(float)) == 0; + } + array2D() : x(0), y(0), p(nullptr) {} + ~array2D() { free(p); } +}; + +// 你需要自定义以下函数 + +// 1. sp_get_needed_size: 预计算序列化长度 +std::size_t sp_get_needed_size(const array2D& ar) { + return 2 * struct_pack::get_write_size(ar.x) + + struct_pack::get_write_size(ar.p, 1ull * ar.x * ar.y); +} +// 2. sp_serialize_to: 将对象序列化到writer +template +void sp_serialize_to(Writer& writer, const array2D& ar) { + struct_pack::write(writer, ar.x); + struct_pack::write(writer, ar.y); + struct_pack::write(writer, ar.p, 1ull * ar.x * ar.y); +} +// 3. sp_deserialize_to: 从reader反序列化对象 +template +struct_pack::errc sp_deserialize_to(Reader& reader, array2D& ar) { + if (auto ec = struct_pack::read(reader, ar.x); ec != struct_pack::errc{}) { + return ec; + } + if (auto ec = struct_pack::read(reader, ar.y); ec != struct_pack::errc{}) { + return ec; + } + auto length = 1ull * ar.x * ar.y * sizeof(float); + if constexpr (struct_pack::checkable_reader_t) { + if (!reader.check(length)) { + //checkable_reader_t允许我们在读取数据前先检查是否超出长度限制 + return struct_pack::errc::no_buffer_space; + } + } + ar.p = (float*)malloc(length); + auto ec = struct_pack::read(reader, ar.p, 1ull * ar.x * ar.y); + if (ec != struct_pack::errc{}) { + free(ar.p); + } + return ec; + +// 4. 默认用于类型检查的字符串就是该类型的名字. 你也可以通过下面的函数来配置 + +// constexpr std::string_view sp_set_type_name(test*) { return "myarray2D"; } + +// 5. 如果你想使用 struct_pack::get_field/struct_pack::get_field_to, 还需要定义下面的函数以跳过自定义类型的反序列化。 + +// template +// struct_pack::errc sp_deserialize_to_with_skip(Reader& reader, array2D& ar); + +} + +void user_defined_serialization() { + std::vector ar; + ar.emplace_back(11, 22); + ar.emplace_back(114, 514); + ar[0](1, 6) = 3.14; + ar[1](87, 111) = 2.71; + auto buffer = struct_pack::serialize(ar); + auto result = struct_pack::deserialize(buffer); + assert(result.has_value()); + assert(ar == result); +} +``` + ### 序列化到自定义的输出流 该流需要满足以下约束条件: