Skip to content
Permalink
Browse files

utils: memcpy_archive: Improved cooperation with boost.serialization,…

… removed special overload for boost::optional
  • Loading branch information...
fweik committed Oct 30, 2019
1 parent 54eb0bb commit 344d8de332dbe15e18dc87b6277900dd1b7fac67
Showing with 110 additions and 113 deletions.
  1. +63 −80 src/utils/include/utils/serialization/memcpy_archive.hpp
  2. +47 −33 src/utils/tests/memcpy_archive_test.cpp
@@ -22,7 +22,8 @@
#include <utils/Span.hpp>

#include <boost/mpl/bool.hpp>
#include <boost/optional.hpp>
#include <boost/serialization/is_bitwise_serializable.hpp>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/serialization.hpp>

#include <cstring>
@@ -38,47 +39,26 @@ namespace Utils {
*
* @tparam T type under consideration.
*/
template <class T> struct is_statically_serializable : std::false_type {};
template <class T>
struct is_statically_serializable
: std::integral_constant<
bool, std::is_trivially_copyable<T>::value or
boost::serialization::is_bitwise_serializable<T>::value> {};

namespace detail {
/* Use memcpy for packing */
template <class T>
using use_memcpy = std::integral_constant<
bool, std::is_trivially_copyable<T>::value or
boost::serialization::is_bitwise_serializable<T>::value>;
/* Use serialize function only if the type is opt-in but not
* trivially copyable, in which case memcpy is more efficient. */
template <class T>
using use_serialize =
std::integral_constant<bool, not std::is_trivially_copyable<T>::value and
std::integral_constant<bool, not use_memcpy<T>::value and
is_statically_serializable<T>::value>;

struct SizeArchive {
using is_saving = boost::mpl::true_;
using is_loading = boost::mpl::false_;

size_t size = {};

template <typename T>
constexpr auto operator<<(T const &)
-> std::enable_if_t<std::is_trivially_copyable<T>::value> {
size += sizeof(T);
}

template <class T>
constexpr auto operator<<(T &v)
-> std::enable_if_t<detail::use_serialize<T>::value> {
boost::serialization::serialize(*this, v, 0);
}

template <class T> constexpr auto operator<<(boost::optional<T> &) {
operator<<(bool());
operator<<(T{});
}

template <class T> constexpr SizeArchive &operator&(T &t) {
operator<<(t);

return *this;
}
};

class BasicMemcpyArchive {
template <class Derived> class BasicMemcpyArchive {
/** Buffer to write to */
Utils::Span<char> buf;
/** Current position in the buffer */
@@ -88,11 +68,16 @@ class BasicMemcpyArchive {
explicit BasicMemcpyArchive(Utils::Span<char> buf)
: buf(buf), insert(buf.data()) {}

size_t get_library_version() const { return 4; }

size_t bytes_processed() const { return insert - buf.data(); }
void skip(size_t bytes) {
assert((insert + bytes) <= buf.end());
insert += bytes;
}

template <typename T>
auto operator>>(T &value)
-> std::enable_if_t<std::is_trivially_copyable<T>::value> {
auto operator>>(T &value) -> std::enable_if_t<use_memcpy<T>::value> {
/* check that there is enough space left in the buffer */
assert((insert + sizeof(T)) <= buf.end());

@@ -101,23 +86,53 @@ class BasicMemcpyArchive {
}

template <typename T>
auto operator<<(T const &value)
-> std::enable_if_t<std::is_trivially_copyable<T>::value> {
auto operator<<(T const &value) -> std::enable_if_t<use_memcpy<T>::value> {
/* check that there is enough space left in the buffer */
assert((insert + sizeof(T)) <= buf.end());
std::memcpy(insert, &value, sizeof(T));
insert += sizeof(T);
}

private:
template <class T> void process(T &value) {
auto const old_pos = insert;
boost::serialization::serialize_adl(*static_cast<Derived *>(this), value,
4);
auto const new_pos = insert;
assert((new_pos - old_pos) <= sizeof(T));

auto const padding_size = sizeof(T) - (new_pos - old_pos);
skip(padding_size);
}

public:
template <class T>
auto operator>>(T &value)
-> std::enable_if_t<detail::use_serialize<T>::value> {
process(value);
}

template <class T>
auto operator<<(T &value)
-> std::enable_if_t<detail::use_serialize<T>::value> {
process(value);
}

template <class T> void operator<<(const boost::serialization::nvp<T> &nvp) {
operator<<(nvp.const_value());
}

template <class T> void operator>>(const boost::serialization::nvp<T> &nvp) {
operator>>(nvp.value());
}

/**
* @brief Determine the static packing size of a type.
* @tparam T Type to consider.
* @return Packed size in bytes.
*/
template <class T> static size_t packing_size() {
detail::SizeArchive sa{};
T t;
return (sa & t).size;
template <class T> static constexpr size_t packing_size() {
return sizeof(T);
}
};
} // namespace detail
@@ -131,9 +146,9 @@ class BasicMemcpyArchive {
* for types that are trivially copyable, or by explicitly assuring
* this by specializing @c is_statically_serializable to std::true_type.
*/
class MemcpyIArchive : public detail::BasicMemcpyArchive {
class MemcpyIArchive : public detail::BasicMemcpyArchive<MemcpyIArchive> {
private:
using base_type = detail::BasicMemcpyArchive;
using base_type = detail::BasicMemcpyArchive<MemcpyIArchive>;

public:
using is_loading = boost::mpl::true_;
@@ -142,8 +157,7 @@ class MemcpyIArchive : public detail::BasicMemcpyArchive {
/**
* @param buf Buffer to read from.
*/
explicit MemcpyIArchive(Utils::Span<char> buf)
: detail::BasicMemcpyArchive(buf) {}
explicit MemcpyIArchive(Utils::Span<char> buf) : base_type(buf) {}

/**
* @brief Number of bytes read from the buffer.
@@ -155,25 +169,6 @@ class MemcpyIArchive : public detail::BasicMemcpyArchive {
using base_type::packing_size;
using base_type::operator>>;

template <class T>
auto operator>>(T &value)
-> std::enable_if_t<detail::use_serialize<T>::value> {
boost::serialization::serialize(*this, value, 0);
}

template <class T> void operator>>(boost::optional<T> &o) {
bool initialized{};
operator>>(initialized);
T val{};
operator>>(val);

if (initialized) {
o = val;
} else {
o = boost::none;
}
}

template <class T> MemcpyIArchive &operator&(T &value) {
operator>>(value);

@@ -186,8 +181,8 @@ class MemcpyIArchive : public detail::BasicMemcpyArchive {
*
* @copydetails MemcpyIArchive
*/
class MemcpyOArchive : public detail::BasicMemcpyArchive {
using base_type = detail::BasicMemcpyArchive;
class MemcpyOArchive : public detail::BasicMemcpyArchive<MemcpyOArchive> {
using base_type = detail::BasicMemcpyArchive<MemcpyOArchive>;

public:
using is_loading = boost::mpl::false_;
@@ -196,8 +191,7 @@ class MemcpyOArchive : public detail::BasicMemcpyArchive {
/**
* @param buf Buffer to write to.
*/
explicit MemcpyOArchive(Utils::Span<char> buf)
: detail::BasicMemcpyArchive(buf) {}
explicit MemcpyOArchive(Utils::Span<char> buf) : base_type(buf) {}

/**
* @brief Number of bytes written to the buffer.
@@ -209,17 +203,6 @@ class MemcpyOArchive : public detail::BasicMemcpyArchive {
using base_type::packing_size;
using base_type::operator<<;

template <typename T>
auto operator<<(T &value)
-> std::enable_if_t<detail::use_serialize<T>::value> {
boost::serialization::serialize(*this, value, 0);
}

template <class T> void operator<<(boost::optional<T> &o) {
operator<<(o.is_initialized());
operator<<(o.value_or(T{}));
}

template <class T> MemcpyOArchive &operator&(T &value) {
operator<<(value);

@@ -21,10 +21,14 @@
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>

#include "utils/serialization/memcpy_archive.hpp"
#include <utils/serialization/memcpy_archive.hpp>

#include <boost/optional.hpp>
#include <utils/Vector.hpp>
#include <utils/type_traits.hpp>

#include <boost/optional.hpp>
#include <boost/serialization/optional.hpp>
#include <boost/variant.hpp>

#include <vector>

@@ -38,6 +42,14 @@ using OpVec = boost::optional<Utils::Vector3d>;

namespace Utils {
template <> struct is_statically_serializable<NonTrivial> : std::true_type {};

template <class T>
struct is_statically_serializable<boost::optional<T>>
: is_statically_serializable<T> {};

template <class... T>
struct is_statically_serializable<boost::variant<T...>>
: Utils::conjunction<is_statically_serializable<T>...> {};
} // namespace Utils

BOOST_AUTO_TEST_CASE(packing_size_test) {
@@ -49,42 +61,44 @@ BOOST_AUTO_TEST_CASE(packing_size_test) {
}
}

BOOST_AUTO_TEST_CASE(optional_packing) {
std::vector<char> buf(2 * Utils::MemcpyOArchive::packing_size<OpVec>());
BOOST_AUTO_TEST_CASE(type_traits) {
static_assert(Utils::is_statically_serializable<OpVec>::value, "");
static_assert(not Utils::detail::use_memcpy<OpVec>::value, "");
static_assert(Utils::detail::use_serialize<OpVec>::value, "");
}

const OpVec active = Utils::Vector3d{1., 2., 3.};
const OpVec inactive = boost::none;
{
auto oa = Utils::MemcpyOArchive{Utils::make_span(buf)};
auto in1 = active;
auto in2 = inactive;
oa << in1;
oa << in2;
BOOST_AUTO_TEST_CASE(skiping_and_position) {
std::vector<char> buf(10);

BOOST_CHECK_EQUAL(oa.bytes_written(), buf.size());
}
auto ar = Utils::MemcpyOArchive(Utils::make_span(buf));

{
auto ia = Utils::MemcpyIArchive{Utils::make_span(buf)};
OpVec out1, out2;
ia >> out1;
ia >> out2;
BOOST_CHECK_EQUAL(0, ar.bytes_processed());
ar.skip(5);
BOOST_CHECK_EQUAL(5, ar.bytes_processed());
}

BOOST_CHECK_EQUAL(ia.bytes_read(), buf.size());
BOOST_AUTO_TEST_CASE(memcpy_processing) {
std::vector<char> buf(10);

BOOST_CHECK(out1 == active);
BOOST_CHECK(out2 == inactive);
auto const test_number = 5;
auto oa = Utils::MemcpyOArchive(Utils::make_span(buf));
oa << test_number;
BOOST_CHECK_EQUAL(oa.bytes_written(), sizeof(test_number));

{
auto ia = Utils::MemcpyIArchive(Utils::make_span(buf));
int out;
ia >> out;
BOOST_CHECK_EQUAL(out, test_number);
BOOST_CHECK_EQUAL(ia.bytes_read(), sizeof(test_number));
}
}

BOOST_AUTO_TEST_CASE(non_trivial_packing) {
static_assert(not std::is_trivially_copyable<NonTrivial>::value, "");

std::vector<char> buf(2 * Utils::MemcpyOArchive::packing_size<NonTrivial>());

auto const active = NonTrivial{Utils::Vector3d{1., 2., 3.}};
auto const inactive = NonTrivial{boost::none};
BOOST_AUTO_TEST_CASE(serializaton_processing) {
std::vector<char> buf(2 * Utils::MemcpyOArchive::packing_size<OpVec>());

const OpVec active = Utils::Vector3d{1., 2., 3.};
const OpVec inactive = boost::none;
{
auto oa = Utils::MemcpyOArchive{Utils::make_span(buf)};
auto in1 = active;
@@ -97,13 +111,13 @@ BOOST_AUTO_TEST_CASE(non_trivial_packing) {

{
auto ia = Utils::MemcpyIArchive{Utils::make_span(buf)};
NonTrivial out1, out2;
OpVec out1, out2;
ia >> out1;
ia >> out2;

BOOST_CHECK_EQUAL(ia.bytes_read(), buf.size());

BOOST_CHECK(out1.ov == active.ov);
BOOST_CHECK(out2.ov == inactive.ov);
BOOST_CHECK(out1 == active);
BOOST_CHECK(out2 == inactive);
}
}
}

0 comments on commit 344d8de

Please sign in to comment.
You can’t perform that action at this time.