format code base in Mozilla style

This commit is contained in:
Mindaugas Vinkelis
2022-12-01 13:50:03 +02:00
parent d690908541
commit 3e02d0ca44
106 changed files with 15083 additions and 12518 deletions

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}