Begin refactor of msgpack packer
This commit is contained in:
parent
53665b8e70
commit
c89d7cd541
6
MODULE.bazel
Normal file
6
MODULE.bazel
Normal file
@ -0,0 +1,6 @@
|
||||
###############################################################################
|
||||
# Bazel now uses Bzlmod by default to manage external dependencies.
|
||||
# Please consider migrating your external dependencies from WORKSPACE to MODULE.bazel.
|
||||
#
|
||||
# For more details, please check https://github.com/bazelbuild/bazel/issues/18958
|
||||
###############################################################################
|
||||
1245
MODULE.bazel.lock
generated
Normal file
1245
MODULE.bazel.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
330
include/parselink/msgpack/core/detail/builtin_packable_types.h
Normal file
330
include/parselink/msgpack/core/detail/builtin_packable_types.h
Normal file
@ -0,0 +1,330 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// Author: Kurt Sassenrath
|
||||
// Module: msgpack
|
||||
//
|
||||
// Support for packing various types.
|
||||
//
|
||||
// Copyright (c) 2023 Kurt Sassenrath.
|
||||
//
|
||||
// License TBD.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef msgpack_core_detail_builtin_packable_types_41bd88d512497527
|
||||
#define msgpack_core_detail_builtin_packable_types_41bd88d512497527
|
||||
|
||||
#include "parselink/msgpack/core/format.h"
|
||||
#include "parselink/msgpack/util/endianness.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <ranges>
|
||||
|
||||
namespace msgpack {
|
||||
namespace detail {
|
||||
|
||||
// This is a generic helper function for writing integral bytes
|
||||
template <typename T, typename Itr>
|
||||
constexpr auto pack_integral(T value, Itr out, std::size_t sz) noexcept {
|
||||
auto bytes = ::detail::as_bytes(host_to_be(value));
|
||||
for (std::size_t i = 0; i < sz; ++i) {
|
||||
*out++ = *(bytes.end() - sz + i);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// This structure helps to derive sizes needed to optimally represent the
|
||||
// given value (for integer formats) or the length (variable-length formats).
|
||||
template <format::type T>
|
||||
struct builtin_pack_helper {
|
||||
#if 0
|
||||
// Returns the number of bytes needed to store the representation of a
|
||||
// value in the associated msgpack format.
|
||||
static constexpr std::size_t rep_bytes(auto /*value*/) noexcept {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns the number of bytes needed to store the length of a value for
|
||||
// a given format. For fixed-size data, this will be zero.
|
||||
static constexpr std::size_t len_bytes(auto /*value*/) noexcept {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Returns the total number of bytes needed to store a value for a given
|
||||
// format.
|
||||
static constexpr std::size_t total_bytes(auto value) noexcept {
|
||||
return 1 + rep_bytes(value) + len_bytes(value);
|
||||
}
|
||||
|
||||
// Get the marker. Defaults to invalid in this structure.
|
||||
static constexpr std::byte marker(auto value) noexcept {
|
||||
return format::invalid::marker;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
template <>
|
||||
struct builtin_pack_helper<format::type::unsigned_int> {
|
||||
static constexpr std::size_t rep_bytes(std::uint64_t value) noexcept {
|
||||
constexpr auto fixint_size =
|
||||
static_cast<std::uint64_t>(format::positive_fixint::mask);
|
||||
if (value <= fixint_size) return 0;
|
||||
auto bytes_needed =
|
||||
static_cast<std::uint64_t>((std::bit_width(value) + 7) >> 3);
|
||||
return std::bit_ceil(bytes_needed);
|
||||
}
|
||||
|
||||
static constexpr std::byte marker(std::uint64_t value) noexcept {
|
||||
switch (rep_bytes(value)) {
|
||||
case 0: return static_cast<std::byte>(value);
|
||||
case 1: return format::uint8::marker;
|
||||
case 2: return format::uint16::marker;
|
||||
case 4: return format::uint32::marker;
|
||||
default: return format::uint64::marker;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct builtin_pack_helper<format::type::signed_int> {
|
||||
static constexpr std::size_t rep_bytes(std::int64_t value) noexcept {
|
||||
// Probably a better way to do this.
|
||||
if (value < 0 && value >= -32) return 0;
|
||||
auto underlying = static_cast<std::uint64_t>(value);
|
||||
|
||||
// save a branch; these should be cheap on modern hardware.
|
||||
std::uint64_t counts[2] = {
|
||||
static_cast<std::uint64_t>(std::countl_zero(underlying)),
|
||||
static_cast<std::uint64_t>(std::countl_one(underlying))};
|
||||
|
||||
std::uint64_t width = 1 + std::numeric_limits<std::uint64_t>::digits
|
||||
- counts[underlying >> 63];
|
||||
|
||||
return std::bit_ceil((width + 7) >> 3);
|
||||
}
|
||||
|
||||
static constexpr std::byte marker(std::int64_t value) noexcept {
|
||||
switch (rep_bytes(value)) {
|
||||
case 0: return static_cast<std::byte>(value);
|
||||
case 1: return format::int8::marker;
|
||||
case 2: return format::int16::marker;
|
||||
case 4: return format::int32::marker;
|
||||
default: return format::int64::marker;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct builtin_pack_helper<format::type::nil> {
|
||||
static constexpr std::size_t rep_bytes(nil) noexcept { return 0; }
|
||||
|
||||
static constexpr std::byte marker(nil) noexcept {
|
||||
return format::nil::marker;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct builtin_pack_helper<format::type::invalid> {
|
||||
static constexpr std::size_t rep_bytes(invalid) noexcept { return 0; }
|
||||
|
||||
static constexpr std::byte marker(invalid) noexcept {
|
||||
return format::invalid::marker;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept byte_range = std::ranges::contiguous_range<T>
|
||||
&& std::same_as<std::ranges::range_value_t<T>, std::byte>;
|
||||
|
||||
template <>
|
||||
struct builtin_pack_helper<format::type::binary> {
|
||||
static constexpr uint32_t max_payload_length = 4;
|
||||
|
||||
static constexpr std::size_t rep_bytes(
|
||||
byte_range auto const& value) noexcept {
|
||||
return std::ranges::size(value);
|
||||
}
|
||||
|
||||
static constexpr std::size_t len_bytes(
|
||||
byte_range auto const& value) noexcept {
|
||||
auto const len = rep_bytes(value);
|
||||
auto const rounded_bytes = std::bit_ceil(static_cast<std::uint32_t>(
|
||||
((std::bit_width(std::ranges::size(value)) + 7) >> 3)));
|
||||
return std::min(max_payload_length, rounded_bytes);
|
||||
}
|
||||
|
||||
static constexpr auto marker(byte_range auto const& value) noexcept {
|
||||
switch (len_bytes(value)) {
|
||||
case 1: return format::bin8::marker;
|
||||
case 2: return format::bin16::marker;
|
||||
case 4:
|
||||
default: return format::bin32::marker;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct builtin_pack_helper<format::type::string> {
|
||||
static constexpr uint32_t max_payload_length = 4;
|
||||
|
||||
static constexpr std::size_t rep_bytes(auto const& value) noexcept {
|
||||
return std::size(value);
|
||||
}
|
||||
|
||||
static constexpr std::size_t len_bytes(auto const& value) noexcept {
|
||||
auto const len = std::size(value);
|
||||
if (len <= std::uint32_t(format::fixstr::mask)) return 0;
|
||||
return std::min(max_payload_length,
|
||||
std::bit_ceil(std::uint32_t((std::bit_width(len) + 7) >> 3)));
|
||||
}
|
||||
|
||||
static constexpr auto marker(auto const& value) noexcept {
|
||||
switch (len_bytes(value)) {
|
||||
case 0:
|
||||
return format::fixstr::marker
|
||||
| static_cast<std::byte>(std::size(value));
|
||||
case 1: return format::str8::marker;
|
||||
case 2: return format::str16::marker;
|
||||
case 4:
|
||||
default: return format::str32::marker;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct builtin_pack_helper<format::type::boolean> {
|
||||
static constexpr std::size_t rep_bytes(auto const&) noexcept { return 0; }
|
||||
|
||||
static constexpr auto marker(auto const& value) noexcept {
|
||||
return format::boolean::marker
|
||||
| (value ? std::byte{0x01} : std::byte{});
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <format::type F>
|
||||
struct builtin_packer_base {
|
||||
static constexpr auto format = F;
|
||||
using helper = detail::builtin_pack_helper<F>;
|
||||
|
||||
static constexpr std::size_t rep_size(auto const& value) noexcept {
|
||||
return helper::rep_bytes(value);
|
||||
}
|
||||
|
||||
static constexpr std::size_t len_size(auto const& value) noexcept {
|
||||
if constexpr (requires { helper::len_bytes(value); }) {
|
||||
return helper::len_bytes(value);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr auto total_size(auto const& value) noexcept {
|
||||
return 1 + len_size(value) + rep_size(value);
|
||||
}
|
||||
|
||||
static constexpr auto marker(auto const& value) noexcept {
|
||||
return helper::marker(value);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename>
|
||||
struct builtin_packer;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Built-in packer specializations
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <>
|
||||
struct builtin_packer<invalid> : builtin_packer_base<format::type::invalid> {
|
||||
static constexpr auto pack(auto, auto out) noexcept -> decltype(out) {
|
||||
*out++ = format::invalid::marker;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct builtin_packer<nil> : builtin_packer_base<format::type::nil> {
|
||||
static constexpr auto pack(auto, auto out) noexcept -> decltype(out) {
|
||||
*out++ = format::nil::marker;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct builtin_packer<bool> : builtin_packer_base<format::type::boolean> {
|
||||
static constexpr auto pack(bool value, auto out) noexcept -> decltype(out) {
|
||||
*out++ = marker(value);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template <std::unsigned_integral T>
|
||||
struct builtin_packer<T> : builtin_packer_base<format::type::unsigned_int> {
|
||||
static constexpr auto pack(T const& value, auto out) noexcept
|
||||
-> decltype(out) {
|
||||
*out++ = marker(value);
|
||||
return detail::pack_integral(value, out, rep_size(value));
|
||||
}
|
||||
};
|
||||
|
||||
template <std::signed_integral T>
|
||||
struct builtin_packer<T> : builtin_packer_base<format::type::signed_int> {
|
||||
static constexpr auto pack(T const& value, auto out) noexcept
|
||||
-> decltype(out) {
|
||||
*out++ = marker(value);
|
||||
return detail::pack_integral(value, out, rep_size(value));
|
||||
}
|
||||
};
|
||||
|
||||
template <detail::byte_range T>
|
||||
struct builtin_packer<T> : builtin_packer_base<format::type::binary> {
|
||||
static constexpr auto pack(T const& value, auto out) noexcept
|
||||
-> decltype(out) {
|
||||
*out++ = marker(value);
|
||||
out = detail::pack_integral(
|
||||
std::ranges::size(value), out, len_size(value));
|
||||
return std::ranges::copy(value, out).out;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct builtin_packer<std::string_view>
|
||||
: builtin_packer_base<format::type::string> {
|
||||
static constexpr auto pack(std::string_view value, auto out) noexcept
|
||||
-> decltype(out) {
|
||||
*out++ = marker(value);
|
||||
auto const size = std::size(value);
|
||||
out = detail::pack_integral(size, out, len_size(value));
|
||||
auto const* beg = reinterpret_cast<std::byte const*>(&*value.begin());
|
||||
return std::ranges::copy(beg, beg + size, out).out;
|
||||
}
|
||||
};
|
||||
|
||||
template <std::convertible_to<std::string_view> T>
|
||||
struct builtin_packer<T> : builtin_packer<std::string_view> {};
|
||||
|
||||
template <>
|
||||
struct builtin_packer<msgpack::array_desc>
|
||||
: builtin_packer_base<format::type::array> {};
|
||||
|
||||
template <>
|
||||
struct builtin_packer<msgpack::map_desc>
|
||||
: builtin_packer_base<format::type::map> {};
|
||||
|
||||
// Requirements for builtin_packable types.
|
||||
template <typename T>
|
||||
concept builtin_packable = requires(T const& value, std::byte* dest) {
|
||||
{ builtin_packer<T>::total_size(value) } -> std::same_as<std::size_t>;
|
||||
{ builtin_packer<T>::pack(value, dest) } -> std::same_as<decltype(dest)>;
|
||||
};
|
||||
|
||||
} // namespace msgpack
|
||||
|
||||
#endif // msgpack_core_detail_builtin_packable_types_41bd88d512497527
|
||||
42
include/parselink/msgpack/core/detail/packable_ranges.h
Normal file
42
include/parselink/msgpack/core/detail/packable_ranges.h
Normal file
@ -0,0 +1,42 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// ___ __ _ _
|
||||
// / _ \__ _ _ __ ___ ___ / /(_)_ __ | | __
|
||||
// / /_)/ _` | '__/ __|/ _ \/ / | | '_ \| |/ /
|
||||
// / ___/ (_| | | \__ \ __/ /__| | | | | <
|
||||
// \/ \__,_|_| |___/\___\____/_|_| |_|_|\_\ .
|
||||
//
|
||||
//-----------------------------------------------------------------------------
|
||||
// Author: Kurt Sassenrath
|
||||
// Module: msgpack
|
||||
//
|
||||
// Support for packing ranges of packable types into array/map formats.
|
||||
//
|
||||
// If the range size is known at compile time or can be queried at runtime, it
|
||||
// will be leveraged to find the most efficient version of the format.
|
||||
//
|
||||
// Copyright (c) 2023 Kurt Sassenrath.
|
||||
//
|
||||
// License TBD.
|
||||
//-----------------------------------------------------------------------------
|
||||
#ifndef msgpack_core_detail_packable_ranges_10d28c24498828c7
|
||||
#define msgpack_core_detail_packable_ranges_10d28c24498828c7
|
||||
|
||||
#include "builtin_packable_types.h"
|
||||
#include <ranges>
|
||||
|
||||
namespace msgpack {
|
||||
namespace detail {} // namespace detail
|
||||
|
||||
template <typename T>
|
||||
concept packable_range = std::ranges::input_range<T>
|
||||
&& builtin_packable<std::ranges::range_value_t<T>>;
|
||||
|
||||
static_assert(packable_range<std::array<std::string_view, 5>>);
|
||||
static_assert(packable_range<std::span<std::string_view>>);
|
||||
|
||||
template <packable_range T>
|
||||
struct builtin_packer<T> {};
|
||||
|
||||
} // namespace msgpack
|
||||
|
||||
#endif // msgpack_core_detail_packable_ranges_10d28c24498828c7
|
||||
@ -38,6 +38,7 @@
|
||||
#ifndef msgpack_core_packer_1d5939e9c1498568
|
||||
#define msgpack_core_packer_1d5939e9c1498568
|
||||
|
||||
#include "parselink/msgpack/core/detail/builtin_packable_types.h"
|
||||
#include "parselink/msgpack/core/error.h"
|
||||
#include "parselink/msgpack/core/format.h"
|
||||
#include "parselink/msgpack/util/endianness.h"
|
||||
@ -49,244 +50,14 @@
|
||||
|
||||
namespace msgpack {
|
||||
|
||||
namespace detail {
|
||||
|
||||
// This is a generic helper function for writing integral bytes.
|
||||
template <typename T, typename Itr>
|
||||
constexpr auto write_integral(T value, Itr out, std::size_t sz) noexcept {
|
||||
auto bytes = ::detail::as_bytes(host_to_be(value));
|
||||
for (std::size_t i = 0; i < sz; ++i) {
|
||||
*out++ = *(bytes.end() - sz + i);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// Depending on the format, a number of bytes will be necessary to represent
|
||||
// either the value (integer formats) or the length (variable length formats).
|
||||
template <format::type T>
|
||||
struct pack_helper {};
|
||||
|
||||
template <>
|
||||
struct pack_helper<format::type::unsigned_int> {
|
||||
static constexpr std::size_t num_bytes(std::uint64_t value) noexcept {
|
||||
#ifdef PARSELINK_CFG_MSGPACK_PACKER_USE_BRANCHES
|
||||
if (count <= 0x7f) return 0;
|
||||
if (count < 0x100) return 1;
|
||||
if (count < 0x10000) return 2;
|
||||
if (count < 0x100000000) return 4;
|
||||
return 8;
|
||||
#else
|
||||
if (value <= std::uint64_t(format::positive_fixint::mask)) return 0;
|
||||
return std::bit_ceil(std::uint64_t((std::bit_width(value) + 7) >> 3));
|
||||
#endif
|
||||
}
|
||||
|
||||
static constexpr std::byte marker(std::uint64_t value) noexcept {
|
||||
switch (num_bytes(value)) {
|
||||
case 0: return static_cast<std::byte>(value);
|
||||
case 1: return format::uint8::marker;
|
||||
case 2: return format::uint16::marker;
|
||||
case 4: return format::uint32::marker;
|
||||
default: return format::uint64::marker;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct pack_helper<format::type::signed_int> {
|
||||
static constexpr std::size_t num_bytes(std::int64_t value) noexcept {
|
||||
// Probably a better way to do this.
|
||||
if (value < 0 && value >= -32) return 0;
|
||||
auto underlying = static_cast<std::uint64_t>(value);
|
||||
|
||||
// save a branch; these should be cheap on modern hardware.
|
||||
std::uint64_t counts[2] = {
|
||||
static_cast<std::uint64_t>(std::countl_zero(underlying)),
|
||||
static_cast<std::uint64_t>(std::countl_one(underlying))};
|
||||
|
||||
std::uint64_t width = 1 + std::numeric_limits<std::uint64_t>::digits
|
||||
- counts[underlying >> 63];
|
||||
|
||||
return std::bit_ceil((width + 7) >> 3);
|
||||
}
|
||||
|
||||
static constexpr std::byte marker(std::int64_t value) noexcept {
|
||||
switch (num_bytes(value)) {
|
||||
case 0: return static_cast<std::byte>(value);
|
||||
case 1: return format::int8::marker;
|
||||
case 2: return format::int16::marker;
|
||||
case 4: return format::int32::marker;
|
||||
default: return format::int64::marker;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct pack_helper<format::type::binary> {
|
||||
static constexpr uint32_t max_supported_payload = 4;
|
||||
|
||||
static constexpr std::size_t num_bytes(
|
||||
std::span<std::byte const> value) noexcept {
|
||||
return std::min(max_supported_payload,
|
||||
std::bit_ceil(std::uint32_t(
|
||||
((std::bit_width(value.size()) + 7) >> 3))));
|
||||
}
|
||||
|
||||
static constexpr auto marker(std::span<std::byte const> value) noexcept {
|
||||
auto const len = num_bytes(value);
|
||||
switch (len) {
|
||||
case 1: return format::bin8::marker;
|
||||
case 2: return format::bin16::marker;
|
||||
case 4:
|
||||
default: return format::bin32::marker;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct pack_helper<format::type::array> {
|
||||
static constexpr uint32_t max_supported_payload = 4;
|
||||
|
||||
static constexpr std::size_t num_bytes(std::size_t count) noexcept {
|
||||
if (count < 16) return 0;
|
||||
return std::min(max_supported_payload,
|
||||
std::bit_ceil(std::uint32_t(
|
||||
((std::bit_width(count) + 15) >> 4) << 1)));
|
||||
}
|
||||
|
||||
static constexpr auto marker(std::size_t value) noexcept {
|
||||
auto const len = num_bytes(value);
|
||||
switch (len) {
|
||||
case 0:
|
||||
case 2: return format::array16::marker;
|
||||
case 4:
|
||||
default: return format::bin32::marker;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constexpr auto x = pack_helper<format::type::array>::num_bytes(15);
|
||||
constexpr auto y = pack_helper<format::type::array>::num_bytes(16);
|
||||
constexpr auto z = pack_helper<format::type::array>::num_bytes(256);
|
||||
constexpr auto a = pack_helper<format::type::array>::num_bytes(65536);
|
||||
|
||||
template <>
|
||||
struct pack_helper<format::type::string> {
|
||||
static constexpr uint32_t max_supported_payload = 4;
|
||||
|
||||
static constexpr std::size_t num_bytes(std::string_view value) noexcept {
|
||||
auto const len = value.size();
|
||||
if (len <= std::uint32_t(format::fixstr::mask)) return 0;
|
||||
return std::min(max_supported_payload,
|
||||
std::bit_ceil(std::uint32_t((std::bit_width(len) + 7) >> 3)));
|
||||
}
|
||||
|
||||
static constexpr std::size_t payload_size(std::string_view value) noexcept {
|
||||
return value.size();
|
||||
}
|
||||
|
||||
static constexpr auto marker(std::string_view value) noexcept {
|
||||
auto const len = num_bytes(value);
|
||||
switch (len) {
|
||||
case 0: return format::fixstr::marker | std::byte(value.size());
|
||||
case 1: return format::str8::marker;
|
||||
case 2: return format::str16::marker;
|
||||
case 4:
|
||||
default: return format::str32::marker;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// Pack adapter is the basis for packing native values into MessagePack format.
|
||||
template <typename T>
|
||||
struct pack_adapter {};
|
||||
|
||||
template <typename T>
|
||||
concept basic_packable_type = requires(T const& t, std::byte* b) {
|
||||
{ pack_adapter<T>::total_size(t) } -> std::same_as<std::size_t>;
|
||||
{ pack_adapter<T>::write(t, b) } -> std::same_as<decltype(b)>;
|
||||
{ pack_adapter<T>::marker(t) } -> std::same_as<std::byte>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept packable_type = basic_packable_type<T>;
|
||||
|
||||
template <format::type F>
|
||||
struct builtin_pack_adapter {
|
||||
static constexpr auto format_type = F;
|
||||
using pack_helper = detail::pack_helper<F>;
|
||||
|
||||
// Retrieves the number of bytes to store the format, aside from the marker
|
||||
// byte.
|
||||
static constexpr auto format_size(auto value) noexcept {
|
||||
return pack_helper::num_bytes(value);
|
||||
}
|
||||
|
||||
// Retrieves the number of bytes to store the payload, if it is
|
||||
// variable-length.
|
||||
static constexpr std::size_t payload_size(auto value) noexcept {
|
||||
if constexpr (requires { pack_helper::payload_size(value); }) {
|
||||
return pack_helper::payload_size(value);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr auto total_size(auto value) noexcept {
|
||||
return 1 + format_size(value) + payload_size(value);
|
||||
}
|
||||
|
||||
static constexpr auto marker(auto value) noexcept {
|
||||
return detail::pack_helper<F>::marker(value);
|
||||
}
|
||||
};
|
||||
|
||||
template <std::signed_integral T>
|
||||
struct pack_adapter<T> : builtin_pack_adapter<format::type::signed_int> {
|
||||
template <typename Itr>
|
||||
static constexpr Itr write(T value, Itr out) noexcept {
|
||||
return detail::write_integral(value, out, format_size(value));
|
||||
}
|
||||
};
|
||||
|
||||
template <std::unsigned_integral T>
|
||||
struct pack_adapter<T> : builtin_pack_adapter<format::type::unsigned_int> {
|
||||
template <typename Itr>
|
||||
static constexpr Itr write(T value, Itr out) noexcept {
|
||||
return detail::write_integral(value, out, format_size(value));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct pack_adapter<std::string_view>
|
||||
: builtin_pack_adapter<format::type::string> {
|
||||
template <typename Itr>
|
||||
static constexpr Itr write(std::string_view value, Itr out) noexcept {
|
||||
out = detail::write_integral(value.size(), out, format_size(value));
|
||||
auto const* beg = reinterpret_cast<std::byte const*>(&*value.begin());
|
||||
std::copy(beg, beg + value.size(), out);
|
||||
return out + value.size();
|
||||
}
|
||||
};
|
||||
|
||||
template <std::convertible_to<std::string_view> T>
|
||||
struct pack_adapter<T> : pack_adapter<std::string_view> {};
|
||||
|
||||
template <>
|
||||
struct pack_adapter<std::span<std::byte const>>
|
||||
: builtin_pack_adapter<format::type::binary> {
|
||||
template <typename Itr>
|
||||
static constexpr Itr write(
|
||||
std::span<std::byte const> value, Itr out) noexcept {
|
||||
out = detail::write_integral(value.size(), out, format_size(value));
|
||||
std::copy(value.begin(), value.end(), out);
|
||||
return out + value.size();
|
||||
}
|
||||
};
|
||||
concept packable_type = builtin_packable<T>;
|
||||
|
||||
class packer {
|
||||
// Replace with output_range?
|
||||
using BufferType = std::span<std::byte>;
|
||||
|
||||
public:
|
||||
@ -299,15 +70,13 @@ public:
|
||||
constexpr tl::expected<tl::monostate, error> pack(T&& v) noexcept {
|
||||
using diff_type =
|
||||
std::iterator_traits<decltype(curr_)>::difference_type;
|
||||
diff_type const space_needed = pack_adapter<T>::total_size(v);
|
||||
diff_type const space_needed = builtin_packer<T>::total_size(v);
|
||||
if (space_needed > std::distance(curr_, end_)) {
|
||||
return tl::make_unexpected(error::out_of_space);
|
||||
} else {
|
||||
curr_ = builtin_packer<T>::pack(v, curr_);
|
||||
return tl::monostate{};
|
||||
}
|
||||
*curr_++ = pack_adapter<T>::marker(v);
|
||||
if (space_needed > 1) {
|
||||
curr_ = pack_adapter<T>::write(v, curr_);
|
||||
}
|
||||
return tl::monostate{};
|
||||
}
|
||||
|
||||
constexpr auto tell() const noexcept {
|
||||
|
||||
@ -22,11 +22,28 @@
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
#include "rng.h"
|
||||
#include <algorithm>
|
||||
#include <boost/ut.hpp>
|
||||
#include <chrono>
|
||||
|
||||
using namespace boost::ut;
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<msgpack::invalid> : fmt::formatter<std::string_view> {
|
||||
template <typename FormatContext>
|
||||
auto format(msgpack::nil const&, FormatContext& ctx) const {
|
||||
return fmt::format_to(ctx.out(), "{{msgpack::invalid}}");
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<msgpack::nil> : fmt::formatter<std::string_view> {
|
||||
template <typename FormatContext>
|
||||
auto format(msgpack::nil const&, FormatContext& ctx) const {
|
||||
return fmt::format_to(ctx.out(), "{{msgpack::nil}}");
|
||||
}
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename... Bytes>
|
||||
@ -41,6 +58,24 @@ constexpr auto equal(auto a, auto b) {
|
||||
template <typename T>
|
||||
struct test_data {};
|
||||
|
||||
template <>
|
||||
struct test_data<msgpack::nil> {
|
||||
static constexpr msgpack::nil values[] = {{}};
|
||||
static constexpr auto payload = make_bytes(0xc0);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct test_data<msgpack::invalid> {
|
||||
static constexpr msgpack::invalid values[] = {{}};
|
||||
static constexpr auto payload = make_bytes(0xc1);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct test_data<bool> {
|
||||
static constexpr auto values = std::to_array<bool>({false, true});
|
||||
static constexpr auto payload = make_bytes(0xc2, 0xc3);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct test_data<std::uint64_t> {
|
||||
static constexpr auto values = std::to_array<std::uint64_t>({
|
||||
@ -185,7 +220,9 @@ bool test_deduced() noexcept {
|
||||
|
||||
msgpack::packer packer(payload);
|
||||
for (auto const& value : test_data<T>::values) {
|
||||
if (!test_data<T>::valid(value)) break;
|
||||
if constexpr (requires { test_data<T>::valid(value); }) {
|
||||
if (!test_data<T>::valid(value)) break;
|
||||
}
|
||||
expect(!!packer.pack(T(value)));
|
||||
auto expect = std::span(expected_payload.begin(), packer.tell());
|
||||
auto correct = equal(packer.subspan(), expect);
|
||||
@ -202,6 +239,13 @@ bool test_deduced() noexcept {
|
||||
} // anonymous namespace
|
||||
|
||||
suite packer = [] {
|
||||
"packer::pack<bool>"_test = [] { expect(test_deduced<bool>()); };
|
||||
"packer::pack<nil>"_test = [] { expect(test_deduced<msgpack::nil>()); };
|
||||
"packer::pack<invalid>"_test = [] {
|
||||
expect(test_deduced<msgpack::invalid>());
|
||||
};
|
||||
|
||||
// Unsigned ints
|
||||
"packer::pack<std::uint8_t>"_test = [] {
|
||||
expect(test_deduced<std::uint8_t>());
|
||||
};
|
||||
@ -215,6 +259,7 @@ suite packer = [] {
|
||||
expect(test_deduced<std::uint64_t>());
|
||||
};
|
||||
|
||||
// Signed ints
|
||||
"packer::pack<std::int8_t>"_test = [] {
|
||||
expect(test_deduced<std::int8_t>());
|
||||
};
|
||||
@ -228,60 +273,50 @@ suite packer = [] {
|
||||
expect(test_deduced<std::int64_t>());
|
||||
};
|
||||
|
||||
// Strings
|
||||
"packer::pack<std::string_view>"_test = [] {
|
||||
expect(test_deduced<std::string_view>());
|
||||
};
|
||||
#if 0
|
||||
"packer::pack<char const*>"_test = [] {
|
||||
expect(test_deduced<char const*>());
|
||||
};
|
||||
"packer::pack<std::array<std::byte>>"_test = [] {
|
||||
#endif
|
||||
"packer::pack<std::span<std::byte>>"_test = [] {
|
||||
expect(test_deduced<std::span<std::byte const>>());
|
||||
};
|
||||
};
|
||||
|
||||
static constexpr auto num_bytes(std::uint64_t count) {
|
||||
if (count <= 0x7f) return 0;
|
||||
if (count < 0x100) return 1;
|
||||
if (count < 0x10000) return 2;
|
||||
if (count < 0x100000000) return 4;
|
||||
return 8;
|
||||
}
|
||||
"packer::pack<std::array<std::byte, N>>"_test = [] {
|
||||
{
|
||||
constexpr auto data = make_bytes(0x01, 0x02, 0x03, 0x04);
|
||||
constexpr auto expected =
|
||||
make_bytes(0xc4, 0x04, 0x01, 0x02, 0x03, 0x04);
|
||||
std::array<std::byte, std::size(expected)> payload;
|
||||
|
||||
static constexpr std::size_t bit_count_bytes(std::uint64_t count) {
|
||||
if (count <= 0x7f) return 0;
|
||||
return std::bit_ceil(std::uint64_t((std::bit_width(count) + 7) >> 3));
|
||||
}
|
||||
msgpack::packer packer(payload);
|
||||
expect(!!packer.pack(data));
|
||||
expect(std::ranges::equal(payload, expected));
|
||||
}
|
||||
{
|
||||
constexpr std::array<std::byte, 256> data{};
|
||||
constexpr std::array<std::byte, 259> expected{
|
||||
std::byte(0xc5), std::byte(0x01), std::byte(0x00)};
|
||||
std::array<std::byte, std::size(expected)> payload;
|
||||
|
||||
suite perf = [] {
|
||||
"test"_test = [] {
|
||||
for (auto j = 0; j < 10; ++j) {
|
||||
using namespace std::chrono_literals;
|
||||
std::vector<std::uint64_t> a;
|
||||
std::vector<std::uint64_t> r1;
|
||||
std::vector<std::uint64_t> r2;
|
||||
a.reserve(10000000);
|
||||
r1.reserve(10000000);
|
||||
r2.reserve(10000000);
|
||||
rng x;
|
||||
msgpack::packer packer(payload);
|
||||
expect(!!packer.pack(data));
|
||||
expect(std::ranges::equal(payload, expected));
|
||||
}
|
||||
{
|
||||
constexpr std::array<std::byte, 65536> data{};
|
||||
constexpr std::array<std::byte, std::size(data) + 5> expected{
|
||||
std::byte(0xc6), std::byte(0x00), std::byte(0x01),
|
||||
std::byte(0x00), std::byte(0x00)};
|
||||
std::array<std::byte, std::size(expected)> payload;
|
||||
|
||||
for (auto i = 0; i < 10000000; ++i) {
|
||||
a.push_back(x.get<std::size_t>());
|
||||
}
|
||||
|
||||
auto start2 = std::chrono::steady_clock::now();
|
||||
for (auto v : a) {
|
||||
r2.push_back(num_bytes(v));
|
||||
}
|
||||
auto end2 = std::chrono::steady_clock::now();
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
for (auto v : a) {
|
||||
r1.push_back(bit_count_bytes(v));
|
||||
}
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
|
||||
fmt::print("Time taken: {}us vs {}us\n", (end - start) / 1us,
|
||||
(end2 - start2) / 1us);
|
||||
msgpack::packer packer(payload);
|
||||
expect(!!packer.pack(data));
|
||||
expect(std::ranges::equal(payload, expected));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user