diff --git a/src/core/ghosts.cpp b/src/core/ghosts.cpp index 6710b76e020..f27f1ce222a 100644 --- a/src/core/ghosts.cpp +++ b/src/core/ghosts.cpp @@ -34,11 +34,15 @@ #include "errorhandling.hpp" #include "particle_data.hpp" -#include #include #include +#include + +#include +#include #include +#include #include #include #include @@ -76,71 +80,6 @@ class CommBuf { std::vector bondbuf; //< Buffer for bond lists }; -/** - * Adapter to CommBuf that allows putting in and getting out data. - * Note: The underlying buffer (span) must be large enough. This class - * does *not* resize the buffer. - */ -struct Archiver { -private: - Utils::Span buf; - char *insert; - -public: - explicit Archiver(Utils::Span buf) : buf(buf), insert(buf.data()) {} - ~Archiver() { assert(insert - buf.data() == buf.size()); } - - template ::value>::type> - void operator<<(const T &value) { - std::memcpy(insert, &value, sizeof(T)); - insert += sizeof(T); - } - - template ::value>::type> - void operator>>(T &value) { - std::memcpy(&value, insert, sizeof(T)); - insert += sizeof(T); - } -}; - -/** - * Adapter to CommBuf that allows putting in and getting back bond data. - * Note, this class inserts the data handed to operator<< at the end - * of the underlying buffer. It does resize the buffer. - */ -struct BondArchiver { -private: - /* underlying buffer type */ - using buffer_type = std::vector; - buffer_type &bondbuf; - - /* iterator into the underlying buffer */ - using const_iterator = buffer_type::const_iterator; - const_iterator bond_retrieve; - - // Need to save these because send buffer will invalidate cb.bondbuffer. - const const_iterator initial_begin; - const size_t initial_size; - -public: - explicit BondArchiver(buffer_type &bondbuf) - : bondbuf(bondbuf), bond_retrieve(bondbuf.cbegin()), - initial_begin(bondbuf.cbegin()), initial_size(bondbuf.size()) {} - - ~BondArchiver() { assert(bond_retrieve == initial_begin + initial_size); } - - template inline void operator<<(const Utils::Span data) { - bondbuf.insert(bondbuf.end(), data.cbegin(), data.cend()); - } - - template inline void operator>>(Utils::Span data) { - std::copy_n(bond_retrieve, data.size(), data.begin()); - bond_retrieve += data.size(); - } -}; - /** whether the ghosts should have velocity information, e.g. for DPD or RATTLE. * You need this whenever you need the relative velocity of two particles. * NO CHANGES OF THIS VALUE OUTSIDE OF \ref on_ghost_flags_change !!!! @@ -161,34 +100,33 @@ void free_comm(GhostCommunicator *gcr) { ghost_comm.part_lists.clear(); } -static int calc_transmit_size(GhostCommunication &ghost_comm, - unsigned int data_parts) { - int n_buffer_new; +static size_t calc_transmit_size(unsigned data_parts) { + size_t size = {}; + if (data_parts & GHOSTTRANS_PROPRTS) { + size += Utils::MemcpyOArchive::packing_size(); + } + if (data_parts & GHOSTTRANS_BONDS) { + size += Utils::MemcpyOArchive::packing_size(); + } + if (data_parts & GHOSTTRANS_POSITION) + size += Utils::MemcpyOArchive::packing_size(); + if (data_parts & GHOSTTRANS_MOMENTUM) + size += Utils::MemcpyOArchive::packing_size(); + if (data_parts & GHOSTTRANS_FORCE) + size += Utils::MemcpyOArchive::packing_size(); + + return size; +} +static size_t calc_transmit_size(GhostCommunication &ghost_comm, + unsigned int data_parts) { if (data_parts & GHOSTTRANS_PARTNUM) - n_buffer_new = sizeof(int) * ghost_comm.part_lists.size(); - else { - n_buffer_new = 0; - if (data_parts & GHOSTTRANS_PROPRTS) { - n_buffer_new += sizeof(ParticleProperties); - // sending size of bond lists - if (ghosts_have_bonds) { - n_buffer_new += sizeof(int); - } - } - if (data_parts & GHOSTTRANS_POSITION) - n_buffer_new += sizeof(ParticlePosition); - if (data_parts & GHOSTTRANS_MOMENTUM) - n_buffer_new += sizeof(ParticleMomentum); - if (data_parts & GHOSTTRANS_FORCE) - n_buffer_new += sizeof(ParticleForce); - - int count = 0; - for (auto const &pl : ghost_comm.part_lists) - count += pl->n; - n_buffer_new *= count; - } - return n_buffer_new; + return sizeof(int) * ghost_comm.part_lists.size(); + + auto const n_part = boost::accumulate( + ghost_comm.part_lists, 0ul, + [](size_t sum, auto part_list) { return sum + part_list->n; }); + return n_part * calc_transmit_size(data_parts); } static void prepare_send_buffer(CommBuf &send_buffer, @@ -198,8 +136,8 @@ static void prepare_send_buffer(CommBuf &send_buffer, send_buffer.resize(calc_transmit_size(ghost_comm, data_parts)); send_buffer.bonds().clear(); - auto archiver = Archiver{Utils::make_span(send_buffer)}; - auto bond_archiver = BondArchiver{send_buffer.bonds()}; + auto archiver = Utils::MemcpyOArchive{Utils::make_span(send_buffer)}; + auto bond_buffer = std::back_inserter(send_buffer.bonds()); /* put in data */ for (auto part_list : ghost_comm.part_lists) { @@ -207,13 +145,9 @@ static void prepare_send_buffer(CommBuf &send_buffer, int np = part_list->n; archiver << np; } else { - for (Particle const &part : part_list->particles()) { + for (Particle &part : part_list->particles()) { if (data_parts & GHOSTTRANS_PROPRTS) { archiver << part.p; - if (ghosts_have_bonds) { - archiver << static_cast(part.bl.n); - bond_archiver << Utils::make_const_span(part.bl); - } } if (data_parts & GHOSTTRANS_POSITION) { /* ok, this is not nice, but perhaps fast */ @@ -227,9 +161,15 @@ static void prepare_send_buffer(CommBuf &send_buffer, if (data_parts & GHOSTTRANS_FORCE) { archiver << part.f; } + if (data_parts & GHOSTTRANS_BONDS) { + archiver << part.bl.n; + boost::copy(part.bl, bond_buffer); + } } } } + + assert(archiver.bytes_written() == send_buffer.size()); } static void prepare_ghost_cell(Cell *cell, int size) { @@ -270,8 +210,8 @@ static void put_recv_buffer(CommBuf &recv_buffer, GhostCommunication &ghost_comm, unsigned int data_parts) { /* put back data */ - auto archiver = Archiver{Utils::make_span(recv_buffer)}; - auto bond_archiver = BondArchiver{recv_buffer.bonds()}; + auto archiver = Utils::MemcpyIArchive{Utils::make_span(recv_buffer)}; + auto bond_buffer = recv_buffer.bonds().begin(); for (auto part_list : ghost_comm.part_lists) { if (data_parts & GHOSTTRANS_PARTNUM) { @@ -282,12 +222,6 @@ static void put_recv_buffer(CommBuf &recv_buffer, for (Particle &part : part_list->particles()) { if (data_parts & GHOSTTRANS_PROPRTS) { archiver >> part.p; - if (ghosts_have_bonds) { - int n_bonds; - archiver >> n_bonds; - part.bl.resize(n_bonds); - bond_archiver >> Utils::make_span(part.bl); - } if (local_particles[part.p.identity] == nullptr) { local_particles[part.p.identity] = ∂ } @@ -301,17 +235,26 @@ static void put_recv_buffer(CommBuf &recv_buffer, if (data_parts & GHOSTTRANS_FORCE) { archiver >> part.f; } + if (data_parts & GHOSTTRANS_BONDS) { + decltype(part.bl.n) n_bonds; + archiver >> n_bonds; + part.bl.resize(n_bonds); + std::copy_n(bond_buffer, n_bonds, part.bl.begin()); + bond_buffer += n_bonds; + } } } } + assert(archiver.bytes_read() == recv_buffer.size()); + recv_buffer.bonds().clear(); } static void add_forces_from_recv_buffer(CommBuf &recv_buffer, GhostCommunication &ghost_comm) { /* put back data */ - auto archiver = Archiver{Utils::make_span(recv_buffer)}; + auto archiver = Utils::MemcpyIArchive{Utils::make_span(recv_buffer)}; for (auto &part_list : ghost_comm.part_lists) { for (Particle &part : part_list->particles()) { ParticleForce pf; @@ -338,9 +281,9 @@ static void cell_cell_transfer(GhostCommunication &ghost_comm, Particle &part2 = dst_list->part[p]; if (data_parts & GHOSTTRANS_PROPRTS) { part2.p = part1.p; - if (ghosts_have_bonds) { - part2.bl = part1.bl; - } + } + if (data_parts & GHOSTTRANS_BONDS) { + part2.bl = part1.bl; } if (data_parts & GHOSTTRANS_POSITION) { /* ok, this is not nice, but perhaps fast */ @@ -390,6 +333,9 @@ void ghost_communicator(GhostCommunicator *gcr, unsigned int data_parts) { if (ghosts_have_v && (data_parts & GHOSTTRANS_POSITION)) data_parts |= GHOSTTRANS_MOMENTUM; + if (ghosts_have_bonds && (data_parts & GHOSTTRANS_PROPRTS)) + data_parts |= GHOSTTRANS_BONDS; + for (auto it = gcr->comm.begin(); it != gcr->comm.end(); ++it) { GhostCommunication &ghost_comm = *it; int const comm_type = ghost_comm.type & GHOST_JOBMASK; diff --git a/src/core/ghosts.hpp b/src/core/ghosts.hpp index dc2c4f58d6c..f97f6d78128 100644 --- a/src/core/ghosts.hpp +++ b/src/core/ghosts.hpp @@ -134,7 +134,8 @@ enum : unsigned { /// transfer \ref ParticleForce GHOSTTRANS_FORCE = 16u, /// resize the receiver particle arrays to the size of the senders - GHOSTTRANS_PARTNUM = 64u + GHOSTTRANS_PARTNUM = 64u, + GHOSTTRANS_BONDS = 128u }; /** \name Data Types */ diff --git a/src/core/unit_tests/Particle_test.cpp b/src/core/unit_tests/Particle_test.cpp index bf22d8f1ba7..59cbd0cc3b7 100644 --- a/src/core/unit_tests/Particle_test.cpp +++ b/src/core/unit_tests/Particle_test.cpp @@ -29,6 +29,8 @@ #include #include +#include + #include "Particle.hpp" #include "serialization/Particle.hpp" @@ -81,3 +83,37 @@ BOOST_AUTO_TEST_CASE(serialization) { BOOST_CHECK(q.el == el); #endif } + +namespace Utils { +template <> +struct is_statically_serializable : std::true_type {}; +} // namespace Utils + +BOOST_AUTO_TEST_CASE(properties_serialization) { + auto const expected_size = + Utils::MemcpyOArchive::packing_size(); + + BOOST_CHECK_LE(expected_size, sizeof(ParticleProperties)); + + std::vector buf(expected_size); + + auto prop = ParticleProperties{}; + prop.identity = 1234; + + { + auto oa = Utils::MemcpyOArchive{Utils::make_span(buf)}; + + oa << prop; + + BOOST_CHECK_EQUAL(oa.bytes_written(), expected_size); + } + + { + auto ia = Utils::MemcpyIArchive{Utils::make_span(buf)}; + ParticleProperties out; + + ia >> out; + BOOST_CHECK_EQUAL(ia.bytes_read(), expected_size); + BOOST_CHECK_EQUAL(out.identity, prop.identity); + } +} \ No newline at end of file diff --git a/src/python/pypresso.cmakein b/src/python/pypresso.cmakein index 8476da0fa64..019af45c477 100755 --- a/src/python/pypresso.cmakein +++ b/src/python/pypresso.cmakein @@ -14,13 +14,13 @@ else fi export PYTHONPATH -if [ "@CMAKE_CXX_COMPILER_ID@" != "GNU" -a "@WITH_ASAN@" = "ON" ]; then +if [ "@CMAKE_CXX_COMPILER_ID@" != "GNU" ] && [ "@WITH_ASAN@" = "ON" ]; then asan_lib=$("@CMAKE_CXX_COMPILER@" /dev/null -### -o /dev/null -fsanitize=address 2>&1 | grep -o '[" ][^" ]*libclang_rt.asan[^" ]*[^s][" ]' | sed 's/[" ]//g' | sed 's/\.a$/.so/g') for lib in $asan_lib; do test -f $lib && LD_PRELOAD="$lib $LD_PRELOAD" done fi -if [ "@CMAKE_CXX_COMPILER_ID@" != "GNU" -a "@WITH_UBSAN@" = "ON" -a "@WITH_ASAN@" != "ON" ]; then +if [ "@CMAKE_CXX_COMPILER_ID@" != "GNU" ] && [ "@WITH_UBSAN@" = "ON" ] && [ "@WITH_ASAN@" != "ON" ]; then ubsan_lib=$("@CMAKE_CXX_COMPILER@" /dev/null -### -o /dev/null -fsanitize=undefined 2>&1 | grep -o '[" ][^" ]*libclang_rt.ubsan[^" ]*[^s][" ]' | sed 's/[" ]//g' | sed 's/\.a$/.so/g') for lib in $ubsan_lib; do test -f $lib && LD_PRELOAD="$lib $LD_PRELOAD" @@ -47,57 +47,57 @@ if [ "@WITH_ASAN@" = "ON" ]; then fi export ASAN_OPTIONS fi -if [ "@WITH_MSAN@" = "ON" -a "@WARNINGS_ARE_ERRORS@" = "ON" ]; then +if [ "@WITH_MSAN@" = "ON" ] && [ "@WARNINGS_ARE_ERRORS@" = "ON" ]; then export MSAN_OPTIONS="halt_on_error=1 $MSAN_OPTIONS" fi case $1 in --gdb) shift - [ "@PYTHON_FRONTEND@" = "@IPYTHON_EXECUTABLE@" ] && exec gdb -ex "set print thread-events off" -ex "set exec-wrapper sh -c 'exec \"@IPYTHON_EXECUTABLE@\" \"\$@\"'" --args "@PYTHON_EXECUTABLE@" $@ - exec gdb --args @PYTHON_FRONTEND@ $@ + [ "@PYTHON_FRONTEND@" = "@IPYTHON_EXECUTABLE@" ] && exec gdb -ex "set print thread-events off" -ex "set exec-wrapper sh -c 'exec \"@IPYTHON_EXECUTABLE@\" \"\$@\"'" --args "@PYTHON_EXECUTABLE@" "$@" + exec gdb --args @PYTHON_FRONTEND@ "$@" ;; --lldb) shift - exec lldb -- @PYTHON_FRONTEND@ $@ + exec lldb -- @PYTHON_FRONTEND@ "$@" ;; --valgrind) shift - exec valgrind --leak-check=full @PYTHON_FRONTEND@ $@ + exec valgrind --leak-check=full @PYTHON_FRONTEND@ "$@" ;; --cuda-gdb) shift - exec cuda-gdb --args @PYTHON_FRONTEND@ $@ + exec cuda-gdb --args @PYTHON_FRONTEND@ "$@" ;; --cuda-memcheck) shift - exec cuda-memcheck @PYTHON_FRONTEND@ $@ + exec cuda-memcheck @PYTHON_FRONTEND@ "$@" ;; --gdb=*) options="${1#*=}" shift - [ "@PYTHON_FRONTEND@" = "@IPYTHON_EXECUTABLE@" ] && exec gdb -ex "set print thread-events off" -ex "set exec-wrapper sh -c 'exec \"@IPYTHON_EXECUTABLE@\" \"\$@\"'" ${options} --args "@PYTHON_EXECUTABLE@" $@ - exec gdb ${options} --args @PYTHON_FRONTEND@ $@ + [ "@PYTHON_FRONTEND@" = "@IPYTHON_EXECUTABLE@" ] && exec gdb -ex "set print thread-events off" -ex "set exec-wrapper sh -c 'exec \"@IPYTHON_EXECUTABLE@\" \"\$@\"'" ${options} --args "@PYTHON_EXECUTABLE@" "$@" + exec gdb ${options} --args @PYTHON_FRONTEND@ "$@" ;; --lldb=*) options="${1#*=}" shift - exec lldb ${options} -- @PYTHON_FRONTEND@ $@ + exec lldb ${options} -- @PYTHON_FRONTEND@ "$@" ;; --valgrind=*) options="${1#*=}" shift - exec valgrind ${options} @PYTHON_FRONTEND@ $@ + exec valgrind ${options} @PYTHON_FRONTEND@ "$@" ;; --cuda-gdb=*) options="${1#*=}" shift - exec cuda-gdb ${options} --args @PYTHON_FRONTEND@ $@ + exec cuda-gdb ${options} --args @PYTHON_FRONTEND@ "$@" ;; --cuda-memcheck=*) options="${1#*=}" shift - exec cuda-memcheck ${options} @PYTHON_FRONTEND@ $@ + exec cuda-memcheck ${options} @PYTHON_FRONTEND@ "$@" ;; *) exec @PYTHON_FRONTEND@ "$@" diff --git a/src/utils/include/utils/serialization/memcpy_archive.hpp b/src/utils/include/utils/serialization/memcpy_archive.hpp new file mode 100644 index 00000000000..4a414bb9735 --- /dev/null +++ b/src/utils/include/utils/serialization/memcpy_archive.hpp @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2019 The ESPResSo project + * + * This file is part of ESPResSo. + * + * ESPResSo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ESPResSo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#ifndef ESPRESSO_MEMCPY_ARCHIVE_HPP +#define ESPRESSO_MEMCPY_ARCHIVE_HPP + +#include + +#include +#include +#include +#include + +#include + +namespace Utils { +/** @brief Type trait to indicate that a type is + * serializable with a static size, e.g. is + * suitable for memcpy serialization. Only + * specialize this to std::true_type if it is + * guarantueed that serializing this type always + * returns the same number of bytes, independent + * of object state. + * + * @tparam T type under consideration. + */ +template +struct is_statically_serializable + : std::integral_constant< + bool, std::is_trivially_copyable::value or + boost::serialization::is_bitwise_serializable::value> {}; + +namespace detail { +/* Use memcpy for packing */ +template +using use_memcpy = std::integral_constant< + bool, std::is_trivially_copyable::value or + boost::serialization::is_bitwise_serializable::value>; +/* Use serialize function only if the type is opt-in but not + * trivially copyable, in which case memcpy is more efficient. */ +template +using use_serialize = + std::integral_constant::value and + is_statically_serializable::value>; + +template class BasicMemcpyArchive { + /** Buffer to write to */ + Utils::Span buf; + /** Current position in the buffer */ + char *insert; + +public: + explicit BasicMemcpyArchive(Utils::Span 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; + } + +private: + void read(void *data, size_t bytes) { + /* check that there is enough space left in the buffer */ + assert((insert + bytes) <= buf.end()); + std::memcpy(data, insert, bytes); + insert += bytes; + } + + void write(const void *data, size_t bytes) { + /* check that there is enough space left in the buffer */ + assert((insert + bytes) <= buf.end()); + std::memcpy(insert, data, bytes); + insert += bytes; + } + +public: + template + auto operator>>(T &value) -> std::enable_if_t::value> { + read(&value, sizeof(T)); + } + + template + auto operator<<(T const &value) -> std::enable_if_t::value> { + write(&value, sizeof(T)); + } + +private: + template void process(T &value) { + auto const old_pos = insert; + boost::serialization::serialize_adl(*static_cast(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 + auto operator>>(T &value) + -> std::enable_if_t::value> { + process(value); + } + + template + auto operator<<(T &value) + -> std::enable_if_t::value> { + process(value); + } + + template void operator<<(const boost::serialization::nvp &nvp) { + operator<<(nvp.const_value()); + } + + template void operator>>(const boost::serialization::nvp &nvp) { + operator>>(nvp.value()); + } + + /** + * @brief Determine the static packing size of a type. + * @tparam T Type to consider. + * @return Packed size in bytes. + */ + template static constexpr size_t packing_size() { + return sizeof(T); + } +}; +} // namespace detail + +/** + * @brief Archive that deserializes from a buffer via memcpy. + * + * Can only process types that have a static serialization size, + * e.g. that serialize to the same number of bytes independent of + * the state of the object. This can either be automatically detected + * for types that are trivially copyable, or by explicitly assured + * by specializing @c is_statically_serializable to std::true_type. + */ +class MemcpyIArchive : public detail::BasicMemcpyArchive { +private: + using base_type = detail::BasicMemcpyArchive; + +public: + using is_loading = boost::mpl::true_; + using is_saving = boost::mpl::false_; + + /** + * @param buf Buffer to read from. + */ + explicit MemcpyIArchive(Utils::Span buf) : base_type(buf) {} + + /** + * @brief Number of bytes read from the buffer. + * @return Number of bytes read. + */ + size_t bytes_read() const { return bytes_processed(); } + + /** @copydoc base_type::packing_size */ + using base_type::packing_size; + using base_type::operator>>; + + template MemcpyIArchive &operator&(T &value) { + operator>>(value); + + return *this; + } +}; + +/** + * @brief Archive that serializes to a buffer via memcpy. + * + * @copydetails MemcpyIArchive + */ +class MemcpyOArchive : public detail::BasicMemcpyArchive { + using base_type = detail::BasicMemcpyArchive; + +public: + using is_loading = boost::mpl::false_; + using is_saving = boost::mpl::true_; + + /** + * @param buf Buffer to write to. + */ + explicit MemcpyOArchive(Utils::Span buf) : base_type(buf) {} + + /** + * @brief Number of bytes written to the buffer. + * @return Number of bytes written. + */ + size_t bytes_written() const { return bytes_processed(); } + + /** @copydoc base_type::packing_size */ + using base_type::packing_size; + using base_type::operator<<; + + template MemcpyOArchive &operator&(T &value) { + operator<<(value); + + return *this; + } +}; +} // namespace Utils + +#endif // ESPRESSO_MEMCPY_ARCHIVE_HPP diff --git a/src/utils/tests/CMakeLists.txt b/src/utils/tests/CMakeLists.txt index 2fbe6cbd52e..d3c7380ce3e 100644 --- a/src/utils/tests/CMakeLists.txt +++ b/src/utils/tests/CMakeLists.txt @@ -40,6 +40,7 @@ unit_test(NAME quaternion_test SRC quaternion_test.cpp DEPENDS utils) unit_test(NAME mask_test SRC mask_test.cpp DEPENDS utils) unit_test(NAME type_traits_test SRC type_traits_test.cpp DEPENDS utils) unit_test(NAME uniform_test SRC uniform_test.cpp DEPENDS utils) +unit_test(NAME memcpy_archive_test SRC memcpy_archive_test.cpp DEPENDS utils) unit_test(NAME gather_buffer_test SRC gather_buffer_test.cpp DEPENDS utils Boost::mpi MPI::MPI_CXX NUM_PROC 4) unit_test(NAME scatter_buffer_test SRC scatter_buffer_test.cpp DEPENDS utils Boost::mpi MPI::MPI_CXX NUM_PROC 4) diff --git a/src/utils/tests/memcpy_archive_test.cpp b/src/utils/tests/memcpy_archive_test.cpp new file mode 100644 index 00000000000..2ecacb3f807 --- /dev/null +++ b/src/utils/tests/memcpy_archive_test.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2019 The ESPResSo project + * + * This file is part of ESPResSo. + * + * ESPResSo is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ESPResSo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define BOOST_TEST_MODULE memcpy archive test +#define BOOST_TEST_DYN_LINK +#include + +#include + +#include +#include + +#include +#include + +#include + +struct NonTrivial { + boost::optional ov; + + template void serialize(Archive &ar, long int) { ar &ov; } +}; + +using OpVec = boost::optional; + +namespace Utils { +template <> struct is_statically_serializable : std::true_type {}; + +template +struct is_statically_serializable> + : is_statically_serializable {}; +} // namespace Utils + +BOOST_AUTO_TEST_CASE(packing_size_test) { + BOOST_CHECK_EQUAL(Utils::MemcpyIArchive::packing_size(), sizeof(int)); + + { + BOOST_CHECK_EQUAL(Utils::MemcpyIArchive::packing_size(), + Utils::MemcpyIArchive::packing_size()); + } +} + +BOOST_AUTO_TEST_CASE(type_traits) { + static_assert(Utils::is_statically_serializable::value, ""); + static_assert(Utils::detail::use_memcpy::value, ""); + static_assert(not Utils::detail::use_serialize::value, ""); + + static_assert(Utils::is_statically_serializable::value, ""); + static_assert(not Utils::detail::use_memcpy::value, ""); + static_assert(Utils::detail::use_serialize::value, ""); +} + +BOOST_AUTO_TEST_CASE(skiping_and_position) { + std::array buf; + + auto ar = Utils::MemcpyOArchive(Utils::make_span(buf)); + + BOOST_CHECK_EQUAL(0, ar.bytes_processed()); + ar.skip(5); + BOOST_CHECK_EQUAL(5, ar.bytes_processed()); +} + +BOOST_AUTO_TEST_CASE(memcpy_processing) { + std::array buf; + + 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(serializaton_processing) { + std::array buf; + + 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_CHECK_EQUAL(oa.bytes_written(), buf.size()); + } + + { + auto ia = Utils::MemcpyIArchive{Utils::make_span(buf)}; + OpVec out1, out2; + ia >> out1; + ia >> out2; + + BOOST_CHECK_EQUAL(ia.bytes_read(), buf.size()); + + BOOST_CHECK(out1 == active); + BOOST_CHECK(out2 == inactive); + } +}