Add helpers for binary serialization

To construct serializer from given entities:
* Data source/destination - any value that has to be serialized/deserialized,
  usually already existing type.
* Format - functional object to define high level serialization logic to
  define specific format and data schema. Like order of fields, allocation.
* Visitor - functional object to define low level serialization logic to
  operator on given data part.
  * BinaryWriter - copies given value into provided buffer.
  * BinaryReader - copies value into given destination from provided buffer.
  * SizeAccumulator - calculates required buffer size for given data.
pull/3198/head
elsid 3 years ago
parent f70a154195
commit a58f1a94e3
No known key found for this signature in database
GPG Key ID: D27B8E8D10A2896B

@ -36,6 +36,10 @@ if (GTEST_FOUND AND GMOCK_FOUND)
detournavigator/recastmeshobject.cpp
detournavigator/navmeshtilescache.cpp
detournavigator/tilecachedrecastmeshmanager.cpp
detournavigator/serialization/binaryreader.cpp
detournavigator/serialization/binarywriter.cpp
detournavigator/serialization/sizeaccumulator.cpp
detournavigator/serialization/integration.cpp
settings/parser.cpp

@ -0,0 +1,67 @@
#include "format.hpp"
#include <components/detournavigator/serialization/binaryreader.hpp>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <cstdint>
#include <cstring>
#include <vector>
namespace
{
using namespace testing;
using namespace DetourNavigator::Serialization;
using namespace DetourNavigator::SerializationTesting;
TEST(DetourNavigatorSerializationBinaryReaderTest, shouldReadArithmeticTypeValue)
{
std::uint32_t value = 42;
std::vector<std::byte> data(sizeof(value));
std::memcpy(data.data(), &value, sizeof(value));
BinaryReader binaryReader(data.data(), data.data() + data.size());
std::uint32_t result = 0;
const TestFormat<Mode::Read> format;
binaryReader(format, result);
EXPECT_EQ(result, 42);
}
TEST(DetourNavigatorSerializationBinaryReaderTest, shouldReadArithmeticTypeRangeValue)
{
const std::size_t count = 3;
std::vector<std::byte> data(sizeof(std::size_t) + count * sizeof(std::uint32_t));
std::memcpy(data.data(), &count, sizeof(count));
const std::uint32_t value1 = 960900021;
std::memcpy(data.data() + sizeof(count), &value1, sizeof(std::uint32_t));
const std::uint32_t value2 = 1235496234;
std::memcpy(data.data() + sizeof(count) + sizeof(std::uint32_t), &value2, sizeof(std::uint32_t));
const std::uint32_t value3 = 2342038092;
std::memcpy(data.data() + sizeof(count) + 2 * sizeof(std::uint32_t), &value3, sizeof(std::uint32_t));
BinaryReader binaryReader(data.data(), data.data() + data.size());
std::size_t resultCount = 0;
const TestFormat<Mode::Read> format;
binaryReader(format, resultCount);
std::vector<std::uint32_t> result(resultCount);
binaryReader(format, result.data(), result.size());
EXPECT_THAT(result, ElementsAre(value1, value2, value3));
}
TEST(DetourNavigatorSerializationBinaryReaderTest, forNotEnoughDataForArithmeticTypeShouldThrowException)
{
std::vector<std::byte> data(3);
BinaryReader binaryReader(data.data(), data.data() + data.size());
std::uint32_t result = 0;
const TestFormat<Mode::Read> format;
EXPECT_THROW(binaryReader(format, result), std::runtime_error);
}
TEST(DetourNavigatorSerializationBinaryReaderTest, forNotEnoughDataForArithmeticTypeRangeShouldThrowException)
{
std::vector<std::byte> data(7);
BinaryReader binaryReader(data.data(), data.data() + data.size());
std::vector<std::uint32_t> values(2);
const TestFormat<Mode::Read> format;
EXPECT_THROW(binaryReader(format, values.data(), values.size()), std::runtime_error);
}
}

@ -0,0 +1,57 @@
#include "format.hpp"
#include <components/detournavigator/serialization/binarywriter.hpp>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <array>
#include <cstdint>
#include <vector>
namespace
{
using namespace testing;
using namespace DetourNavigator::Serialization;
using namespace DetourNavigator::SerializationTesting;
TEST(DetourNavigatorSerializationBinaryWriterTest, shouldWriteArithmeticTypeValue)
{
std::vector<std::byte> result(4);
BinaryWriter binaryWriter(result.data(), result.data() + result.size());
const TestFormat<Mode::Write> format;
binaryWriter(format, std::uint32_t(42));
EXPECT_THAT(result, ElementsAre(std::byte(42), std::byte(0), std::byte(0), std::byte(0)));
}
TEST(DetourNavigatorSerializationBinaryWriterTest, shouldWriteArithmeticTypeRangeValue)
{
std::vector<std::byte> result(8);
BinaryWriter binaryWriter(result.data(), result.data() + result.size());
std::vector<std::uint32_t> values({42, 13});
const TestFormat<Mode::Write> format;
binaryWriter(format, values.data(), values.size());
constexpr std::array<std::byte, 8> expected {
std::byte(42), std::byte(0), std::byte(0), std::byte(0),
std::byte(13), std::byte(0), std::byte(0), std::byte(0),
};
EXPECT_THAT(result, ElementsAreArray(expected));
}
TEST(DetourNavigatorSerializationBinaryWriterTest, forNotEnoughSpaceForArithmeticTypeShouldThrowException)
{
std::vector<std::byte> result(3);
BinaryWriter binaryWriter(result.data(), result.data() + result.size());
const TestFormat<Mode::Write> format;
EXPECT_THROW(binaryWriter(format, std::uint32_t(42)), std::runtime_error);
}
TEST(DetourNavigatorSerializationBinaryWriterTest, forNotEnoughSpaceForArithmeticTypeRangeShouldThrowException)
{
std::vector<std::byte> result(7);
BinaryWriter binaryWriter(result.data(), result.data() + result.size());
std::vector<std::uint32_t> values({42, 13});
const TestFormat<Mode::Write> format;
EXPECT_THROW(binaryWriter(format, values.data(), values.size()), std::runtime_error);
}
}

@ -0,0 +1,75 @@
#ifndef OPENMW_TEST_SUITE_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H
#define OPENMW_TEST_SUITE_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H
#include <components/detournavigator/serialization/format.hpp>
#include <utility>
#include <type_traits>
namespace DetourNavigator::SerializationTesting
{
struct Pod
{
int mInt = 42;
double mDouble = 3.14;
friend bool operator==(const Pod& l, const Pod& r)
{
const auto tuple = [] (const Pod& v) { return std::tuple(v.mInt, v.mDouble); };
return tuple(l) == tuple(r);
}
};
enum Enum
{
A,
B,
C,
};
struct Composite
{
short mFloatArray[3] = {0};
std::vector<int> mIntVector;
std::vector<Enum> mEnumVector;
std::vector<Pod> mPodVector;
std::size_t mPodDataSize = 0;
std::vector<Pod> mPodBuffer;
std::size_t mCharDataSize = 0;
std::vector<char> mCharBuffer;
};
template <Serialization::Mode mode>
struct TestFormat : Serialization::Format<mode, TestFormat<mode>>
{
using Serialization::Format<mode, TestFormat<mode>>::operator();
template <class Visitor, class T>
auto operator()(Visitor&& visitor, T& value) const
-> std::enable_if_t<std::is_same_v<std::decay_t<T>, Pod>>
{
visitor(*this, value.mInt);
visitor(*this, value.mDouble);
}
template <class Visitor, class T>
auto operator()(Visitor&& visitor, T& value) const
-> std::enable_if_t<std::is_same_v<std::decay_t<T>, Composite>>
{
visitor(*this, value.mFloatArray);
visitor(*this, value.mIntVector);
visitor(*this, value.mEnumVector);
visitor(*this, value.mPodVector);
visitor(*this, value.mPodDataSize);
if constexpr (mode == Serialization::Mode::Read)
value.mPodBuffer.resize(value.mPodDataSize);
visitor(*this, value.mPodBuffer.data(), value.mPodDataSize);
visitor(*this, value.mCharDataSize);
if constexpr (mode == Serialization::Mode::Read)
value.mCharBuffer.resize(value.mCharDataSize);
visitor(*this, value.mCharBuffer.data(), value.mCharDataSize);
}
};
}
#endif

@ -0,0 +1,56 @@
#include "format.hpp"
#include <components/detournavigator/serialization/sizeaccumulator.hpp>
#include <components/detournavigator/serialization/binarywriter.hpp>
#include <components/detournavigator/serialization/binaryreader.hpp>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <vector>
namespace
{
using namespace testing;
using namespace DetourNavigator::Serialization;
using namespace DetourNavigator::SerializationTesting;
struct DetourNavigatorSerializationIntegrationTest : Test
{
Composite mComposite;
DetourNavigatorSerializationIntegrationTest()
{
mComposite.mIntVector = {4, 5, 6};
mComposite.mEnumVector = {Enum::A, Enum::B, Enum::C};
mComposite.mPodVector = {Pod {4, 23.87}, Pod {5, -31.76}, Pod {6, 65.12}};
mComposite.mPodBuffer = {Pod {7, 456.123}, Pod {8, -628.346}};
mComposite.mPodDataSize = mComposite.mPodBuffer.size();
std::string charData = "serialization";
mComposite.mCharBuffer = {charData.begin(), charData.end()};
mComposite.mCharDataSize = charData.size();
}
};
TEST_F(DetourNavigatorSerializationIntegrationTest, sizeAccumulatorShouldSupportCustomSerializer)
{
SizeAccumulator sizeAccumulator;
TestFormat<Mode::Write>{}(sizeAccumulator, mComposite);
EXPECT_EQ(sizeAccumulator.value(), 143);
}
TEST_F(DetourNavigatorSerializationIntegrationTest, binaryReaderShouldDeserializeDataWrittenByBinaryWriter)
{
std::vector<std::byte> data(143);
TestFormat<Mode::Write>{}(BinaryWriter(data.data(), data.data() + data.size()), mComposite);
Composite result;
TestFormat<Mode::Read>{}(BinaryReader(data.data(), data.data() + data.size()), result);
EXPECT_EQ(result.mIntVector, mComposite.mIntVector);
EXPECT_EQ(result.mEnumVector, mComposite.mEnumVector);
EXPECT_EQ(result.mPodVector, mComposite.mPodVector);
EXPECT_EQ(result.mPodDataSize, mComposite.mPodDataSize);
EXPECT_EQ(result.mPodBuffer, mComposite.mPodBuffer);
EXPECT_EQ(result.mCharDataSize, mComposite.mCharDataSize);
EXPECT_EQ(result.mCharBuffer, mComposite.mCharBuffer);
}
}

@ -0,0 +1,43 @@
#include "format.hpp"
#include <components/detournavigator/serialization/sizeaccumulator.hpp>
#include <gtest/gtest.h>
#include <cstddef>
#include <cstdint>
#include <variant>
#include <vector>
namespace
{
using namespace testing;
using namespace DetourNavigator::Serialization;
using namespace DetourNavigator::SerializationTesting;
TEST(DetourNavigatorSerializationSizeAccumulatorTest, shouldProvideSizeForArithmeticType)
{
SizeAccumulator sizeAccumulator;
constexpr std::monostate format;
sizeAccumulator(format, std::uint32_t());
EXPECT_EQ(sizeAccumulator.value(), 4);
}
TEST(DetourNavigatorSerializationSizeAccumulatorTest, shouldProvideSizeForArithmeticTypeRange)
{
SizeAccumulator sizeAccumulator;
const std::uint64_t* const data = nullptr;
const std::size_t count = 3;
const std::monostate format;
sizeAccumulator(format, data, count);
EXPECT_EQ(sizeAccumulator.value(), 24);
}
TEST(DetourNavigatorSerializationSizeAccumulatorTest, shouldSupportCustomSerializer)
{
SizeAccumulator sizeAccumulator;
const TestFormat<Mode::Write> format;
sizeAccumulator(format, Pod {});
EXPECT_EQ(sizeAccumulator.value(), 12);
}
}

@ -0,0 +1,62 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYREADER_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYREADER_H
#include <cassert>
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <type_traits>
namespace DetourNavigator::Serialization
{
class BinaryReader
{
public:
explicit BinaryReader(const std::byte* pos, const std::byte* end)
: mPos(pos), mEnd(end)
{
assert(mPos <= mEnd);
}
BinaryReader(const BinaryReader&) = delete;
template <class Format, class T>
void operator()(Format&& format, T& value)
{
if constexpr (std::is_arithmetic_v<T>)
{
if (mEnd - mPos < static_cast<std::ptrdiff_t>(sizeof(value)))
throw std::runtime_error("Not enough data");
std::memcpy(&value, mPos, sizeof(value));
mPos += sizeof(value);
}
else
{
format(*this, value);
}
}
template <class Format, class T>
auto operator()(Format&& format, T* data, std::size_t count)
{
if constexpr (std::is_arithmetic_v<T>)
{
if (mEnd - mPos < static_cast<std::ptrdiff_t>(count * sizeof(T)))
throw std::runtime_error("Not enough data");
const std::size_t size = sizeof(T) * count;
std::memcpy(data, mPos, size);
mPos += size;
}
else
{
format(*this, data, count);
}
}
private:
const std::byte* mPos;
const std::byte* const mEnd;
};
}
#endif

@ -0,0 +1,62 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYWRITER_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_BINARYWRITER_H
#include <cassert>
#include <cstddef>
#include <cstring>
#include <stdexcept>
#include <type_traits>
namespace DetourNavigator::Serialization
{
struct BinaryWriter
{
public:
explicit BinaryWriter(std::byte* dest, const std::byte* end)
: mDest(dest), mEnd(end)
{
assert(mDest <= mEnd);
}
BinaryWriter(const BinaryWriter&) = delete;
template <class Format, class T>
void operator()(Format&& format, const T& value)
{
if constexpr (std::is_arithmetic_v<T>)
{
if (mEnd - mDest < static_cast<std::ptrdiff_t>(sizeof(value)))
throw std::runtime_error("Not enough space");
std::memcpy(mDest, &value, sizeof(value));
mDest += sizeof(value);
}
else
{
format(*this, value);
}
}
template <class Format, class T>
auto operator()(Format&& format, const T* data, std::size_t count)
{
if constexpr (std::is_arithmetic_v<T>)
{
const std::size_t size = sizeof(T) * count;
if (mEnd - mDest < static_cast<std::ptrdiff_t>(size))
throw std::runtime_error("Not enough space");
std::memcpy(mDest, data, size);
mDest += size;
}
else
{
format(*this, data, count);
}
}
private:
std::byte* mDest;
const std::byte* const mEnd;
};
}
#endif

@ -0,0 +1,80 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_FORMAT_H
#include <algorithm>
#include <cstddef>
#include <functional>
#include <type_traits>
#include <utility>
#include <vector>
namespace DetourNavigator::Serialization
{
enum class Mode
{
Read,
Write,
};
template <class>
struct IsContiguousContainer : std::false_type {};
template <class ... Args>
struct IsContiguousContainer<std::vector<Args ...>> : std::true_type {};
template <class T>
constexpr bool isContiguousContainer = IsContiguousContainer<std::decay_t<T>>::value;
template <Mode mode, class Derived>
struct Format
{
template <class Visitor, class T>
void operator()(Visitor&& visitor, T* data, std::size_t size) const
{
if constexpr (std::is_arithmetic_v<T>)
{
visitor(self(), data, size);
}
else if constexpr (std::is_enum_v<T>)
{
if constexpr (mode == Mode::Write)
visitor(self(), reinterpret_cast<const std::underlying_type_t<T>*>(data), size);
else
{
static_assert(mode == Mode::Read);
visitor(self(), reinterpret_cast<std::underlying_type_t<T>*>(data), size);
}
}
else
{
std::for_each(data, data + size, [&] (auto& v) { visitor(self(), v); });
}
}
template <class Visitor, class T, std::size_t size>
void operator()(Visitor&& visitor, T(& data)[size]) const
{
self()(std::forward<Visitor>(visitor), data, size);
}
template <class Visitor, class T>
auto operator()(Visitor&& visitor, T&& value) const
-> std::enable_if_t<isContiguousContainer<T>>
{
if constexpr (mode == Mode::Write)
visitor(self(), value.size());
else
{
static_assert(mode == Mode::Read);
std::size_t size = 0;
visitor(self(), size);
value.resize(size);
}
self()(std::forward<Visitor>(visitor), value.data(), value.size());
}
const Derived& self() const { return static_cast<const Derived&>(*this); }
};
}
#endif

@ -0,0 +1,41 @@
#ifndef OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_SIZEACCUMULATOR_H
#define OPENMW_COMPONENTS_DETOURNAVIGATOR_SERIALIZATION_SIZEACCUMULATOR_H
#include <cstddef>
#include <type_traits>
namespace DetourNavigator::Serialization
{
class SizeAccumulator
{
public:
SizeAccumulator() = default;
SizeAccumulator(const SizeAccumulator&) = delete;
std::size_t value() const { return mValue; }
template <class Format, class T>
void operator()(Format&& format, const T& value)
{
if constexpr (std::is_arithmetic_v<T>)
mValue += sizeof(T);
else
format(*this, value);
}
template <class Format, class T>
auto operator()(Format&& format, const T* data, std::size_t count)
{
if constexpr (std::is_arithmetic_v<T>)
mValue += count * sizeof(T);
else
format(*this, data, count);
}
private:
std::size_t mValue = 0;
};
}
#endif
Loading…
Cancel
Save