mirror of
https://github.com/fraillt/bitsery.git
synced 2026-06-08 08:13:56 +00:00
format code base in Mozilla style
This commit is contained in:
@@ -1,48 +1,62 @@
|
||||
//include bitsery.h to get serialization and deserialization classes
|
||||
// include bitsery.h to get serialization and deserialization classes
|
||||
#include <bitsery/bitsery.h>
|
||||
//in ordered to serialize/deserialize data to buffer, include buffer adapter
|
||||
// in ordered to serialize/deserialize data to buffer, include buffer adapter
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
//bitsery itself doesn't is lightweight, and doesnt include any unnessessary files,
|
||||
//traits helps library to know how to use types correctly,
|
||||
//in this case we'll be using vector both, to serialize/deserialize data and to store use as a buffer.
|
||||
// bitsery itself doesn't is lightweight, and doesnt include any unnessessary
|
||||
// files, traits helps library to know how to use types correctly, in this case
|
||||
// we'll be using vector both, to serialize/deserialize data and to store use as
|
||||
// a buffer.
|
||||
#include <bitsery/traits/vector.h>
|
||||
|
||||
enum class MyEnum:uint16_t { V1,V2,V3 };
|
||||
struct MyStruct {
|
||||
uint32_t i;
|
||||
MyEnum e;
|
||||
std::vector<float> fs;
|
||||
enum class MyEnum : uint16_t
|
||||
{
|
||||
V1,
|
||||
V2,
|
||||
V3
|
||||
};
|
||||
struct MyStruct
|
||||
{
|
||||
uint32_t i;
|
||||
MyEnum e;
|
||||
std::vector<float> fs;
|
||||
};
|
||||
|
||||
//define how object should be serialized/deserialized
|
||||
template <typename S>
|
||||
void serialize(S& s, MyStruct& o) {
|
||||
s.value4b(o.i);//fundamental types (ints, floats, enums) of size 4b
|
||||
s.value2b(o.e);
|
||||
s.container4b(o.fs, 10);//resizable containers also requires maxSize, to make it safe from buffer-overflow attacks
|
||||
// define how object should be serialized/deserialized
|
||||
template<typename S>
|
||||
void
|
||||
serialize(S& s, MyStruct& o)
|
||||
{
|
||||
s.value4b(o.i); // fundamental types (ints, floats, enums) of size 4b
|
||||
s.value2b(o.e);
|
||||
s.container4b(o.fs, 10); // resizable containers also requires maxSize, to
|
||||
// make it safe from buffer-overflow attacks
|
||||
}
|
||||
|
||||
//some helper types
|
||||
// some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
MyStruct data{8941, MyEnum::V2, {15.0f, -8.5f, 0.045f}};
|
||||
MyStruct res{};
|
||||
int
|
||||
main()
|
||||
{
|
||||
// set some random data
|
||||
MyStruct data{ 8941, MyEnum::V2, { 15.0f, -8.5f, 0.045f } };
|
||||
MyStruct res{};
|
||||
|
||||
//create buffer to store data
|
||||
Buffer buffer;
|
||||
//use quick serialization function,
|
||||
//it will use default configuration to setup all the nesessary steps
|
||||
//and serialize data to container
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
// create buffer to store data
|
||||
Buffer buffer;
|
||||
// use quick serialization function,
|
||||
// it will use default configuration to setup all the nesessary steps
|
||||
// and serialize data to container
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
|
||||
//same as serialization, but returns deserialization state as a pair
|
||||
//first = error code, second = is buffer was successfully read from begin to the end.
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
// same as serialization, but returns deserialization state as a pair
|
||||
// first = error code, second = is buffer was successfully read from begin to
|
||||
// the end.
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>(
|
||||
{ buffer.begin(), writtenSize }, res);
|
||||
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data.fs == res.fs && data.i == res.i && data.e == res.e);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data.fs == res.fs && data.i == res.i && data.e == res.e);
|
||||
}
|
||||
|
||||
@@ -1,62 +1,74 @@
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
//we'll be using std::array as a buffer type, so include traits for this
|
||||
#include <bitsery/bitsery.h>
|
||||
// we'll be using std::array as a buffer type, so include traits for this
|
||||
#include <bitsery/traits/array.h>
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
//include extension that will allow to compress our data
|
||||
// include extension that will allow to compress our data
|
||||
#include <bitsery/ext/value_range.h>
|
||||
|
||||
namespace MyTypes {
|
||||
|
||||
struct Vec3 { float x, y, z; };
|
||||
struct Vec3
|
||||
{
|
||||
float x, y, z;
|
||||
};
|
||||
|
||||
struct Monster {
|
||||
Vec3 pos;
|
||||
std::vector<Vec3> path;
|
||||
std::string name;
|
||||
};
|
||||
struct Monster
|
||||
{
|
||||
Vec3 pos;
|
||||
std::vector<Vec3> path;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
template<typename S>
|
||||
void serialize(S& s, MyTypes::Vec3 &o) {
|
||||
s.value4b(o.x);
|
||||
s.value4b(o.y);
|
||||
s.value4b(o.z);
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
void serialize (S& s, Monster& o) {
|
||||
s.text1b(o.name, 20);
|
||||
s.object(o.pos);
|
||||
//compress path in a range of -1.0 .. 1.0 with 0.01 precision
|
||||
//enableBitPacking creates separate serializer/deserializer object, that contains bit packing operations
|
||||
s.enableBitPacking([&o](typename S::BPEnabledType& sbp) {
|
||||
sbp.container(o.path, 1000, [](typename S::BPEnabledType& sbp, Vec3& vec3) {
|
||||
constexpr bitsery::ext::ValueRange<float> range{-1.0f,1.0f, 0.01f};
|
||||
sbp.ext(vec3.x, range);
|
||||
sbp.ext(vec3.y, range);
|
||||
sbp.ext(vec3.z, range);
|
||||
});
|
||||
});
|
||||
}
|
||||
template<typename S>
|
||||
void
|
||||
serialize(S& s, MyTypes::Vec3& o)
|
||||
{
|
||||
s.value4b(o.x);
|
||||
s.value4b(o.y);
|
||||
s.value4b(o.z);
|
||||
}
|
||||
|
||||
//use fixed-size buffer
|
||||
template<typename S>
|
||||
void
|
||||
serialize(S& s, Monster& o)
|
||||
{
|
||||
s.text1b(o.name, 20);
|
||||
s.object(o.pos);
|
||||
// compress path in a range of -1.0 .. 1.0 with 0.01 precision
|
||||
// enableBitPacking creates separate serializer/deserializer object, that
|
||||
// contains bit packing operations
|
||||
s.enableBitPacking([&o](typename S::BPEnabledType& sbp) {
|
||||
sbp.container(o.path, 1000, [](typename S::BPEnabledType& sbp, Vec3& vec3) {
|
||||
constexpr bitsery::ext::ValueRange<float> range{ -1.0f, 1.0f, 0.01f };
|
||||
sbp.ext(vec3.x, range);
|
||||
sbp.ext(vec3.y, range);
|
||||
sbp.ext(vec3.z, range);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// use fixed-size buffer
|
||||
using Buffer = std::array<uint8_t, 10000>;
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
MyTypes::Monster data{};
|
||||
data.name = "lew";
|
||||
int
|
||||
main()
|
||||
{
|
||||
// set some random data
|
||||
MyTypes::Monster data{};
|
||||
data.name = "lew";
|
||||
|
||||
//create buffer to store data to
|
||||
Buffer buffer{};
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
// create buffer to store data to
|
||||
Buffer buffer{};
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
|
||||
MyTypes::Monster res{};
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
MyTypes::Monster res{};
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>(
|
||||
{ buffer.begin(), writtenSize }, res);
|
||||
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
}
|
||||
|
||||
@@ -1,44 +1,54 @@
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
//to use brief syntax always include this header
|
||||
#include <bitsery/bitsery.h>
|
||||
// to use brief syntax always include this header
|
||||
#include <bitsery/brief_syntax.h>
|
||||
//we also need additional traits to work with container types,
|
||||
//instead of including <bitsery/traits/vector.h> for vector traits, now we also need traits to work with brief_syntax types.
|
||||
//so include everything from <bitsery/brief_syntax/...> instead of <bitsery/traits/...>
|
||||
//otherwise we'll get static assert error, saying to define serialize function.
|
||||
// we also need additional traits to work with container types,
|
||||
// instead of including <bitsery/traits/vector.h> for vector traits, now we also
|
||||
// need traits to work with brief_syntax types. so include everything from
|
||||
// <bitsery/brief_syntax/...> instead of <bitsery/traits/...> otherwise we'll
|
||||
// get static assert error, saying to define serialize function.
|
||||
#include <bitsery/brief_syntax/vector.h>
|
||||
|
||||
enum class MyEnum:uint16_t { V1,V2,V3 };
|
||||
struct MyStruct {
|
||||
uint32_t i;
|
||||
MyEnum e;
|
||||
std::vector<float> fs;
|
||||
|
||||
//define serialize function as usual
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
//now we can use brief syntax with
|
||||
s(i, e, fs);
|
||||
}
|
||||
enum class MyEnum : uint16_t
|
||||
{
|
||||
V1,
|
||||
V2,
|
||||
V3
|
||||
};
|
||||
struct MyStruct
|
||||
{
|
||||
uint32_t i;
|
||||
MyEnum e;
|
||||
std::vector<float> fs;
|
||||
|
||||
// define serialize function as usual
|
||||
template<typename S>
|
||||
void serialize(S& s)
|
||||
{
|
||||
// now we can use brief syntax with
|
||||
s(i, e, fs);
|
||||
}
|
||||
};
|
||||
|
||||
//some helper types
|
||||
// some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
MyStruct data{8941, MyEnum::V2, {15.0f, -8.5f, 0.045f}};
|
||||
MyStruct res{};
|
||||
int
|
||||
main()
|
||||
{
|
||||
// set some random data
|
||||
MyStruct data{ 8941, MyEnum::V2, { 15.0f, -8.5f, 0.045f } };
|
||||
MyStruct res{};
|
||||
|
||||
//serialization, deserialization flow is unchanged as in basic usage
|
||||
Buffer buffer;
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
// serialization, deserialization flow is unchanged as in basic usage
|
||||
Buffer buffer;
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>(
|
||||
{ buffer.begin(), writtenSize }, res);
|
||||
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data.fs == res.fs && data.i == res.i && data.e == res.e);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data.fs == res.fs && data.i == res.i && data.e == res.e);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
// include extensions to work with tuples and variants
|
||||
// these extesions only work with C++17
|
||||
@@ -10,101 +10,126 @@
|
||||
// let's include this extension to make it more interesting :)
|
||||
#include <bitsery/ext/compact_value.h>
|
||||
|
||||
struct MyStruct {
|
||||
std::vector<int32_t> v{};
|
||||
float f{};
|
||||
struct MyStruct
|
||||
{
|
||||
std::vector<int32_t> v{};
|
||||
float f{};
|
||||
|
||||
bool operator==(const MyStruct& rhs) const {
|
||||
return v == rhs.v && f == rhs.f;
|
||||
}
|
||||
bool operator==(const MyStruct& rhs) const
|
||||
{
|
||||
return v == rhs.v && f == rhs.f;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename S>
|
||||
void serialize(S& s, MyStruct& o) {
|
||||
s.container4b(o.v, 1000);
|
||||
s.value4b(o.f);
|
||||
void
|
||||
serialize(S& s, MyStruct& o)
|
||||
{
|
||||
s.container4b(o.v, 1000);
|
||||
s.value4b(o.f);
|
||||
}
|
||||
|
||||
// this will be the type that we want to serialize/deserialize
|
||||
using MyTuple = std::tuple<float, MyStruct>;
|
||||
using MyVariant = std::variant<int64_t, MyTuple, MyStruct>;
|
||||
|
||||
// define default serialize function for MyVariant, so that we could use quickSerialization/Deserialization functions
|
||||
// define default serialize function for MyVariant, so that we could use
|
||||
// quickSerialization/Deserialization functions
|
||||
template<typename S>
|
||||
void serialize(S& s, MyVariant& o) {
|
||||
// in order to serialize a variant, it needs to know how to do it for all types
|
||||
// we can do this simply by providing any callable object, that accepts serializer and type as arguments
|
||||
s.ext(o, bitsery::ext::StdVariant{
|
||||
// specify how to serialize tuple by creating a lambda
|
||||
[](S& s, MyTuple& o) {
|
||||
// StdTuple is used exactly the same as StdVariant
|
||||
s.ext(o, bitsery::ext::StdTuple{
|
||||
// this is convenient callable object to specify integral value size
|
||||
// it is different equivalent to lambda [](auto& s, float&o) { s.value4b(o);}
|
||||
bitsery::ext::OverloadValue<float, 4>{},
|
||||
// it is not required to provide MyStruct overload, because it we have defined 'serialize' function for it
|
||||
});
|
||||
},
|
||||
// this might also be useful if you want to overload using extension
|
||||
bitsery::ext::OverloadExtValue<int64_t, 8, bitsery::ext::CompactValue>{},
|
||||
// you can even go further and instead of writing lambda for MyTuple you can as well compose the same functionality
|
||||
// with OverloadExtObject, like this:
|
||||
// (comment out MyTuple lambda, and uncomment this)
|
||||
// ext::OverloadExtObject<MyTuple, ext::StdTuple<ext::OverloadValue<float, 4>>>{},
|
||||
void
|
||||
serialize(S& s, MyVariant& o)
|
||||
{
|
||||
// in order to serialize a variant, it needs to know how to do it for all
|
||||
// types we can do this simply by providing any callable object, that accepts
|
||||
// serializer and type as arguments
|
||||
s.ext(
|
||||
o,
|
||||
bitsery::ext::StdVariant{
|
||||
// specify how to serialize tuple by creating a lambda
|
||||
[](S& s, MyTuple& o) {
|
||||
// StdTuple is used exactly the same as StdVariant
|
||||
s.ext(
|
||||
o,
|
||||
bitsery::ext::StdTuple{
|
||||
// this is convenient callable object to specify integral value size
|
||||
// it is different equivalent to lambda [](auto& s, float&o) {
|
||||
// s.value4b(o);}
|
||||
bitsery::ext::OverloadValue<float, 4>{},
|
||||
// it is not required to provide MyStruct overload, because it we
|
||||
// have defined 'serialize' function for it
|
||||
});
|
||||
},
|
||||
// this might also be useful if you want to overload using extension
|
||||
bitsery::ext::OverloadExtValue<int64_t, 8, bitsery::ext::CompactValue>{},
|
||||
// you can even go further and instead of writing lambda for MyTuple you
|
||||
// can as well compose the same functionality
|
||||
// with OverloadExtObject, like this:
|
||||
// (comment out MyTuple lambda, and uncomment this)
|
||||
// ext::OverloadExtObject<MyTuple, ext::StdTuple<ext::OverloadValue<float,
|
||||
// 4>>>{},
|
||||
|
||||
// we can also override default 'serialize' function by creating an overloading for that type
|
||||
[](S& s, MyStruct& o) {
|
||||
s.value4b(o.f);
|
||||
s.container(o.v, 1000, [](S& s, int32_t& v) {
|
||||
s.ext4b(v, bitsery::ext::CompactValue{});
|
||||
});
|
||||
},
|
||||
// NOTE.
|
||||
// it is possible to provide "auto" as type parameter
|
||||
// this will allow you to override all default 'serialize' functions
|
||||
// but in this case it will not be called, because we have explicitly provided overloads for all variant types
|
||||
// also note, that first parameter (serializer) is also "auto", this is required, so that it would be least specialized case
|
||||
// otherwise it will not compile if you any ext::Overload* helper defined, because it will have ambiguous definitions
|
||||
// (ext::OverLoad* defines (templated_type& s, concrete_type& o) and lambda would be (concrete_type& s, templated_type& o))
|
||||
[](auto& , auto&) {
|
||||
assert(false);
|
||||
}
|
||||
});
|
||||
// we can also override default 'serialize' function by creating an
|
||||
// overloading for that type
|
||||
[](S& s, MyStruct& o) {
|
||||
s.value4b(o.f);
|
||||
s.container(o.v, 1000, [](S& s, int32_t& v) {
|
||||
s.ext4b(v, bitsery::ext::CompactValue{});
|
||||
});
|
||||
},
|
||||
// NOTE.
|
||||
// it is possible to provide "auto" as type parameter
|
||||
// this will allow you to override all default 'serialize' functions
|
||||
// but in this case it will not be called, because we have explicitly
|
||||
// provided overloads for all variant types
|
||||
// also note, that first parameter (serializer) is also "auto", this is
|
||||
// required, so that it would be least specialized case
|
||||
// otherwise it will not compile if you any ext::Overload* helper defined,
|
||||
// because it will have ambiguous definitions
|
||||
// (ext::OverLoad* defines (templated_type& s, concrete_type& o) and
|
||||
// lambda would be (concrete_type& s, templated_type& o))
|
||||
[](auto&, auto&) { assert(false); } });
|
||||
}
|
||||
|
||||
|
||||
//some helper types
|
||||
// some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
int
|
||||
main()
|
||||
{
|
||||
|
||||
//set some random data
|
||||
MyVariant data{ MyTuple{-7549, {{-451, 2, 968, 75, 4, 156, 49}, 874.4f}} };
|
||||
MyVariant res{};
|
||||
// set some random data
|
||||
MyVariant data{ MyTuple{ -7549,
|
||||
{ { -451, 2, 968, 75, 4, 156, 49 }, 874.4f } } };
|
||||
MyVariant res{};
|
||||
|
||||
//create buffer to store data
|
||||
Buffer buffer;
|
||||
//use quick serialization function,
|
||||
//it will use default configuration to setup all the nesessary steps
|
||||
//and serialize data to container
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
// create buffer to store data
|
||||
Buffer buffer;
|
||||
// use quick serialization function,
|
||||
// it will use default configuration to setup all the nesessary steps
|
||||
// and serialize data to container
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
|
||||
//same as serialization, but returns deserialization state as a pair
|
||||
//first = error code, second = is buffer was successfully read from begin to the end.
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>({ buffer.begin(), writtenSize }, res);
|
||||
// same as serialization, but returns deserialization state as a pair
|
||||
// first = error code, second = is buffer was successfully read from begin to
|
||||
// the end.
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>(
|
||||
{ buffer.begin(), writtenSize }, res);
|
||||
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data == res);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data == res);
|
||||
}
|
||||
#else
|
||||
#if defined(_MSC_VER)
|
||||
#pragma message("C++17 and /Zc:__cplusplus option is required to enable this example")
|
||||
#pragma message( \
|
||||
"C++17 and /Zc:__cplusplus option is required to enable this example")
|
||||
#else
|
||||
#pragma message("C++17 is required to enable this example")
|
||||
#endif
|
||||
int main() {
|
||||
return 0;
|
||||
int
|
||||
main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@@ -1,5 +1,5 @@
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/bitsery.h>
|
||||
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
@@ -8,91 +8,106 @@
|
||||
|
||||
namespace MyTypes {
|
||||
|
||||
struct Monster {
|
||||
Monster() = default;
|
||||
Monster(std::string _name, uint32_t minDmg, uint32_t maxDmg)
|
||||
:name{_name}, minDamage{minDmg}, maxDamage{maxDmg} {}
|
||||
struct Monster
|
||||
{
|
||||
Monster() = default;
|
||||
Monster(std::string _name, uint32_t minDmg, uint32_t maxDmg)
|
||||
: name{ _name }
|
||||
, minDamage{ minDmg }
|
||||
, maxDamage{ maxDmg }
|
||||
{
|
||||
}
|
||||
|
||||
std::string name{};
|
||||
uint32_t minDamage{};
|
||||
uint32_t maxDamage{};
|
||||
//...
|
||||
};
|
||||
std::string name{};
|
||||
uint32_t minDamage{};
|
||||
uint32_t maxDamage{};
|
||||
//...
|
||||
};
|
||||
|
||||
struct GameState {
|
||||
std::vector<Monster> monsters;
|
||||
};
|
||||
struct GameState
|
||||
{
|
||||
std::vector<Monster> monsters;
|
||||
};
|
||||
|
||||
//default flow for monster
|
||||
template <typename S>
|
||||
void serialize (S& s, Monster& o) {
|
||||
s.text1b(o.name, 20);
|
||||
s.value4b(o.minDamage);
|
||||
s.value4b(o.maxDamage);
|
||||
}
|
||||
// default flow for monster
|
||||
template<typename S>
|
||||
void
|
||||
serialize(S& s, Monster& o)
|
||||
{
|
||||
s.text1b(o.name, 20);
|
||||
s.value4b(o.minDamage);
|
||||
s.value4b(o.maxDamage);
|
||||
}
|
||||
|
||||
template<typename S>
|
||||
void serialize(S& s, GameState &o) {
|
||||
//we can have multiple types in context with std::tuple
|
||||
//if data type doesn't match then it will be compile time error
|
||||
//NOTE: if context is optional then you can call contextOrNull<T>, and it will return null if T doesn't exists
|
||||
auto maxMonsters = s.template context<int>();
|
||||
auto& dmgRange = s.template context<std::pair<uint32_t, uint32_t>>();
|
||||
template<typename S>
|
||||
void
|
||||
serialize(S& s, GameState& o)
|
||||
{
|
||||
// we can have multiple types in context with std::tuple
|
||||
// if data type doesn't match then it will be compile time error
|
||||
// NOTE: if context is optional then you can call contextOrNull<T>, and it
|
||||
// will return null if T doesn't exists
|
||||
auto maxMonsters = s.template context<int>();
|
||||
auto& dmgRange = s.template context<std::pair<uint32_t, uint32_t>>();
|
||||
|
||||
s.container(o.monsters, maxMonsters, [&dmgRange] (S& s, Monster& m) {
|
||||
s.text1b(m.name, 20);
|
||||
//we know min/max damage range for monsters, so we can use this range instead of full value
|
||||
bitsery::ext::ValueRange<uint32_t> range{dmgRange.first, dmgRange.second};
|
||||
//enable bit packing
|
||||
s.enableBitPacking([&m, &range](typename S::BPEnabledType& sbp) {
|
||||
sbp.ext(m.minDamage, range);
|
||||
sbp.ext(m.maxDamage, range);
|
||||
});
|
||||
});
|
||||
}
|
||||
s.container(o.monsters, maxMonsters, [&dmgRange](S& s, Monster& m) {
|
||||
s.text1b(m.name, 20);
|
||||
// we know min/max damage range for monsters, so we can use this range
|
||||
// instead of full value
|
||||
bitsery::ext::ValueRange<uint32_t> range{ dmgRange.first, dmgRange.second };
|
||||
// enable bit packing
|
||||
s.enableBitPacking([&m, &range](typename S::BPEnabledType& sbp) {
|
||||
sbp.ext(m.minDamage, range);
|
||||
sbp.ext(m.maxDamage, range);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//context can contain multiple types by wrapping these types in std::tuple
|
||||
//in serialization function we can get type that we need like this:
|
||||
// s.template context<int>();
|
||||
//this templated version also works if our context is the same as cast:
|
||||
// struct MyContext {...};
|
||||
// ...
|
||||
// s.template context<MyContext>();
|
||||
//NOTE:
|
||||
// if your context has no additional usage outside of serialization flow,
|
||||
// then you can create it internally via configuration (see inheritance.cpp)
|
||||
// context can contain multiple types by wrapping these types in std::tuple
|
||||
// in serialization function we can get type that we need like this:
|
||||
// s.template context<int>();
|
||||
// this templated version also works if our context is the same as cast:
|
||||
// struct MyContext {...};
|
||||
// ...
|
||||
// s.template context<MyContext>();
|
||||
// NOTE:
|
||||
// if your context has no additional usage outside of serialization flow,
|
||||
// then you can create it internally via configuration (see inheritance.cpp)
|
||||
using Context = std::tuple<int, std::pair<uint32_t, uint32_t>>;
|
||||
|
||||
//use fixed-size buffer
|
||||
// use fixed-size buffer
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
// define adapter types,
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
int
|
||||
main()
|
||||
{
|
||||
|
||||
MyTypes::GameState data{};
|
||||
data.monsters.push_back({"weaksy", 100, 200});
|
||||
data.monsters.push_back({"bigsy", 500, 1000});
|
||||
data.monsters.push_back({"tootoo", 350, 750});
|
||||
MyTypes::GameState data{};
|
||||
data.monsters.push_back({ "weaksy", 100, 200 });
|
||||
data.monsters.push_back({ "bigsy", 500, 1000 });
|
||||
data.monsters.push_back({ "tootoo", 350, 750 });
|
||||
|
||||
//set context
|
||||
Context ctx{};
|
||||
//max monsters
|
||||
std::get<0>(ctx) = 4;
|
||||
//damage range
|
||||
std::get<1>(ctx).first = 100;
|
||||
std::get<1>(ctx).second = 1000;
|
||||
// set context
|
||||
Context ctx{};
|
||||
// max monsters
|
||||
std::get<0>(ctx) = 4;
|
||||
// damage range
|
||||
std::get<1>(ctx).first = 100;
|
||||
std::get<1>(ctx).second = 1000;
|
||||
|
||||
// create buffer to store data to
|
||||
Buffer buffer{};
|
||||
auto writtenSize =
|
||||
bitsery::quickSerialization(ctx, OutputAdapter{ buffer }, data);
|
||||
|
||||
//create buffer to store data to
|
||||
Buffer buffer{};
|
||||
auto writtenSize = bitsery::quickSerialization(ctx, OutputAdapter{buffer}, data);
|
||||
MyTypes::GameState res{};
|
||||
auto state = bitsery::quickDeserialization(
|
||||
ctx, InputAdapter{ buffer.begin(), writtenSize }, res);
|
||||
|
||||
MyTypes::GameState res{};
|
||||
auto state = bitsery::quickDeserialization(ctx, InputAdapter{buffer.begin(), writtenSize}, res);
|
||||
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
}
|
||||
|
||||
@@ -1,55 +1,68 @@
|
||||
#include <bitsery/bitsery.h>
|
||||
//in order to work with streams include stream adapter
|
||||
// in order to work with streams include stream adapter
|
||||
#include <bitsery/adapter/stream.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
enum class MyEnum:uint16_t { V1,V2,V3 };
|
||||
struct MyStruct {
|
||||
uint32_t i;
|
||||
MyEnum e;
|
||||
double f;
|
||||
enum class MyEnum : uint16_t
|
||||
{
|
||||
V1,
|
||||
V2,
|
||||
V3
|
||||
};
|
||||
struct MyStruct
|
||||
{
|
||||
uint32_t i;
|
||||
MyEnum e;
|
||||
double f;
|
||||
};
|
||||
|
||||
//define how object should be serialized/deserialized
|
||||
template <typename S>
|
||||
void serialize(S& s, MyStruct& o) {
|
||||
s.value4b(o.i);
|
||||
s.value2b(o.e);
|
||||
s.value8b(o.f);
|
||||
// define how object should be serialized/deserialized
|
||||
template<typename S>
|
||||
void
|
||||
serialize(S& s, MyStruct& o)
|
||||
{
|
||||
s.value4b(o.i);
|
||||
s.value2b(o.e);
|
||||
s.value8b(o.f);
|
||||
}
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
MyStruct data{8941, MyEnum::V2, 0.045};
|
||||
MyStruct res{};
|
||||
int
|
||||
main()
|
||||
{
|
||||
// set some random data
|
||||
MyStruct data{ 8941, MyEnum::V2, 0.045 };
|
||||
MyStruct res{};
|
||||
|
||||
//open file stream for writing and reading
|
||||
auto fileName = "test_file.bin";
|
||||
std::fstream s{fileName, s.binary | s.trunc | s.out};
|
||||
if (!s.is_open()) {
|
||||
std::cout << "cannot open " << fileName << " for writing\n";
|
||||
return 0;
|
||||
}
|
||||
// open file stream for writing and reading
|
||||
auto fileName = "test_file.bin";
|
||||
std::fstream s{ fileName, s.binary | s.trunc | s.out };
|
||||
if (!s.is_open()) {
|
||||
std::cout << "cannot open " << fileName << " for writing\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
//we cannot use quick serialization function, because streams cannot use writtenBytesCount method
|
||||
bitsery::Serializer<bitsery::OutputBufferedStreamAdapter> ser{s};
|
||||
ser.object(data);
|
||||
//flush to writer
|
||||
ser.adapter().flush();
|
||||
s.close();
|
||||
//reopen for reading
|
||||
// we cannot use quick serialization function, because streams cannot use
|
||||
// writtenBytesCount method
|
||||
bitsery::Serializer<bitsery::OutputBufferedStreamAdapter> ser{ s };
|
||||
ser.object(data);
|
||||
// flush to writer
|
||||
ser.adapter().flush();
|
||||
s.close();
|
||||
// reopen for reading
|
||||
|
||||
s.open(fileName, s.binary | s.in);
|
||||
if (!s.is_open()) {
|
||||
std::cout << "cannot open " << fileName << " for reading\n";
|
||||
return 0;
|
||||
}
|
||||
s.open(fileName, s.binary | s.in);
|
||||
if (!s.is_open()) {
|
||||
std::cout << "cannot open " << fileName << " for reading\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
//same as serialization, but returns deserialization state as a pair
|
||||
//first = error code, second = is buffer was successfully read from begin to the end.
|
||||
auto state = bitsery::quickDeserialization<bitsery::InputStreamAdapter>(s, res);
|
||||
// same as serialization, but returns deserialization state as a pair
|
||||
// first = error code, second = is buffer was successfully read from begin to
|
||||
// the end.
|
||||
auto state =
|
||||
bitsery::quickDeserialization<bitsery::InputStreamAdapter>(s, res);
|
||||
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data.f == res.f && data.i == res.i && data.e == res.e);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data.f == res.f && data.i == res.i && data.e == res.e);
|
||||
}
|
||||
|
||||
@@ -1,92 +1,116 @@
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
//include traits for types, that we'll be using
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <bitsery/bitsery.h>
|
||||
// include traits for types, that we'll be using
|
||||
#include <bitsery/traits/array.h>
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
//include extension that will allow to have backward/forward compatibility
|
||||
// include extension that will allow to have backward/forward compatibility
|
||||
#include <bitsery/ext/growable.h>
|
||||
|
||||
namespace MyTypes {
|
||||
|
||||
//define data
|
||||
enum Color:uint8_t { Red, Green, Blue };
|
||||
// define data
|
||||
enum Color : uint8_t
|
||||
{
|
||||
Red,
|
||||
Green,
|
||||
Blue
|
||||
};
|
||||
|
||||
struct Vec3 { float x, y, z; };
|
||||
struct Vec3
|
||||
{
|
||||
float x, y, z;
|
||||
};
|
||||
|
||||
struct Weapon {
|
||||
std::string name{};
|
||||
int16_t damage{};
|
||||
Weapon() = default;
|
||||
Weapon(const std::string& _name, int16_t dmg):name{_name}, damage{dmg} {}
|
||||
private:
|
||||
//define serialize function as private, and give access to bitsery
|
||||
friend bitsery::Access;
|
||||
template <typename S>
|
||||
void serialize (S& s) {
|
||||
//forward/backward compatibility for monsters
|
||||
s.ext(*this, bitsery::ext::Growable{}, [](S& s, Weapon& o1) {
|
||||
s.text1b(o1.name, 20);
|
||||
s.value2b(o1.damage);
|
||||
});
|
||||
}
|
||||
};
|
||||
struct Weapon
|
||||
{
|
||||
std::string name{};
|
||||
int16_t damage{};
|
||||
Weapon() = default;
|
||||
Weapon(const std::string& _name, int16_t dmg)
|
||||
: name{ _name }
|
||||
, damage{ dmg }
|
||||
{
|
||||
}
|
||||
|
||||
struct Monster {
|
||||
Vec3 pos;
|
||||
int16_t mana;
|
||||
int16_t hp;
|
||||
std::string name;
|
||||
std::vector<uint8_t> inventory;
|
||||
Color color;
|
||||
std::vector<Weapon> weapons;
|
||||
Weapon equipped;
|
||||
std::vector<Vec3> path;
|
||||
};
|
||||
private:
|
||||
// define serialize function as private, and give access to bitsery
|
||||
friend bitsery::Access;
|
||||
template<typename S>
|
||||
void serialize(S& s)
|
||||
{
|
||||
// forward/backward compatibility for monsters
|
||||
s.ext(*this, bitsery::ext::Growable{}, [](S& s, Weapon& o1) {
|
||||
s.text1b(o1.name, 20);
|
||||
s.value2b(o1.damage);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, Vec3& o) {
|
||||
s.value4b(o.x);
|
||||
s.value4b(o.y);
|
||||
s.value4b(o.z);
|
||||
}
|
||||
struct Monster
|
||||
{
|
||||
Vec3 pos;
|
||||
int16_t mana;
|
||||
int16_t hp;
|
||||
std::string name;
|
||||
std::vector<uint8_t> inventory;
|
||||
Color color;
|
||||
std::vector<Weapon> weapons;
|
||||
Weapon equipped;
|
||||
std::vector<Vec3> path;
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize (S& s, Monster& o) {
|
||||
//forward/backward compatibility for monsters
|
||||
s.ext(o, bitsery::ext::Growable{}, [](S& s, Monster& o1) {
|
||||
s.value1b(o1.color);
|
||||
s.value2b(o1.mana);
|
||||
s.value2b(o1.hp);
|
||||
s.object(o1.equipped);
|
||||
s.object(o1.pos);
|
||||
s.container(o1.path, 1000);
|
||||
s.container(o1.weapons, 100);
|
||||
s.container1b(o1.inventory, 50);
|
||||
s.text1b(o1.name, 20);
|
||||
});
|
||||
}
|
||||
template<typename S>
|
||||
void
|
||||
serialize(S& s, Vec3& o)
|
||||
{
|
||||
s.value4b(o.x);
|
||||
s.value4b(o.y);
|
||||
s.value4b(o.z);
|
||||
}
|
||||
|
||||
//use fixed-size buffer
|
||||
template<typename S>
|
||||
void
|
||||
serialize(S& s, Monster& o)
|
||||
{
|
||||
// forward/backward compatibility for monsters
|
||||
s.ext(o, bitsery::ext::Growable{}, [](S& s, Monster& o1) {
|
||||
s.value1b(o1.color);
|
||||
s.value2b(o1.mana);
|
||||
s.value2b(o1.hp);
|
||||
s.object(o1.equipped);
|
||||
s.object(o1.pos);
|
||||
s.container(o1.path, 1000);
|
||||
s.container(o1.weapons, 100);
|
||||
s.container1b(o1.inventory, 50);
|
||||
s.text1b(o1.name, 20);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// use fixed-size buffer
|
||||
using Buffer = std::array<uint8_t, 10000>;
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
MyTypes::Monster data{};
|
||||
data.name = "lew";
|
||||
data.weapons.push_back(MyTypes::Weapon{"GoodWeapon", 100});
|
||||
int
|
||||
main()
|
||||
{
|
||||
// set some random data
|
||||
MyTypes::Monster data{};
|
||||
data.name = "lew";
|
||||
data.weapons.push_back(MyTypes::Weapon{ "GoodWeapon", 100 });
|
||||
|
||||
//create buffer to store data to
|
||||
Buffer buffer{};
|
||||
//since we're using different configuration, we cannot use quickSerialization function.
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
// create buffer to store data to
|
||||
Buffer buffer{};
|
||||
// since we're using different configuration, we cannot use quickSerialization
|
||||
// function.
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
|
||||
MyTypes::Monster res{};
|
||||
//deserialize
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
MyTypes::Monster res{};
|
||||
// deserialize
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>(
|
||||
{ buffer.begin(), writtenSize }, res);
|
||||
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
}
|
||||
|
||||
@@ -1,104 +1,136 @@
|
||||
//
|
||||
//this example covers all the corner cases that can happen using inheritance
|
||||
//in reality virtual inherintance is usually avoided, so your code would look much simpler.
|
||||
// this example covers all the corner cases that can happen using inheritance
|
||||
// in reality virtual inherintance is usually avoided, so your code would look
|
||||
// much simpler.
|
||||
//
|
||||
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
|
||||
//include inheritance extension
|
||||
//this header contains two extensions, that specifies inheritance type of base class
|
||||
// BaseClass - normal inheritance
|
||||
// VirtualBaseClass - when virtual inheritance is used
|
||||
//in order for virtual inheritance to work, InheritanceContext is required. for normal inheritance it is not required
|
||||
// include inheritance extension
|
||||
// this header contains two extensions, that specifies inheritance type of base
|
||||
// class
|
||||
// BaseClass - normal inheritance
|
||||
// VirtualBaseClass - when virtual inheritance is used
|
||||
// in order for virtual inheritance to work, InheritanceContext is required. for
|
||||
// normal inheritance it is not required
|
||||
#include <bitsery/ext/inheritance.h>
|
||||
|
||||
using bitsery::ext::BaseClass;
|
||||
using bitsery::ext::VirtualBaseClass;
|
||||
|
||||
struct Base {
|
||||
uint8_t x{};
|
||||
//Base doesn't have to be polymorphic class, inheritance works at compile-time.
|
||||
struct Base
|
||||
{
|
||||
uint8_t x{};
|
||||
// Base doesn't have to be polymorphic class, inheritance works at
|
||||
// compile-time.
|
||||
};
|
||||
template <typename S>
|
||||
void serialize(S& s, Base& o) {
|
||||
s.value1b(o.x);
|
||||
template<typename S>
|
||||
void
|
||||
serialize(S& s, Base& o)
|
||||
{
|
||||
s.value1b(o.x);
|
||||
}
|
||||
|
||||
struct Derive1:virtual Base {// virtually inherits from base
|
||||
uint8_t y1{};
|
||||
struct Derive1 : virtual Base
|
||||
{ // virtually inherits from base
|
||||
uint8_t y1{};
|
||||
};
|
||||
template <typename S>
|
||||
void serialize(S& s, Derive1& o) {
|
||||
//define virtual inheritance, it will not compile if InheritanceContext is not defined in serializer/deserializer
|
||||
s.ext(o, VirtualBaseClass<Base>{});
|
||||
s.value1b(o.y1);
|
||||
template<typename S>
|
||||
void
|
||||
serialize(S& s, Derive1& o)
|
||||
{
|
||||
// define virtual inheritance, it will not compile if InheritanceContext is
|
||||
// not defined in serializer/deserializer
|
||||
s.ext(o, VirtualBaseClass<Base>{});
|
||||
s.value1b(o.y1);
|
||||
}
|
||||
|
||||
//to make it more interesting, serialize private member
|
||||
struct Derived2:virtual Base {
|
||||
explicit Derived2(uint8_t y):y2{y} {}
|
||||
// to make it more interesting, serialize private member
|
||||
struct Derived2 : virtual Base
|
||||
{
|
||||
explicit Derived2(uint8_t y)
|
||||
: y2{ y }
|
||||
{
|
||||
}
|
||||
|
||||
uint8_t getY2() const { return y2; };
|
||||
|
||||
uint8_t getY2() const {
|
||||
return y2;
|
||||
};
|
||||
private:
|
||||
friend bitsery::Access;
|
||||
uint8_t y2{};
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
//notice virtual inheritance
|
||||
s.ext(*this, VirtualBaseClass<Base>{});
|
||||
s.value1b(y2);
|
||||
}
|
||||
friend bitsery::Access;
|
||||
uint8_t y2{};
|
||||
template<typename S>
|
||||
void serialize(S& s)
|
||||
{
|
||||
// notice virtual inheritance
|
||||
s.ext(*this, VirtualBaseClass<Base>{});
|
||||
s.value1b(y2);
|
||||
}
|
||||
};
|
||||
|
||||
struct MultipleInheritance: Derive1, Derived2 {
|
||||
explicit MultipleInheritance(uint8_t y2):Derived2{y2} {}
|
||||
uint8_t z{};
|
||||
struct MultipleInheritance
|
||||
: Derive1
|
||||
, Derived2
|
||||
{
|
||||
explicit MultipleInheritance(uint8_t y2)
|
||||
: Derived2{ y2 }
|
||||
{
|
||||
}
|
||||
uint8_t z{};
|
||||
};
|
||||
template <typename S>
|
||||
void serialize(S& s, MultipleInheritance& o) {
|
||||
//has two bases, serialize them separately
|
||||
s.ext(o, BaseClass<Derive1>{});
|
||||
s.ext(o, BaseClass<Derived2>{});
|
||||
s.value1b(o.z);
|
||||
template<typename S>
|
||||
void
|
||||
serialize(S& s, MultipleInheritance& o)
|
||||
{
|
||||
// has two bases, serialize them separately
|
||||
s.ext(o, BaseClass<Derive1>{});
|
||||
s.ext(o, BaseClass<Derived2>{});
|
||||
s.value1b(o.z);
|
||||
}
|
||||
|
||||
namespace bitsery {
|
||||
// call to serialize function with Derived2 and MultipleInheritance is ambiguous,
|
||||
// it matches two serialize functions: Base classes non-member fnc and Derived2 member fnc
|
||||
// we need explicitly select which function to use
|
||||
template <>
|
||||
struct SelectSerializeFnc<Derived2>:UseMemberFnc {};
|
||||
// call to serialize function with Derived2 and MultipleInheritance is
|
||||
// ambiguous, it matches two serialize functions: Base classes non-member fnc
|
||||
// and Derived2 member fnc we need explicitly select which function to use
|
||||
template<>
|
||||
struct SelectSerializeFnc<Derived2> : UseMemberFnc
|
||||
{
|
||||
};
|
||||
|
||||
//multiple inheritance has non-member serialize function defined
|
||||
template <>
|
||||
struct SelectSerializeFnc<MultipleInheritance>:UseNonMemberFnc {};
|
||||
// multiple inheritance has non-member serialize function defined
|
||||
template<>
|
||||
struct SelectSerializeFnc<MultipleInheritance> : UseNonMemberFnc
|
||||
{
|
||||
};
|
||||
}
|
||||
|
||||
//some helper types
|
||||
// some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using Writer = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using Reader = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
int
|
||||
main()
|
||||
{
|
||||
|
||||
MultipleInheritance data{98};
|
||||
data.x = 254;
|
||||
data.y1 = 47;
|
||||
data.z = 1;
|
||||
MultipleInheritance data{ 98 };
|
||||
data.x = 254;
|
||||
data.y1 = 47;
|
||||
data.z = 1;
|
||||
|
||||
Buffer buf{};
|
||||
Buffer buf{};
|
||||
|
||||
bitsery::ext::InheritanceContext ctx1;
|
||||
auto writtenSize = bitsery::quickSerialization(ctx1, Writer{buf}, data);
|
||||
assert(writtenSize == 4);//base is serialized once, because it is inherited virtually
|
||||
bitsery::ext::InheritanceContext ctx1;
|
||||
auto writtenSize = bitsery::quickSerialization(ctx1, Writer{ buf }, data);
|
||||
assert(writtenSize ==
|
||||
4); // base is serialized once, because it is inherited virtually
|
||||
|
||||
MultipleInheritance res{0};
|
||||
bitsery::ext::InheritanceContext ctx2;
|
||||
auto state = bitsery::quickDeserialization(ctx2, Reader{buf.begin(), writtenSize}, res);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data.x == res.x && data.y1 == res.y1 && data.getY2() == res.getY2() && data.z == res.z);
|
||||
MultipleInheritance res{ 0 };
|
||||
bitsery::ext::InheritanceContext ctx2;
|
||||
auto state = bitsery::quickDeserialization(
|
||||
ctx2, Reader{ buf.begin(), writtenSize }, res);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data.x == res.x && data.y1 == res.y1 && data.getY2() == res.getY2() &&
|
||||
data.z == res.z);
|
||||
}
|
||||
|
||||
@@ -2,61 +2,73 @@
|
||||
// example of how to deserialize non default constructible objects
|
||||
//
|
||||
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
|
||||
class MyData {
|
||||
//define your private data
|
||||
float _x{0};
|
||||
float _y{0};
|
||||
//make bitsery:Access friend
|
||||
friend class bitsery::Access;
|
||||
//create default constructor, don't worry about class invariant, it will be restored in deserialization
|
||||
MyData() = default;
|
||||
//define serialize function
|
||||
class MyData
|
||||
{
|
||||
// define your private data
|
||||
float _x{ 0 };
|
||||
float _y{ 0 };
|
||||
// make bitsery:Access friend
|
||||
friend class bitsery::Access;
|
||||
// create default constructor, don't worry about class invariant, it will be
|
||||
// restored in deserialization
|
||||
MyData() = default;
|
||||
// define serialize function
|
||||
|
||||
template<typename S>
|
||||
void serialize(S& s)
|
||||
{
|
||||
s.value4b(_x);
|
||||
s.value4b(_y);
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value4b(_x);
|
||||
s.value4b(_y);
|
||||
}
|
||||
public:
|
||||
//define non default public constructor
|
||||
MyData(float x, float y):_x{x}, _y{y} {}
|
||||
//this is for convenience
|
||||
bool operator ==(const MyData&rhs) const {
|
||||
return _x == rhs._x && _y == rhs._y;
|
||||
}
|
||||
// define non default public constructor
|
||||
MyData(float x, float y)
|
||||
: _x{ x }
|
||||
, _y{ y }
|
||||
{
|
||||
}
|
||||
// this is for convenience
|
||||
bool operator==(const MyData& rhs) const
|
||||
{
|
||||
return _x == rhs._x && _y == rhs._y;
|
||||
}
|
||||
};
|
||||
|
||||
//some helper types
|
||||
// some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using Writer = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using Reader = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
int
|
||||
main()
|
||||
{
|
||||
|
||||
//initialize our data
|
||||
std::vector<MyData> data{};
|
||||
data.emplace_back(145.4f, 84.48f);
|
||||
std::vector<MyData> res{};
|
||||
// initialize our data
|
||||
std::vector<MyData> data{};
|
||||
data.emplace_back(145.4f, 84.48f);
|
||||
std::vector<MyData> res{};
|
||||
|
||||
//create buffer
|
||||
Buffer buffer{};
|
||||
// create buffer
|
||||
Buffer buffer{};
|
||||
|
||||
//we cant use quick (de)serialization helper methods, because we ant to serialize container directly
|
||||
//create writer and serialize container
|
||||
bitsery::Serializer<Writer> ser{buffer};
|
||||
ser.container(data, 10);
|
||||
ser.adapter().flush();
|
||||
// we cant use quick (de)serialization helper methods, because we ant to
|
||||
// serialize container directly create writer and serialize container
|
||||
bitsery::Serializer<Writer> ser{ buffer };
|
||||
ser.container(data, 10);
|
||||
ser.adapter().flush();
|
||||
|
||||
//create reader and deserialize container
|
||||
bitsery::Deserializer<Reader> des{buffer.begin(), ser.adapter().writtenBytesCount()};
|
||||
des.container(res, 10);
|
||||
// create reader and deserialize container
|
||||
bitsery::Deserializer<Reader> des{ buffer.begin(),
|
||||
ser.adapter().writtenBytesCount() };
|
||||
des.container(res, 10);
|
||||
|
||||
//check if everything went ok
|
||||
assert(des.adapter().error() == bitsery::ReaderError::NoError && des.adapter().isCompletedSuccessfully());
|
||||
assert(res == data);
|
||||
// check if everything went ok
|
||||
assert(des.adapter().error() == bitsery::ReaderError::NoError &&
|
||||
des.adapter().isCompletedSuccessfully());
|
||||
assert(res == data);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,145 +1,166 @@
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
|
||||
//include pointers extension
|
||||
//this header contains multiple extensions for different pointer types and pointer linking context,
|
||||
//that validates pointer ownership and checks if there are and no dangling pointers after serialization/deserialization.
|
||||
//dangling pointer in this context means, that non-owning pointer points to data, that was not serialized.
|
||||
// include pointers extension
|
||||
// this header contains multiple extensions for different pointer types and
|
||||
// pointer linking context, that validates pointer ownership and checks if there
|
||||
// are and no dangling pointers after serialization/deserialization. dangling
|
||||
// pointer in this context means, that non-owning pointer points to data, that
|
||||
// was not serialized.
|
||||
#include <bitsery/ext/pointer.h>
|
||||
|
||||
using bitsery::ext::ReferencedByPointer;
|
||||
using bitsery::ext::PointerObserver;
|
||||
using bitsery::ext::PointerOwner;
|
||||
using bitsery::ext::PointerType ;
|
||||
using bitsery::ext::PointerType;
|
||||
using bitsery::ext::ReferencedByPointer;
|
||||
|
||||
enum class MyEnum:uint16_t { V1,V2,V3 };
|
||||
struct MyStruct {
|
||||
MyStruct(uint32_t i_, MyEnum e_, std::vector<float> fs_)
|
||||
:i{i_},
|
||||
e{e_},
|
||||
fs{fs_} {}
|
||||
MyStruct():MyStruct{0, MyEnum::V1, {}} {}
|
||||
uint32_t i;
|
||||
MyEnum e;
|
||||
std::vector<float> fs;
|
||||
enum class MyEnum : uint16_t
|
||||
{
|
||||
V1,
|
||||
V2,
|
||||
V3
|
||||
};
|
||||
struct MyStruct
|
||||
{
|
||||
MyStruct(uint32_t i_, MyEnum e_, std::vector<float> fs_)
|
||||
: i{ i_ }
|
||||
, e{ e_ }
|
||||
, fs{ fs_ }
|
||||
{
|
||||
}
|
||||
MyStruct()
|
||||
: MyStruct{ 0, MyEnum::V1, {} }
|
||||
{
|
||||
}
|
||||
uint32_t i;
|
||||
MyEnum e;
|
||||
std::vector<float> fs;
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, MyStruct& o) {
|
||||
s.value4b(o.i);
|
||||
s.value2b(o.e);
|
||||
s.container4b(o.fs, 10);
|
||||
template<typename S>
|
||||
void
|
||||
serialize(S& s, MyStruct& o)
|
||||
{
|
||||
s.value4b(o.i);
|
||||
s.value2b(o.e);
|
||||
s.container4b(o.fs, 10);
|
||||
}
|
||||
|
||||
//our test data
|
||||
struct Test1Data {
|
||||
//regular data, nothing fancy here
|
||||
MyStruct o1;
|
||||
int32_t i1;
|
||||
//these container elements can be referenced by pointers
|
||||
std::vector<MyStruct> vdata;
|
||||
//container that holds non owning pointers (observers),
|
||||
std::vector<MyStruct*> vptr;
|
||||
//treat it as is observer
|
||||
MyStruct* po1;
|
||||
//we treat this as owner (responsible for allocation/deallocation
|
||||
int32_t* pi1;
|
||||
// our test data
|
||||
struct Test1Data
|
||||
{
|
||||
// regular data, nothing fancy here
|
||||
MyStruct o1;
|
||||
int32_t i1;
|
||||
// these container elements can be referenced by pointers
|
||||
std::vector<MyStruct> vdata;
|
||||
// container that holds non owning pointers (observers),
|
||||
std::vector<MyStruct*> vptr;
|
||||
// treat it as is observer
|
||||
MyStruct* po1;
|
||||
// we treat this as owner (responsible for allocation/deallocation
|
||||
int32_t* pi1;
|
||||
|
||||
private:
|
||||
friend bitsery::Access;
|
||||
friend bitsery::Access;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
//just a regular fields
|
||||
s.object(o1);
|
||||
s.value4b(i1);
|
||||
template<typename S>
|
||||
void serialize(S& s)
|
||||
{
|
||||
// just a regular fields
|
||||
s.object(o1);
|
||||
s.value4b(i1);
|
||||
|
||||
//set container elements to be candidates for non-owning pointers
|
||||
s.container(vdata, 100, [](S& s, MyStruct& d){
|
||||
s.ext(d, ReferencedByPointer{});
|
||||
});
|
||||
//contains non owning pointers
|
||||
//
|
||||
//IMPORTANT !!!
|
||||
//ALWAYS ACCEPT BY REFERENCE like this: T* (&obj)
|
||||
//if using c++14, then auto& always works.
|
||||
//
|
||||
//you can also serialize non owning pointers first, pointer linking context will keep track on them
|
||||
//and as soon as pointer owner data is deserialized, all non-owning pointers will be updated
|
||||
s.container(vptr, 100, [](S& s, MyStruct* (&d)){
|
||||
s.ext(d, PointerObserver{});
|
||||
});
|
||||
//observer
|
||||
s.ext(po1, PointerObserver{});
|
||||
//owner, mark it as not null
|
||||
s.ext4b(pi1, PointerOwner{PointerType::NotNull});
|
||||
|
||||
}
|
||||
// set container elements to be candidates for non-owning pointers
|
||||
s.container(
|
||||
vdata, 100, [](S& s, MyStruct& d) { s.ext(d, ReferencedByPointer{}); });
|
||||
// contains non owning pointers
|
||||
//
|
||||
// IMPORTANT !!!
|
||||
// ALWAYS ACCEPT BY REFERENCE like this: T* (&obj)
|
||||
// if using c++14, then auto& always works.
|
||||
//
|
||||
// you can also serialize non owning pointers first, pointer linking context
|
||||
// will keep track on them and as soon as pointer owner data is
|
||||
// deserialized, all non-owning pointers will be updated
|
||||
s.container(
|
||||
vptr, 100, [](S& s, MyStruct*(&d)) { s.ext(d, PointerObserver{}); });
|
||||
// observer
|
||||
s.ext(po1, PointerObserver{});
|
||||
// owner, mark it as not null
|
||||
s.ext4b(pi1, PointerOwner{ PointerType::NotNull });
|
||||
}
|
||||
};
|
||||
|
||||
//some helper types
|
||||
// some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using Writer = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using Reader = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
//we will need PointerLinkingContext to work with pointers
|
||||
//if we would require additional context for our own custom flow, we can define it as tuple like this:
|
||||
// std::tuple<MyContext,ext::PointerLinkingContext>
|
||||
//and other code will work as expected as long as it cast to proper type.
|
||||
//see context_usage.cpp for usage example
|
||||
// we will need PointerLinkingContext to work with pointers
|
||||
// if we would require additional context for our own custom flow, we can define
|
||||
// it as tuple like this:
|
||||
// std::tuple<MyContext,ext::PointerLinkingContext>
|
||||
// and other code will work as expected as long as it cast to proper type.
|
||||
// see context_usage.cpp for usage example
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
Test1Data data{};
|
||||
data.vdata.emplace_back(8941, MyEnum::V1, std::vector<float>{4.4f});
|
||||
data.vdata.emplace_back(15478, MyEnum::V2, std::vector<float>{15.0f});
|
||||
data.vdata.emplace_back(59, MyEnum::V3, std::vector<float>{-8.5f, 0.045f});
|
||||
//container of non owning pointers (observers)
|
||||
data.vptr.emplace_back(nullptr);
|
||||
data.vptr.emplace_back(std::addressof(data.vdata[0]));
|
||||
data.vptr.emplace_back(std::addressof(data.vdata[2]));
|
||||
//regular fields
|
||||
data.o1 = MyStruct{4, MyEnum::V2, {57.078f}};
|
||||
data.i1 = 9455;
|
||||
//observer
|
||||
data.po1 = std::addressof(data.vdata[1]);
|
||||
//owning pointer
|
||||
data.pi1 = new int32_t{};
|
||||
int
|
||||
main()
|
||||
{
|
||||
// set some random data
|
||||
Test1Data data{};
|
||||
data.vdata.emplace_back(8941, MyEnum::V1, std::vector<float>{ 4.4f });
|
||||
data.vdata.emplace_back(15478, MyEnum::V2, std::vector<float>{ 15.0f });
|
||||
data.vdata.emplace_back(59, MyEnum::V3, std::vector<float>{ -8.5f, 0.045f });
|
||||
// container of non owning pointers (observers)
|
||||
data.vptr.emplace_back(nullptr);
|
||||
data.vptr.emplace_back(std::addressof(data.vdata[0]));
|
||||
data.vptr.emplace_back(std::addressof(data.vdata[2]));
|
||||
// regular fields
|
||||
data.o1 = MyStruct{ 4, MyEnum::V2, { 57.078f } };
|
||||
data.i1 = 9455;
|
||||
// observer
|
||||
data.po1 = std::addressof(data.vdata[1]);
|
||||
// owning pointer
|
||||
data.pi1 = new int32_t{};
|
||||
|
||||
//create buffer to store data
|
||||
Buffer buffer{};
|
||||
size_t writtenSize{};
|
||||
//in order to use pointers, we need to pass pointer linking context serializer/deserializer
|
||||
{
|
||||
bitsery::ext::PointerLinkingContext ctx{};
|
||||
writtenSize = quickSerialization(ctx, Writer{buffer}, data);
|
||||
// create buffer to store data
|
||||
Buffer buffer{};
|
||||
size_t writtenSize{};
|
||||
// in order to use pointers, we need to pass pointer linking context
|
||||
// serializer/deserializer
|
||||
{
|
||||
bitsery::ext::PointerLinkingContext ctx{};
|
||||
writtenSize = quickSerialization(ctx, Writer{ buffer }, data);
|
||||
|
||||
//make sure that pointer linking context is valid
|
||||
//this ensures that all non-owning pointers points to data that has been serialized,
|
||||
//so we can successfully reconstruct pointers after deserialization
|
||||
assert(ctx.isValid());
|
||||
}
|
||||
// make sure that pointer linking context is valid
|
||||
// this ensures that all non-owning pointers points to data that has been
|
||||
// serialized, so we can successfully reconstruct pointers after
|
||||
// deserialization
|
||||
assert(ctx.isValid());
|
||||
}
|
||||
|
||||
Test1Data res{};
|
||||
{
|
||||
bitsery::ext::PointerLinkingContext ctx{};
|
||||
auto state = quickDeserialization(ctx, Reader{buffer.begin(), writtenSize}, res);
|
||||
//check if everything went find
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
//also check for dangling pointers, after deserialization
|
||||
assert(ctx.isValid());
|
||||
}
|
||||
//owning pointers owns data
|
||||
assert(*res.pi1 == *data.pi1);
|
||||
assert(res.pi1 != data.pi1);
|
||||
//observers, points to other data
|
||||
assert(res.vptr[0] == nullptr);
|
||||
assert(res.vptr[1] == std::addressof(res.vdata[0]));
|
||||
assert(res.vptr[2] == std::addressof(res.vdata[2]));
|
||||
assert(res.po1 == std::addressof(res.vdata[1]));
|
||||
Test1Data res{};
|
||||
{
|
||||
bitsery::ext::PointerLinkingContext ctx{};
|
||||
auto state =
|
||||
quickDeserialization(ctx, Reader{ buffer.begin(), writtenSize }, res);
|
||||
// check if everything went find
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
// also check for dangling pointers, after deserialization
|
||||
assert(ctx.isValid());
|
||||
}
|
||||
// owning pointers owns data
|
||||
assert(*res.pi1 == *data.pi1);
|
||||
assert(res.pi1 != data.pi1);
|
||||
// observers, points to other data
|
||||
assert(res.vptr[0] == nullptr);
|
||||
assert(res.vptr[1] == std::addressof(res.vdata[0]));
|
||||
assert(res.vptr[2] == std::addressof(res.vdata[2]));
|
||||
assert(res.po1 == std::addressof(res.vdata[1]));
|
||||
|
||||
//delete raw owning pointers
|
||||
delete data.pi1;
|
||||
delete res.pi1;
|
||||
// delete raw owning pointers
|
||||
delete data.pi1;
|
||||
delete res.pi1;
|
||||
}
|
||||
|
||||
@@ -1,269 +1,309 @@
|
||||
//
|
||||
// Created by fraillt on 18.4.26.
|
||||
//
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/ext/inheritance.h>
|
||||
#include <bitsery/ext/pointer.h>
|
||||
#include <bitsery/ext/std_smart_ptr.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/ext/pointer.h>
|
||||
#include <bitsery/ext/inheritance.h>
|
||||
#include <bitsery/ext/std_smart_ptr.h>
|
||||
|
||||
//in order to work with polymorphic types, we need to describe few steps:
|
||||
// 1) describe relationships between base and derived types
|
||||
// this will allow to know what are possible types reachable from base class
|
||||
// 2) bind serializer to base class
|
||||
// this will allow to iterate through all types, and add serialization functions,
|
||||
// without this step compiler would simply remove functions that are not bound at compile-time even it we use type at runtime.
|
||||
// in order to work with polymorphic types, we need to describe few steps:
|
||||
// 1) describe relationships between base and derived types
|
||||
// this will allow to know what are possible types reachable from base class
|
||||
// 2) bind serializer to base class
|
||||
// this will allow to iterate through all types, and add serialization
|
||||
// functions, without this step compiler would simply remove functions that are
|
||||
// not bound at compile-time even it we use type at runtime.
|
||||
|
||||
using bitsery::ext::BaseClass;
|
||||
|
||||
using bitsery::ext::PointerObserver;
|
||||
using bitsery::ext::StdSmartPtr;
|
||||
|
||||
//define our data structures
|
||||
struct Color {
|
||||
float r{}, g{}, b{};
|
||||
bool operator == (const Color& o) const {
|
||||
return std::tie(r, g, b) ==
|
||||
std::tie(o.r, o.g, o.b);
|
||||
}
|
||||
|
||||
// define our data structures
|
||||
struct Color
|
||||
{
|
||||
float r{}, g{}, b{};
|
||||
bool operator==(const Color& o) const
|
||||
{
|
||||
return std::tie(r, g, b) == std::tie(o.r, o.g, o.b);
|
||||
}
|
||||
};
|
||||
|
||||
struct Shape {
|
||||
Color clr{};
|
||||
virtual ~Shape() = 0;
|
||||
struct Shape
|
||||
{
|
||||
Color clr{};
|
||||
virtual ~Shape() = 0;
|
||||
};
|
||||
|
||||
Shape::~Shape() = default;
|
||||
|
||||
struct Circle : Shape {
|
||||
int32_t radius{};
|
||||
bool operator == (const Circle& o) const {
|
||||
return std::tie(radius, clr) ==
|
||||
std::tie(o.radius, o.clr);
|
||||
}
|
||||
|
||||
struct Circle : Shape
|
||||
{
|
||||
int32_t radius{};
|
||||
bool operator==(const Circle& o) const
|
||||
{
|
||||
return std::tie(radius, clr) == std::tie(o.radius, o.clr);
|
||||
}
|
||||
};
|
||||
|
||||
struct Rectangle : Shape {
|
||||
int32_t width{};
|
||||
int32_t height{};
|
||||
bool operator == (const Rectangle& o) const {
|
||||
return std::tie(width, height, clr) ==
|
||||
std::tie(o.width, o.height, o.clr);
|
||||
}
|
||||
struct Rectangle : Shape
|
||||
{
|
||||
int32_t width{};
|
||||
int32_t height{};
|
||||
bool operator==(const Rectangle& o) const
|
||||
{
|
||||
return std::tie(width, height, clr) == std::tie(o.width, o.height, o.clr);
|
||||
}
|
||||
};
|
||||
|
||||
struct RoundedRectangle : Rectangle {
|
||||
int32_t radius{};
|
||||
bool operator == (const RoundedRectangle& o) const {
|
||||
return std::tie(radius, static_cast<const Rectangle&>(*this)) ==
|
||||
std::tie(o.radius, static_cast<const Rectangle&>(o));
|
||||
}
|
||||
struct RoundedRectangle : Rectangle
|
||||
{
|
||||
int32_t radius{};
|
||||
bool operator==(const RoundedRectangle& o) const
|
||||
{
|
||||
return std::tie(radius, static_cast<const Rectangle&>(*this)) ==
|
||||
std::tie(o.radius, static_cast<const Rectangle&>(o));
|
||||
}
|
||||
};
|
||||
|
||||
//define serialization functions
|
||||
// define serialization functions
|
||||
template<typename S>
|
||||
void serialize(S &s, Color &o) {
|
||||
//in real world scenario, it might be possible to serialize this using ValueRange, to map values in smaller space
|
||||
//but for the sake of this example keep it simple
|
||||
s.value4b(o.r);
|
||||
s.value4b(o.g);
|
||||
s.value4b(o.b);
|
||||
void
|
||||
serialize(S& s, Color& o)
|
||||
{
|
||||
// in real world scenario, it might be possible to serialize this using
|
||||
// ValueRange, to map values in smaller space but for the sake of this example
|
||||
// keep it simple
|
||||
s.value4b(o.r);
|
||||
s.value4b(o.g);
|
||||
s.value4b(o.b);
|
||||
}
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, Shape &o) {
|
||||
s.object(o.clr);
|
||||
void
|
||||
serialize(S& s, Shape& o)
|
||||
{
|
||||
s.object(o.clr);
|
||||
}
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, Circle &o) {
|
||||
s.ext(o, bitsery::ext::BaseClass<Shape>{});
|
||||
s.value4b(o.radius);
|
||||
void
|
||||
serialize(S& s, Circle& o)
|
||||
{
|
||||
s.ext(o, bitsery::ext::BaseClass<Shape>{});
|
||||
s.value4b(o.radius);
|
||||
}
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, Rectangle &o) {
|
||||
s.ext(o, bitsery::ext::BaseClass<Shape>{});
|
||||
s.value4b(o.width);
|
||||
s.value4b(o.height);
|
||||
void
|
||||
serialize(S& s, Rectangle& o)
|
||||
{
|
||||
s.ext(o, bitsery::ext::BaseClass<Shape>{});
|
||||
s.value4b(o.width);
|
||||
s.value4b(o.height);
|
||||
}
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, RoundedRectangle &o) {
|
||||
s.ext(o, bitsery::ext::BaseClass<Rectangle>{});
|
||||
s.value4b(o.radius);
|
||||
void
|
||||
serialize(S& s, RoundedRectangle& o)
|
||||
{
|
||||
s.ext(o, bitsery::ext::BaseClass<Rectangle>{});
|
||||
s.value4b(o.radius);
|
||||
}
|
||||
|
||||
//define our test structure
|
||||
struct SomeShapes {
|
||||
std::vector<std::shared_ptr<Shape>> sharedList;
|
||||
std::unique_ptr<Shape> uniquePtr;
|
||||
//weak ptr and refPtr will point to sharedList
|
||||
std::weak_ptr<Shape> weakPtr;
|
||||
Shape* refPtr;
|
||||
// define our test structure
|
||||
struct SomeShapes
|
||||
{
|
||||
std::vector<std::shared_ptr<Shape>> sharedList;
|
||||
std::unique_ptr<Shape> uniquePtr;
|
||||
// weak ptr and refPtr will point to sharedList
|
||||
std::weak_ptr<Shape> weakPtr;
|
||||
Shape* refPtr;
|
||||
};
|
||||
|
||||
//creates object, and populates some data
|
||||
SomeShapes createData() {
|
||||
SomeShapes data{};
|
||||
{
|
||||
auto tmp = new RoundedRectangle{};
|
||||
tmp->height = 151572;
|
||||
tmp->width = 488795;
|
||||
tmp->radius = 898;
|
||||
tmp->clr.r = 0.5f;
|
||||
tmp->clr.g = 1.0f;
|
||||
tmp->clr.b = 1.0f;
|
||||
data.uniquePtr.reset(tmp);
|
||||
}
|
||||
{
|
||||
auto tmp = new Circle{};
|
||||
tmp->radius = 75987;
|
||||
tmp->clr.r = 0.5f;
|
||||
tmp->clr.g = 0.0f;
|
||||
tmp->clr.b = 1.0f;
|
||||
data.sharedList.emplace_back(tmp);
|
||||
}
|
||||
{
|
||||
auto tmp = new Rectangle{};
|
||||
tmp->height = 15157;
|
||||
tmp->width = 48879;
|
||||
tmp->clr.r = 1.0f;
|
||||
tmp->clr.g = 0.0f;
|
||||
tmp->clr.b = 0.0f;
|
||||
data.sharedList.emplace_back(tmp);
|
||||
}
|
||||
data.weakPtr = data.sharedList[0];
|
||||
data.refPtr = data.sharedList[1].get();
|
||||
// creates object, and populates some data
|
||||
SomeShapes
|
||||
createData()
|
||||
{
|
||||
SomeShapes data{};
|
||||
{
|
||||
auto tmp = new RoundedRectangle{};
|
||||
tmp->height = 151572;
|
||||
tmp->width = 488795;
|
||||
tmp->radius = 898;
|
||||
tmp->clr.r = 0.5f;
|
||||
tmp->clr.g = 1.0f;
|
||||
tmp->clr.b = 1.0f;
|
||||
data.uniquePtr.reset(tmp);
|
||||
}
|
||||
{
|
||||
auto tmp = new Circle{};
|
||||
tmp->radius = 75987;
|
||||
tmp->clr.r = 0.5f;
|
||||
tmp->clr.g = 0.0f;
|
||||
tmp->clr.b = 1.0f;
|
||||
data.sharedList.emplace_back(tmp);
|
||||
}
|
||||
{
|
||||
auto tmp = new Rectangle{};
|
||||
tmp->height = 15157;
|
||||
tmp->width = 48879;
|
||||
tmp->clr.r = 1.0f;
|
||||
tmp->clr.g = 0.0f;
|
||||
tmp->clr.b = 0.0f;
|
||||
data.sharedList.emplace_back(tmp);
|
||||
}
|
||||
data.weakPtr = data.sharedList[0];
|
||||
data.refPtr = data.sharedList[1].get();
|
||||
|
||||
return data;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, SomeShapes &o) {
|
||||
s.ext(o.uniquePtr, StdSmartPtr{});
|
||||
// to make things more interesting first serialize weakPtr and refPtr,
|
||||
// even though objects that weakPtr and refPtr is serialized later,
|
||||
// bitsery will work regardless
|
||||
s.ext(o.weakPtr, StdSmartPtr{});
|
||||
s.ext(o.refPtr, PointerObserver{});
|
||||
s.container(o.sharedList, 100, [](S& s, std::shared_ptr<Shape> &item) {
|
||||
s.ext(item, StdSmartPtr{});
|
||||
});
|
||||
void
|
||||
serialize(S& s, SomeShapes& o)
|
||||
{
|
||||
s.ext(o.uniquePtr, StdSmartPtr{});
|
||||
// to make things more interesting first serialize weakPtr and refPtr,
|
||||
// even though objects that weakPtr and refPtr is serialized later,
|
||||
// bitsery will work regardless
|
||||
s.ext(o.weakPtr, StdSmartPtr{});
|
||||
s.ext(o.refPtr, PointerObserver{});
|
||||
s.container(o.sharedList, 100, [](S& s, std::shared_ptr<Shape>& item) {
|
||||
s.ext(item, StdSmartPtr{});
|
||||
});
|
||||
}
|
||||
|
||||
// STEP 1
|
||||
// define relationships between base and derived classes
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
namespace ext {
|
||||
|
||||
//for each base class define DIRECTLY derived classes
|
||||
//e.g. PolymorphicBaseClass<Shape> : PolymorphicDerivedClasses<Circle, Rectangle, RoundedRectangle>
|
||||
// is incorrect, because RoundedRectangle does not directly derive from Shape
|
||||
template<>
|
||||
struct PolymorphicBaseClass<Shape> : PolymorphicDerivedClasses<Circle, Rectangle> {
|
||||
};
|
||||
// for each base class define DIRECTLY derived classes
|
||||
// e.g. PolymorphicBaseClass<Shape> : PolymorphicDerivedClasses<Circle,
|
||||
// Rectangle, RoundedRectangle>
|
||||
// is incorrect, because RoundedRectangle does not directly derive from Shape
|
||||
template<>
|
||||
struct PolymorphicBaseClass<Shape>
|
||||
: PolymorphicDerivedClasses<Circle, Rectangle>
|
||||
{
|
||||
};
|
||||
|
||||
template<>
|
||||
struct PolymorphicBaseClass<Rectangle> : PolymorphicDerivedClasses<RoundedRectangle> {
|
||||
};
|
||||
}
|
||||
template<>
|
||||
struct PolymorphicBaseClass<Rectangle>
|
||||
: PolymorphicDerivedClasses<RoundedRectangle>
|
||||
{
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// convenient type that stores all our types, so that we could easily register and
|
||||
// also it automatically ensures, that classes is registered in the same order for serialization and deserialization
|
||||
using MyPolymorphicClassesForRegistering = bitsery::ext::PolymorphicClassesList<Shape>;
|
||||
// convenient type that stores all our types, so that we could easily register
|
||||
// and also it automatically ensures, that classes is registered in the same
|
||||
// order for serialization and deserialization
|
||||
using MyPolymorphicClassesForRegistering =
|
||||
bitsery::ext::PolymorphicClassesList<Shape>;
|
||||
|
||||
//some helper types
|
||||
// some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using Writer = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using Reader = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
//we need to define few things in order to work with polymorphism
|
||||
//1) we need pointer linking context to work with pointers
|
||||
//2) we need polymorphic context to be able to work with polymorphic types
|
||||
using TContext = std::tuple<
|
||||
bitsery::ext::PointerLinkingContext,
|
||||
bitsery::ext::PolymorphicContext<bitsery::ext::StandardRTTI>>;
|
||||
//NOTE:
|
||||
// RTTI can be customizable, if you can't use dynamic_cast and typeid, and have 'custom' solution
|
||||
// we need to define few things in order to work with polymorphism
|
||||
// 1) we need pointer linking context to work with pointers
|
||||
// 2) we need polymorphic context to be able to work with polymorphic types
|
||||
using TContext =
|
||||
std::tuple<bitsery::ext::PointerLinkingContext,
|
||||
bitsery::ext::PolymorphicContext<bitsery::ext::StandardRTTI>>;
|
||||
// NOTE:
|
||||
// RTTI can be customizable, if you can't use dynamic_cast and typeid, and have
|
||||
// 'custom' solution
|
||||
using MySerializer = bitsery::Serializer<Writer, TContext>;
|
||||
using MyDeserializer = bitsery::Deserializer<Reader, TContext>;
|
||||
|
||||
//checks if deserialized data is equal
|
||||
void assertSameShapes(const SomeShapes &data, const SomeShapes &res) {
|
||||
{
|
||||
auto d = dynamic_cast<RoundedRectangle *>(data.uniquePtr.get());
|
||||
auto r = dynamic_cast<RoundedRectangle *>(res.uniquePtr.get());
|
||||
assert(r != nullptr);
|
||||
assert(*d == *r);
|
||||
}
|
||||
{
|
||||
auto d = dynamic_cast<Circle *>(data.sharedList[0].get());
|
||||
auto r = dynamic_cast<Circle *>(res.sharedList[0].get());
|
||||
assert(r != nullptr);
|
||||
assert(*d == *r);
|
||||
}
|
||||
{
|
||||
auto d = dynamic_cast<Rectangle *>(data.sharedList[1].get());
|
||||
auto r = dynamic_cast<Rectangle *>(res.sharedList[1].get());
|
||||
assert(r != nullptr);
|
||||
assert(*d == *r);
|
||||
}
|
||||
assert(res.weakPtr.lock().get() == res.sharedList[0].get());
|
||||
assert(res.refPtr == res.sharedList[1].get());
|
||||
// checks if deserialized data is equal
|
||||
void
|
||||
assertSameShapes(const SomeShapes& data, const SomeShapes& res)
|
||||
{
|
||||
{
|
||||
auto d = dynamic_cast<RoundedRectangle*>(data.uniquePtr.get());
|
||||
auto r = dynamic_cast<RoundedRectangle*>(res.uniquePtr.get());
|
||||
assert(r != nullptr);
|
||||
assert(*d == *r);
|
||||
}
|
||||
{
|
||||
auto d = dynamic_cast<Circle*>(data.sharedList[0].get());
|
||||
auto r = dynamic_cast<Circle*>(res.sharedList[0].get());
|
||||
assert(r != nullptr);
|
||||
assert(*d == *r);
|
||||
}
|
||||
{
|
||||
auto d = dynamic_cast<Rectangle*>(data.sharedList[1].get());
|
||||
auto r = dynamic_cast<Rectangle*>(res.sharedList[1].get());
|
||||
assert(r != nullptr);
|
||||
assert(*d == *r);
|
||||
}
|
||||
assert(res.weakPtr.lock().get() == res.sharedList[0].get());
|
||||
assert(res.refPtr == res.sharedList[1].get());
|
||||
}
|
||||
|
||||
int main() {
|
||||
int
|
||||
main()
|
||||
{
|
||||
|
||||
auto data = createData();
|
||||
auto data = createData();
|
||||
|
||||
//create buffer to store data
|
||||
Buffer buffer{};
|
||||
size_t writtenSize{};
|
||||
// we will not use quickSerialization/Deserialization functions to show, that we need to register polymorphic classes, explicitly
|
||||
{
|
||||
// create buffer to store data
|
||||
Buffer buffer{};
|
||||
size_t writtenSize{};
|
||||
// we will not use quickSerialization/Deserialization functions to show, that
|
||||
// we need to register polymorphic classes, explicitly
|
||||
{
|
||||
|
||||
//STEP 2
|
||||
// before start serialization/deserialization,
|
||||
// bind it with base polymorphic types, it will go through all reachable classes that is defined in first step.
|
||||
// NOTE: you dont need to add Rectangle to reach for RoundedRectangle
|
||||
TContext ctx{};
|
||||
std::get<1>(ctx).registerBasesList<MySerializer>(MyPolymorphicClassesForRegistering{});
|
||||
//create writer and serialize
|
||||
MySerializer ser{ctx, buffer};
|
||||
ser.object(data);
|
||||
ser.adapter().flush();
|
||||
writtenSize = ser.adapter().writtenBytesCount();
|
||||
// STEP 2
|
||||
// before start serialization/deserialization,
|
||||
// bind it with base polymorphic types, it will go through all reachable
|
||||
// classes that is defined in first step. NOTE: you dont need to add
|
||||
// Rectangle to reach for RoundedRectangle
|
||||
TContext ctx{};
|
||||
std::get<1>(ctx).registerBasesList<MySerializer>(
|
||||
MyPolymorphicClassesForRegistering{});
|
||||
// create writer and serialize
|
||||
MySerializer ser{ ctx, buffer };
|
||||
ser.object(data);
|
||||
ser.adapter().flush();
|
||||
writtenSize = ser.adapter().writtenBytesCount();
|
||||
|
||||
//make sure that pointer linking context is valid
|
||||
//this ensures that all non-owning pointers points to data that has been serialized,
|
||||
//so we can successfully reconstruct pointers after deserialization
|
||||
assert(std::get<0>(ctx).isValid());
|
||||
}
|
||||
SomeShapes res{};
|
||||
{
|
||||
TContext ctx{};
|
||||
std::get<1>(ctx).registerBasesList<MyDeserializer>(MyPolymorphicClassesForRegistering{});
|
||||
//deserialize our data
|
||||
MyDeserializer des{ctx, buffer.begin(), writtenSize};
|
||||
des.object(res);
|
||||
assert(des.adapter().error() == bitsery::ReaderError::NoError && des.adapter().isCompletedSuccessfully());
|
||||
//also check for dangling pointers, after deserialization
|
||||
assert(std::get<0>(ctx).isValid());
|
||||
// clear shared state from pointer linking context,
|
||||
// it is only required if there are any pointers that manage shared state, e.g. std::shared_ptr
|
||||
assert(res.weakPtr.use_count() == 2);//one in sharedList and one in pointer linking context
|
||||
std::get<0>(ctx).clearSharedState();
|
||||
assert(res.weakPtr.use_count() == 1);
|
||||
}
|
||||
assertSameShapes(data, res);
|
||||
return 0;
|
||||
// make sure that pointer linking context is valid
|
||||
// this ensures that all non-owning pointers points to data that has been
|
||||
// serialized, so we can successfully reconstruct pointers after
|
||||
// deserialization
|
||||
assert(std::get<0>(ctx).isValid());
|
||||
}
|
||||
SomeShapes res{};
|
||||
{
|
||||
TContext ctx{};
|
||||
std::get<1>(ctx).registerBasesList<MyDeserializer>(
|
||||
MyPolymorphicClassesForRegistering{});
|
||||
// deserialize our data
|
||||
MyDeserializer des{ ctx, buffer.begin(), writtenSize };
|
||||
des.object(res);
|
||||
assert(des.adapter().error() == bitsery::ReaderError::NoError &&
|
||||
des.adapter().isCompletedSuccessfully());
|
||||
// also check for dangling pointers, after deserialization
|
||||
assert(std::get<0>(ctx).isValid());
|
||||
// clear shared state from pointer linking context,
|
||||
// it is only required if there are any pointers that manage shared state,
|
||||
// e.g. std::shared_ptr
|
||||
assert(res.weakPtr.use_count() ==
|
||||
2); // one in sharedList and one in pointer linking context
|
||||
std::get<0>(ctx).clearSharedState();
|
||||
assert(res.weakPtr.use_count() == 1);
|
||||
}
|
||||
assertSameShapes(data, res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user