mirror of https://github.com/OpenMW/openmw.git
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
parent
f70a154195
commit
a58f1a94e3
@ -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…
Reference in New Issue