multiple breaking change improvements

This commit is contained in:
Mindaugas Vinkelis
2019-06-25 11:08:26 +03:00
committed by Mindaugas Vinkelis
parent 57dd028b7a
commit 1822796f2e
65 changed files with 1993 additions and 2242 deletions

View File

@@ -31,7 +31,7 @@ namespace MyTypes {
//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, [&sbp](Vec3& vec3) {
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);

View File

@@ -58,7 +58,7 @@ void serialize(S& s, MyVariant& o) {
// 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](int32_t& v) {
s.container(o.v, 1000, [](S& s, int32_t& v) {
s.ext4b(v, ext::CompactValue{});
});
},

View File

@@ -34,15 +34,15 @@ namespace MyTypes {
template<typename S>
void serialize(S& s, GameState &o) {
//we can have multiple types in context with std::tuple
//this cast also works if our context is the same as cast
auto maxMonsters = s.template context<int>();
//all data from context is always pointer
//if data type doesn't match then it will be compile time error
auto dmgRange = s.template context<std::pair<uint32_t, uint32_t>>();
s.container(o.monsters, *maxMonsters, [&s, dmgRange] (Monster& m) {
//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};
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);
@@ -53,26 +53,24 @@ namespace MyTypes {
}
using namespace bitsery;
//use fixed-size buffer
using Buffer = std::vector<uint8_t>;
using OutputAdapter = OutputBufferAdapter<Buffer>;
using InputAdapter = InputBufferAdapter<Buffer>;
//context can contain multiple types
//it would make more sense to define separate structure for context, but for sake of this example make it more complex
//in serialization function we can cast it like this:
//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>();
//if we want to get whole tuple, just call s.context() without template paramter.
//this templated version also works if our context is the same as cast:
// struct MyContext {...};
// ...
// s.template context<MyContext>();
using Context = std::tuple<int, std::pair<uint32_t, uint32_t>>;
//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
using Buffer = std::vector<uint8_t>;
using namespace bitsery;
// define Writer and Reader types,
using Writer = AdapterWriter<OutputBufferAdapter<Buffer>, DefaultConfig, Context>;
using Reader = AdapterReader<InputBufferAdapter<Buffer>, DefaultConfig, Context>;
int main() {
@@ -92,18 +90,18 @@ int main() {
//create buffer to store data to
Buffer buffer{};
//pass game mode object to serializer as context
BasicSerializer<AdapterWriter<OutputAdapter, bitsery::DefaultConfig>, Context> ser{buffer, &ctx};
//create adapter writer with context
//context is passed by reference without taking ownership
Writer writer{buffer, ctx};
//serialize data
BasicSerializer<Writer> ser{writer};
ser.object(data);
auto& w = AdapterAccess::getWriter(ser);
w.flush();
auto writtenSize = w.writtenBytesCount();
writer.flush();
MyTypes::GameState res{};
BasicDeserializer <AdapterReader<InputAdapter, bitsery::DefaultConfig>, Context> des { InputAdapter{buffer.begin(), writtenSize}, &ctx};
Reader reader {{buffer.begin(), writer.writtenBytesCount()}, ctx};
BasicDeserializer<Reader> des {reader };
des.object(res);
auto& r = AdapterAccess::getReader(des);
assert(r.error() == ReaderError::NoError && r.isCompletedSuccessfully());
assert(reader.error() == ReaderError::NoError && reader.isCompletedSuccessfully());
}

View File

@@ -21,8 +21,8 @@ void serialize(S& s, MyStruct& o) {
using namespace bitsery;
//some helper types
using Stream = std::fstream;
//buffered stream adapter allows for faster writes
using Writer = AdapterWriter<OutputBufferedStreamAdapter, DefaultConfig>;
int main() {
//set some random data
@@ -31,17 +31,18 @@ int main() {
//open file stream for writing and reading
auto fileName = "test_file.bin";
Stream s{fileName, s.binary | s.trunc | s.out};
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
//for serialization we can use buffered stream adapter, it can greatly improve performance for some streams
Serializer<OutputBufferedStreamAdapter> ser{s};
Writer writer{s};
BasicSerializer<Writer> ser{writer};
ser.object(data);
//flush to writer
AdapterAccess::getWriter(ser).flush();
writer.flush();
s.close();
//reopen for reading

View File

@@ -25,7 +25,7 @@ namespace MyTypes {
template <typename S>
void serialize (S& s) {
//forward/backward compatibility for monsters
s.ext(*this, bitsery::ext::Growable{}, [&s](Weapon& o1) {
s.ext(*this, bitsery::ext::Growable{}, [](S& s, Weapon& o1) {
s.text1b(o1.name, 20);
s.value2b(o1.damage);
});
@@ -54,7 +54,7 @@ namespace MyTypes {
template <typename S>
void serialize (S& s, Monster& o) {
//forward/backward compatibility for monsters
s.ext(o, bitsery::ext::Growable{}, [&s](Monster& o1) {
s.ext(o, bitsery::ext::Growable{}, [](S& s, Monster& o1) {
s.value1b(o1.color);
s.value2b(o1.mana);
s.value2b(o1.hp);
@@ -75,11 +75,6 @@ using Buffer = std::array<uint8_t, 10000>;
using OutputAdapter = OutputBufferAdapter<Buffer>;
using InputAdapter = InputBufferAdapter<Buffer>;
//create configuration that enables session support, to work with "growable" extension
struct SessionsEnabled:public DefaultConfig {
static constexpr bool BufferSessionsEnabled = true;
};
int main() {
//set some random data
MyTypes::Monster data{};
@@ -89,17 +84,11 @@ int main() {
//create buffer to store data to
Buffer buffer{};
//since we're using different configuration, we cannot use quickSerialization function.
BasicSerializer<AdapterWriter<OutputAdapter, SessionsEnabled>> ser{OutputAdapter{buffer}};
ser.object(data);
auto& w = AdapterAccess::getWriter(ser);
w.flush();
auto writtenSize = w.writtenBytesCount();
auto writtenSize = quickSerialization<OutputAdapter>(buffer, data);
MyTypes::Monster res{};
//deserialize
BasicDeserializer<AdapterReader<InputAdapter, SessionsEnabled>> des{InputAdapter{buffer.begin(), writtenSize}};
des.object(res);
auto& r = AdapterAccess::getReader(des);
assert(r.error() == ReaderError::NoError && r.isCompletedSuccessfully());
auto state = quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
assert(state.first == ReaderError::NoError && state.second);
}

View File

@@ -12,7 +12,6 @@
// 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
//it can be created either internally (via configuration) or externally (pointer to context).
#include <bitsery/ext/inheritance.h>
using bitsery::ext::BaseClass;
@@ -32,7 +31,7 @@ struct Derive1:virtual Base {// virtually inherits from base
};
template <typename S>
void serialize(S& s, Derive1& o) {
//define virtual inheritance, it will not compile if InheritanceContext is not defined in serializer/deserialzer
//define virtual inheritance, it will not compile if InheritanceContext is not defined in serializer/deserializer
s.ext(o, VirtualBaseClass<Base>{});
s.value1b(o.y1);
}
@@ -81,18 +80,11 @@ namespace bitsery {
using namespace bitsery;
// since in this example we're using virtual inheritance we need InheritanceContext as well.
// InheritanceContext is default constructable and has no useful information outside of serializer/deserializer
// lets create it internally, via configuration
struct ConfigWithContext:DefaultConfig {
//always add internal contexts to tuple
using InternalContext = std::tuple<ext::InheritanceContext>;
};
//some helper types
using Buffer = std::vector<uint8_t>;
using Writer = AdapterWriter<OutputBufferAdapter<Buffer>, ConfigWithContext>;
using Reader = AdapterReader<InputBufferAdapter<Buffer>, ConfigWithContext>;
using Writer = AdapterWriter<OutputBufferAdapter<Buffer>, DefaultConfig, ext::InheritanceContext>;
using Reader = AdapterReader<InputBufferAdapter<Buffer>, DefaultConfig, ext::InheritanceContext>;
int main() {
@@ -103,15 +95,20 @@ int main() {
Buffer buf{};
BasicSerializer<Writer> ser{OutputBufferAdapter<Buffer>{buf}, nullptr};
ext::InheritanceContext ctx1;
Writer writer{buf, ctx1};
BasicSerializer<Writer> ser{writer};
ser.object(data);
auto writtenSize = AdapterAccess::getWriter(ser).writtenBytesCount();
writer.flush();
MultipleInheritance res{0};
BasicDeserializer<Reader> des{InputBufferAdapter<Buffer>{buf.begin(), writtenSize}};
ext::InheritanceContext ctx2;
Reader reader{{buf.begin(), writer.writtenBytesCount()}, ctx2};
BasicDeserializer<Reader> des{reader};
des.object(res);
assert(AdapterAccess::getReader(des).error() == ReaderError::NoError && AdapterAccess::getReader(des).isCompletedSuccessfully());
assert(reader.error() == ReaderError::NoError && reader.isCompletedSuccessfully());
assert(data.x == res.x && data.y1 == res.y1 && data.getY2() == res.getY2() && data.z == res.z);
assert(writtenSize == 4);//base is serialized once, because it is inherited virtually
assert(writer.writtenBytesCount() == 4);//base is serialized once, because it is inherited virtually
}

View File

@@ -34,8 +34,8 @@ using namespace bitsery;
//some helper types
using Buffer = std::vector<uint8_t>;
using OutputAdapter = OutputBufferAdapter<Buffer>;
using InputAdapter = InputBufferAdapter<Buffer>;
using Writer = AdapterWriter<OutputBufferAdapter<Buffer>, DefaultConfig>;
using Reader = AdapterReader<InputBufferAdapter<Buffer>, DefaultConfig>;
int main() {
@@ -48,17 +48,18 @@ int main() {
//create buffer
Buffer buffer{};
//create and serialize container, and get written bytes count
Serializer<OutputAdapter> ser{OutputAdapter{buffer}};
//create writer and serialize container
Writer writer{buffer};
BasicSerializer<Writer> ser{writer};
ser.container(data, 10);
auto writtenBytes = AdapterAccess::getWriter(ser).writtenBytesCount();
writer.flush();
//create and deserialize container
Deserializer<InputAdapter> des{InputAdapter{buffer.begin(), writtenBytes}};
//create reader and deserialize container
Reader reader{{buffer.begin(), writer.writtenBytesCount()}};
BasicDeserializer<Reader> des{reader};
des.container(res, 10);
//check if everything went ok
auto& reader = AdapterAccess::getReader(des);
assert(reader.error() == ReaderError::NoError && reader.isCompletedSuccessfully());
assert(res == data);
}

View File

@@ -56,7 +56,7 @@ private:
s.value4b(i1);
//set container elements to be candidates for non-owning pointers
s.container(vdata, 100, [&s](MyStruct& d){
s.container(vdata, 100, [](S& s, MyStruct& d){
s.ext(d, ReferencedByPointer{});
});
//contains non owning pointers
@@ -67,7 +67,7 @@ private:
//
//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](MyStruct* (&d)){
s.container(vptr, 100, [](S& s, MyStruct* (&d)){
s.ext(d, PointerObserver{});
});
//observer
@@ -86,13 +86,12 @@ using OutputAdapter = OutputBufferAdapter<Buffer>;
using InputAdapter = InputBufferAdapter<Buffer>;
//we will need PointerLinkingContext to work with pointers
//so lets define our serializer/deserializer
//if we need context for our own custom flow, we can define it as tuple like this:
//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
using MySerializer = BasicSerializer<AdapterWriter<OutputAdapter, DefaultConfig>, ext::PointerLinkingContext>;
using MyDeserializer = BasicDeserializer<AdapterReader<InputAdapter, DefaultConfig>, ext::PointerLinkingContext>;
using Writer = AdapterWriter<OutputBufferAdapter<Buffer>, DefaultConfig, ext::PointerLinkingContext>;
using Reader = AdapterReader<InputBufferAdapter<Buffer>, DefaultConfig, ext::PointerLinkingContext>;
int main() {
//set some random data
@@ -115,16 +114,15 @@ int main() {
//create buffer to store data
Buffer buffer{};
size_t writtenSize{};
//in order to use pointers, we need to pass pointer linking context to serializer/deserializer
//in order to use pointers, we need to pass pointer linking context to writer/reader
{
ext::PointerLinkingContext ctx{};
//pass lining context to serializer, by pointer
MySerializer ser{OutputAdapter{buffer}, &ctx};
Writer writer{buffer, ctx};
BasicSerializer<Writer> ser{writer};
//serialize our data
ser.object(data);
auto& w = AdapterAccess::getWriter(ser);
w.flush();
writtenSize = w.writtenBytesCount();
writer.flush();
writtenSize = writer.writtenBytesCount();
//make sure that pointer linking context is valid
//this ensures that all non-owning pointers points to data that has been serialized,
@@ -135,13 +133,13 @@ int main() {
Test1Data res{};
{
ext::PointerLinkingContext ctx{};
//pass lining context to deserializer, by pointer
MyDeserializer des{InputAdapter{buffer.begin(), writtenSize}, &ctx};
//pass lining context to reader
Reader reader{{buffer.begin(), writtenSize}, ctx};
BasicDeserializer<Reader> des{reader};
//deserialize our data
des.object(res);
auto& r = AdapterAccess::getReader(des);
//check if everything went find
assert(r.error() == ReaderError::NoError && r.isCompletedSuccessfully());
assert(reader.error() == ReaderError::NoError && reader.isCompletedSuccessfully());
//also check for dangling pointers, after deserialization
assert(ctx.isValid());
}

View File

@@ -153,7 +153,7 @@ void serialize(S &s, SomeShapes &o) {
// bitsery will work regardless
s.ext(o.weakPtr, StdSmartPtr{});
s.ext(o.refPtr, PointerObserver{});
s.container(o.sharedList, 100, [&s](std::shared_ptr<Shape> &item) {
s.container(o.sharedList, 100, [](S& s, std::shared_ptr<Shape> &item) {
s.ext(item, StdSmartPtr{});
});
}
@@ -197,8 +197,8 @@ using TContext = std::tuple<ext::PointerLinkingContext, ext::PolymorphicContext<
//NOTE:
// RTTI can be customizable, if you can't use dynamic_cast and typeid, and have 'custom' solution
using MySerializer = BasicSerializer<AdapterWriter<OutputAdapter, DefaultConfig>, TContext>;
using MyDeserializer = BasicDeserializer<AdapterReader<InputAdapter, DefaultConfig>, TContext>;
using Writer = AdapterWriter<OutputBufferAdapter<Buffer>, DefaultConfig, TContext>;
using Reader = AdapterReader<InputBufferAdapter<Buffer>, DefaultConfig, TContext>;
//checks if deserialized data is equal
@@ -233,17 +233,19 @@ int main() {
Buffer buffer{};
size_t writtenSize{};
{
//STEP 2
//bind serializer with base polymorphic types, it will go through all reachable classes that is defined in first step.
//so you dont need to add Rectangle to reach for RoundedRectangle
// 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{});
//serialize our data
MySerializer ser{OutputAdapter{buffer}, &ctx};
std::get<1>(ctx).registerBasesList<BasicSerializer<Writer>>(MyPolymorphicClassesForRegistering{});
//create writer and serialize
Writer writer{buffer, ctx};
BasicSerializer<Writer> ser{writer};
ser.object(data);
auto &w = AdapterAccess::getWriter(ser);
w.flush();
writtenSize = w.writtenBytesCount();
writer.flush();
writtenSize = writer.writtenBytesCount();
//make sure that pointer linking context is valid
//this ensures that all non-owning pointers points to data that has been serialized,
@@ -253,13 +255,13 @@ int main() {
SomeShapes res{};
{
TContext ctx{};
std::get<1>(ctx).registerBasesList<MyDeserializer>(MyPolymorphicClassesForRegistering{});
std::get<1>(ctx).registerBasesList<BasicDeserializer<Reader>>(MyPolymorphicClassesForRegistering{});
//serialize our data
MyDeserializer des{InputAdapter{buffer.begin(), writtenSize}, &ctx};
Reader reader {{buffer.begin(), writtenSize}, ctx};
BasicDeserializer<Reader> des{reader};
des.object(res);
auto &r = AdapterAccess::getReader(des);
//check if everything went find
assert(r.error() == ReaderError::NoError && r.isCompletedSuccessfully());
assert(reader.error() == ReaderError::NoError && reader.isCompletedSuccessfully());
//also check for dangling pointers, after deserialization
assert(std::get<0>(ctx).isValid());
// clear shared state from pointer linking context,