yalantinglibs/include/ylt/struct_pack/packer.hpp

591 lines
21 KiB
C++

/*
* Copyright (c) 2023, Alibaba Group Holding Limited;
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <bitset>
#include <cstddef>
#include <cstdint>
#include <type_traits>
#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 {
template <
#if __cpp_concepts >= 201907L
writer_t writer,
#else
typename writer,
#endif
typename serialize_type, bool force_optimize = false>
class packer {
constexpr inline static serialize_buffer_size useless_info{};
public:
packer(writer &writer_, const serialize_buffer_size &info)
: writer_(writer_), info_(info) {
#if __cpp_concepts < 201907L
static_assert(writer_t<writer>,
"The writer type must satisfy requirements!");
#endif
}
packer(writer &writer_) : writer_(writer_), info_(useless_info) {
#if __cpp_concepts < 201907L
static_assert(writer_t<writer>,
"The writer type must satisfy requirements!");
#endif
}
packer(const packer &) = delete;
packer &operator=(const packer &) = delete;
template <typename DerivedClasses, typename size_type, typename version>
friend struct serialize_one_derived_class_helper;
template <std::size_t size_type, typename T, std::size_t... I,
typename... Args>
void serialize_expand_compatible_helper(const T &t, std::index_sequence<I...>,
const Args &...args) {
using Type = get_args_type<T, Args...>;
(serialize_many<size_type, compatible_version_number<Type>[I]>(t, args...),
...);
}
template <uint64_t conf, std::size_t size_type, typename T, typename... Args>
STRUCT_PACK_INLINE void serialize(const T &t, const Args &...args) {
serialize_metainfo<conf, size_type == 1, T, Args...>();
serialize_many<size_type, UINT64_MAX>(t, args...);
using Type = get_args_type<T, Args...>;
if constexpr (serialize_static_config<Type>::has_compatible) {
constexpr std::size_t sz = compatible_version_number<Type>.size();
return serialize_expand_compatible_helper<size_type, T, Args...>(
t, std::make_index_sequence<sz>{}, args...);
}
}
template <typename T, typename... Args>
static constexpr uint32_t STRUCT_PACK_INLINE calculate_raw_hash() {
if constexpr (sizeof...(Args) == 0) {
return get_types_code<remove_cvref_t<T>>();
}
else {
return get_types_code<
std::tuple<remove_cvref_t<T>, remove_cvref_t<Args>...>>();
}
}
template <uint64_t conf, typename T, typename... Args>
static constexpr uint32_t STRUCT_PACK_INLINE calculate_hash_head() {
constexpr uint32_t raw_types_code = calculate_raw_hash<T, Args...>();
if constexpr (check_has_metainfo<conf, T>()) {
return raw_types_code + 1;
}
else { // default case, only has hash_code
return raw_types_code;
}
}
template <uint64_t conf, bool is_default_size_type, typename T,
typename... Args>
constexpr void STRUCT_PACK_INLINE serialize_metainfo() {
constexpr auto hash_head = calculate_hash_head<conf, T, Args...>() |
(is_default_size_type ? 0 : 1);
if constexpr (!check_if_disable_hash_head<conf, serialize_type>()) {
write_wrapper<sizeof(uint32_t)>(writer_, (char *)&hash_head);
}
if constexpr (hash_head % 2) { // has more metainfo
auto metainfo = info_.metainfo();
write_wrapper<sizeof(char)>(writer_, (char *)&metainfo);
if constexpr (serialize_static_config<serialize_type>::has_compatible) {
std::size_t sz = info_.size();
switch (metainfo & 0b11) {
case 1:
low_bytes_write_wrapper<2>(writer_, sz);
break;
case 2:
low_bytes_write_wrapper<4>(writer_, sz);
break;
case 3:
if constexpr (sizeof(std::size_t) >= 8) {
low_bytes_write_wrapper<8>(writer_, sz);
}
else {
unreachable();
}
break;
default:
unreachable();
}
}
if constexpr (check_if_add_type_literal<conf, serialize_type>()) {
constexpr auto type_literal =
struct_pack::get_type_literal<T, Args...>();
write_bytes_array(writer_, type_literal.data(),
type_literal.size() + 1);
}
}
}
template <std::size_t size_type, uint64_t version,
std::uint64_t parent_tag = 0, typename First, typename... Args>
constexpr void STRUCT_PACK_INLINE serialize_many(const First &first_item,
const Args &...items) {
serialize_one<size_type, version, parent_tag>(first_item);
if constexpr (sizeof...(items) > 0) {
serialize_many<size_type, version, parent_tag>(items...);
}
}
constexpr void STRUCT_PACK_INLINE write_padding(std::size_t sz) {
if (sz > 0) {
constexpr char buf = 0;
for (std::size_t i = 0; i < sz; ++i) write_wrapper<1>(writer_, &buf);
}
}
template <uint64_t parent_tag, std::size_t sz, typename Arg,
typename unsigned_t, typename signed_t>
static constexpr void STRUCT_PACK_INLINE
get_fast_varint_width_impl(char (&vec)[sz], unsigned int &i, const Arg &item,
unsigned_t &unsigned_max, signed_t &signed_max) {
if constexpr (varint_t<Arg, parent_tag>) {
if (get_varint_value(item) != 0) {
vec[i / 8] |= (0b1 << (i % 8));
if constexpr (std::is_unsigned_v<std::remove_reference_t<
decltype(get_varint_value(item))>>) {
unsigned_max =
std::max<unsigned_t>(unsigned_max, get_varint_value(item));
}
else {
signed_max = std::max<signed_t>(signed_max,
get_varint_value(item) > 0
? get_varint_value(item)
: -(get_varint_value(item) + 1));
}
}
++i;
}
}
template <uint64_t parent_tag, std::size_t sz, typename... Args>
static constexpr int STRUCT_PACK_INLINE
get_fast_varint_width(char (&vec)[sz], const Args &...items) {
typename uint_t<get_int_len<parent_tag, Args...>()>::type unsigned_max = 0;
typename int_t<get_int_len<parent_tag, Args...>()>::type signed_max = 0;
unsigned int i = 0;
(get_fast_varint_width_impl<parent_tag>(vec, i, items, unsigned_max,
signed_max),
...);
return get_fast_varint_width_from_max<parent_tag, Args...>(unsigned_max,
signed_max);
}
template <uint64_t parent_tag, std::size_t sz, typename Arg>
constexpr void STRUCT_PACK_INLINE serialize_one_fast_varint(const Arg &item) {
if constexpr (varint_t<Arg, parent_tag>) {
if (get_varint_value(item))
low_bytes_write_wrapper<(std::min)(sz, sizeof(Arg))>(
writer_, get_varint_value(item));
}
}
template <uint64_t parent_tag, typename... Args>
constexpr void STRUCT_PACK_INLINE
serialize_fast_varint(const Args &...items) {
constexpr auto cnt = calculate_fast_varint_count<parent_tag, Args...>();
constexpr auto bitset_size = ((cnt + 2) + 7) / 8;
if constexpr (cnt == 0) {
return;
}
else {
char vec[bitset_size]{};
auto width = get_fast_varint_width<parent_tag>(vec, items...);
vec[cnt / 8] |= (width & 0b1) << (cnt % 8);
vec[(cnt + 1) / 8] |= (!!(width & 0b10)) << ((cnt + 1) % 8);
write_bytes_array(writer_, vec, bitset_size);
switch (width) {
case 0:
(serialize_one_fast_varint<parent_tag, 1>(items), ...);
break;
case 1:
(serialize_one_fast_varint<parent_tag, 2>(items), ...);
break;
case 2:
(serialize_one_fast_varint<parent_tag, 4>(items), ...);
break;
case 3:
if constexpr (has_64bits_varint<parent_tag, Args...>()) {
(serialize_one_fast_varint<parent_tag, 8>(items), ...);
}
else {
unreachable();
}
break;
default:
unreachable();
}
}
}
template <std::size_t size_type, uint64_t version, uint64_t parent_tag = 0,
typename T>
constexpr void inline serialize_one(const T &item) {
using type = remove_cvref_t<decltype(item)>;
static_assert(!std::is_pointer_v<type>);
constexpr auto id = get_type_id<type, parent_tag>();
if constexpr (is_trivial_view_v<T>) {
return serialize_one<size_type, version>(item.get());
}
else if constexpr (version == UINT64_MAX) {
if constexpr (id == type_id::compatible_t) {
// do nothing
}
else if constexpr (std::is_same_v<type, std::monostate>) {
// do nothing
}
else if constexpr (id == type_id::user_defined_type) {
sp_serialize_to(writer_, item);
}
else if constexpr (detail::varint_t<type, parent_tag>) {
if constexpr (is_enable_fast_varint_coding(parent_tag)) {
// do nothing
}
else {
detail::serialize_varint(writer_, item);
}
}
else if constexpr (std::is_fundamental_v<type> || std::is_enum_v<type> ||
id == type_id::int128_t || id == type_id::uint128_t) {
write_wrapper<sizeof(item)>(writer_, (char *)&item);
}
else if constexpr (id == type_id::bitset_t) {
write_bytes_array(writer_, (char *)&item, sizeof(item));
}
else if constexpr (unique_ptr<type>) {
bool has_value = (item != nullptr);
write_wrapper<sizeof(char)>(writer_, (char *)&has_value);
if (has_value) {
if constexpr (is_base_class<typename type::element_type>) {
bool is_ok{};
uint32_t id = item->get_struct_pack_id();
auto index = search_type_by_md5<typename type::element_type>(
item->get_struct_pack_id(), is_ok);
assert(is_ok);
write_wrapper<sizeof(uint32_t)>(writer_, (char *)&id);
template_switch<serialize_one_derived_class_helper<
derived_class_set_t<typename type::element_type>,
std::integral_constant<std::size_t, size_type>,
std::integral_constant<std::uint64_t, version>>>(index, this,
item.get());
}
else {
serialize_one<size_type, version>(*item);
}
}
}
else if constexpr (id == type_id::array_t) {
if constexpr (is_trivial_serializable<type>::value &&
is_little_endian_copyable<sizeof(item[0])>) {
write_bytes_array(writer_, (char *)&item, sizeof(type));
}
else {
for (const auto &i : item) {
serialize_one<size_type, version>(i);
}
}
}
else if constexpr (map_container<type> || container<type>) {
auto size = item.size();
if constexpr (size_type == 1) {
low_bytes_write_wrapper<size_type>(writer_, size);
}
else {
#ifdef STRUCT_PACK_OPTIMIZE
constexpr bool struct_pack_optimize = true;
#else
constexpr bool struct_pack_optimize = false;
#endif
if constexpr (force_optimize || struct_pack_optimize) {
if constexpr (size_type == 2) {
low_bytes_write_wrapper<size_type>(writer_, size);
}
else if constexpr (size_type == 4) {
low_bytes_write_wrapper<size_type>(writer_, size);
}
else if constexpr (size_type == 8) {
if constexpr (sizeof(std::size_t) >= 8) {
low_bytes_write_wrapper<size_type>(writer_, size);
}
else {
std::uint64_t sz = size;
low_bytes_write_wrapper<size_type>(writer_, sz);
}
}
else {
static_assert(!sizeof(item), "illegal size_type.");
}
}
else {
switch ((info_.metainfo() & 0b11000) >> 3) {
case 1:
low_bytes_write_wrapper<2>(writer_, size);
break;
case 2:
low_bytes_write_wrapper<4>(writer_, size);
break;
case 3:
if constexpr (sizeof(std::size_t) >= 8) {
low_bytes_write_wrapper<8>(writer_, size);
}
else {
unreachable();
}
break;
default:
unreachable();
}
}
}
if constexpr (trivially_copyable_container<type> &&
is_little_endian_copyable<sizeof(
typename type::value_type)>) {
using value_type = typename type::value_type;
write_bytes_array(writer_, (char *)item.data(),
item.size() * sizeof(typename type::value_type));
return;
}
else {
for (const auto &i : item) {
serialize_one<size_type, version>(i);
}
}
}
else if constexpr (container_adapter<type>) {
static_assert(!sizeof(type),
"the container adapter type is not supported");
}
else if constexpr (!pair<type> && tuple<type> &&
!is_trivial_tuple<type>) {
std::apply(
[&](auto &&...items) CONSTEXPR_INLINE_LAMBDA {
serialize_many<size_type, version>(items...);
},
item);
}
else if constexpr (optional<type>) {
bool has_value = item.has_value();
write_wrapper<sizeof(bool)>(writer_, (char *)&has_value);
if (has_value) {
serialize_one<size_type, version>(*item);
}
}
else if constexpr (is_variant_v<type>) {
static_assert(std::variant_size_v<type> < 256,
"variant's size is too large");
uint8_t index = item.index();
write_wrapper<sizeof(uint8_t)>(writer_, (char *)&index);
std::visit(
[this](auto &&e) {
this->serialize_one<size_type, version>(e);
},
item);
}
else if constexpr (expected<type>) {
bool has_value = item.has_value();
write_wrapper<sizeof(bool)>(writer_, (char *)&has_value);
if (has_value) {
if constexpr (!std::is_same_v<typename type::value_type, void>)
serialize_one<size_type, version>(item.value());
}
else {
serialize_one<size_type, version>(item.error());
}
}
else if constexpr (std::is_class_v<type>) {
if constexpr (!pair<type> && !is_trivial_tuple<type>)
if constexpr (!user_defined_refl<type>)
static_assert(
std::is_aggregate_v<remove_cvref_t<type>>,
"struct_pack only support aggregated type, or you should "
"add macro STRUCT_PACK_REFL(Type,field1,field2...)");
if constexpr (is_trivial_serializable<type>::value &&
is_little_endian_copyable<sizeof(type)>) {
write_wrapper<sizeof(type)>(writer_, (char *)&item);
}
else if constexpr ((is_trivial_serializable<type>::value &&
!is_little_endian_copyable<sizeof(type)>) ||
is_trivial_serializable<type, true>::value) {
visit_members(item, [this](auto &&...items) CONSTEXPR_INLINE_LAMBDA {
int i = 1;
((serialize_one<size_type, version>(items),
write_padding(align::padding_size<type>[i++])),
...);
});
}
else {
constexpr uint64_t tag = get_parent_tag<type>();
if constexpr (is_enable_fast_varint_coding(tag)) {
visit_members(
item, [this](auto &&...items) CONSTEXPR_INLINE_LAMBDA {
constexpr uint64_t tag =
get_parent_tag<type>(); // to pass msvc with c++17
serialize_fast_varint<tag>(items...);
});
}
visit_members(item, [this](auto &&...items) CONSTEXPR_INLINE_LAMBDA {
constexpr uint64_t tag =
get_parent_tag<type>(); // to pass msvc with c++17
serialize_many<size_type, version, tag>(items...);
});
}
}
else {
static_assert(!sizeof(type), "the type is not supported yet");
}
}
else if constexpr (exist_compatible_member<type, version>) {
if constexpr (id == type_id::compatible_t) {
if constexpr (version == type::version_number) {
bool has_value = item.has_value();
write_wrapper<sizeof(bool)>(writer_, (char *)&has_value);
if (has_value) {
serialize_one<size_type, UINT64_MAX>(*item);
}
}
}
else if constexpr (unique_ptr<type>) {
if (item != nullptr) {
if constexpr (is_base_class<typename type::element_type>) {
bool is_ok{};
auto index = search_type_by_md5<typename type::element_type>(
item->get_struct_pack_id(), is_ok);
assert(is_ok);
template_switch<serialize_one_derived_class_helper<
derived_class_set_t<typename type::element_type>,
std::integral_constant<std::size_t, size_type>,
std::integral_constant<std::uint64_t, version>>>(index, this,
item.get());
}
else {
serialize_one<size_type, version>(*item);
}
}
}
else if constexpr (id == type_id::array_t) {
for (const auto &i : item) {
serialize_one<size_type, version>(i);
}
}
else if constexpr (map_container<type> || container<type>) {
for (const auto &i : item) {
serialize_one<size_type, version>(i);
}
}
else if constexpr (!pair<type> && tuple<type> &&
!is_trivial_tuple<type>) {
std::apply(
[&](auto &&...items) CONSTEXPR_INLINE_LAMBDA {
serialize_many<size_type, version>(items...);
},
item);
}
else if constexpr (optional<type>) {
if (item.has_value()) {
serialize_one<size_type, version>(*item);
}
}
else if constexpr (is_variant_v<type>) {
std::visit(
[this](const auto &e) {
this->serialize_one<size_type, version>(e);
},
item);
}
else if constexpr (expected<type>) {
if (item.has_value()) {
if constexpr (!std::is_same_v<typename type::value_type, void>)
serialize_one<size_type, version>(item.value());
}
else {
serialize_one<size_type, version>(item.error());
}
}
else if constexpr (std::is_class_v<type>) {
visit_members(item, [&](auto &&...items) CONSTEXPR_INLINE_LAMBDA {
serialize_many<size_type, version>(items...);
});
}
}
return;
}
template <typename T>
friend constexpr serialize_buffer_size get_needed_size(const T &t);
writer &writer_;
const serialize_buffer_size &info_;
};
template <uint64_t conf = sp_config::DEFAULT,
#if __cpp_concepts >= 201907L
struct_pack::writer_t Writer,
#else
typename Writer,
#endif
typename... Args>
STRUCT_PACK_MAY_INLINE void serialize_to(Writer &writer,
const serialize_buffer_size &info,
const Args &...args) {
#if __cpp_concepts < 201907L
static_assert(writer_t<Writer>, "The writer type must satisfy requirements!");
#endif
static_assert(sizeof...(args) > 0);
detail::packer<Writer, detail::get_args_type<Args...>> o(writer, info);
if constexpr (!check_if_has_container<detail::get_args_type<Args...>>()) {
o.template serialize<conf, 1>(args...);
}
else {
switch ((info.metainfo() & 0b11000) >> 3) {
case 0:
o.template serialize<conf, 1>(args...);
break;
#ifdef STRUCT_PACK_OPTIMIZE
case 1:
o.template serialize<conf, 2>(args...);
break;
case 2:
o.template serialize<conf, 4>(args...);
break;
case 3:
if constexpr (sizeof(std::size_t) >= 8) {
o.template serialize<conf, 8>(args...);
}
else {
unreachable();
}
break;
#else
case 1:
case 2:
case 3:
o.template serialize<conf, 2>(args...);
break;
#endif
default:
detail::unreachable();
break;
};
}
}
} // namespace struct_pack::detail