VIP tutorial on Version extension

This commit is contained in:
Mindaugas Vinkelis
2020-02-02 20:03:21 +02:00
parent d24dfe14f5
commit 541632fa9e
17 changed files with 148 additions and 113 deletions

View File

@@ -1,8 +1,8 @@
The grand plan for this tutorial is to learn how to serialize/deserialize any object efficiently in time and space, so you could focus on other, more interesting things.
This tutorial will cover these main topics:
* `Hello World` write one control flow for both: serialization and deserialization.
* `Composer` efficiently compose complex serialization flows.
* [Getting started](hello_world.md) with bitsery, and serialize/deserialize your first object.
* [Extend to your needs](first_extension.md) by enabling serialization/deserialization depending on version number.
* `Squeeze Me!` compress your data when you know what it stores.
* `Anything is Possible` extend library for custom container, compress geometry and more.
* `Little or Big` change endianness if you want best performance on PowerPC.

View File

@@ -1,3 +0,0 @@
*document in progress*
* explain why *value* and *object* is fundamental functions.
* write about *Growable* extension

View File

@@ -0,0 +1,67 @@
... TODO explain step-by-step what we need and how to get there
Instead I immediately provide implementation for an extension.
```cpp
#include "../details/adapter_common.h"
#include "../traits/core/traits.h"
namespace bitsery {
namespace ext {
template<size_t VERSION>
class Version {
public:
template<typename Ser, typename T, typename Fnc>
void serialize(Ser &ser, const T &v, Fnc &&fnc) const {
details::writeSize(ser.adapter(), VERSION);
fnc(ser, const_cast<T&>(v), VERSION);
}
template<typename Des, typename T, typename Fnc>
void deserialize(Des &des, T &v, Fnc &&fnc) const {
size_t version{};
details::readSize(des.adapter(), version, 0u, std::false_type{});
fnc(des, v, version);
}
};
}
namespace traits {
template<typename T, size_t V>
struct ExtensionTraits<ext::Version<V>, T> {
using TValue = T;
static constexpr bool SupportValueOverload = false;
static constexpr bool SupportObjectOverload = false;
static constexpr bool SupportLambdaOverload = true;
};
}
}
```
Adding such extension to the bitsery itself is impractical because it is very easy to implement, but at the same time it has a lot of customization options that actual user might require e.g.:
* how do you want to handle reading/writing version number? (in this case use compact representation as size, but do not check for errors if version number is too large)
* maybe you want to be able to set ReaderError, when version is larger than deserialization implementation handles. (we simply ignore this case and later we'll probably get reading error later anyway)
* or maybe you want to wrap object in `Growable` extension? so that you could ignore unknown fields in newer version of object.
Example of how to use this provided implementation:
```cpp
struct TypeV2 {
uint16_t x{};
uint16_t y{};
};
template <typename S>
void serialize(S& ser, TypeV2& obj) {
ser.ext(obj, bitsery::ext::Version<2u>{}, [](S& s, TypeV2&o, size_t version) {
s.value2b(o.x);
if (version == 2u) {
s.value2b(o.y);
}
});
}
```

View File

@@ -18,11 +18,9 @@ There's nothing to build or make - **bitsery** is header only.
#include <bitsery/traits/vector.h>
#include <bitsery/traits/string.h>
using namespace bitsery;
using Buffer = std::vector<uint8_t>;
using OutputAdapter = OutputBufferAdapter<Buffer>;
using InputAdapter = InputBufferAdapter<Buffer>;
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
```
@@ -62,7 +60,7 @@ void serialize(S& s, MyStruct& o) {
This example we choosed probably unfamiliar verbose syntax, so lets explain core functionality that you'll use all the time:
* **s.value4b(o.i);** serialize fundamental types (ints, floats, enums) value**4b** means, that data type is 4 bytes. If you use same code on different machines, if it compiles it means it is compatible.
* **s.text1b(o.str);** serialize text (null-terminated) of char type, if you use *wchar* then you would write *text2b*.
* **s.text1b(o.str);** serialize text (null-terminated) of char type, if you use *wchar* then you would write *text2b* or *text4b* depending on the OS platform.
* **s.container4b(o.fs, 100);** serializes any container of fundamental types of size 4bytes, **100** is max size of container.
**Bitsery** is designed to be save with untrusted (malicious) data from network, so for dynamic containers you always need to provide max possible size available, to avoid buffer-overflow attacks.
**text** didn't had this max size specified, because it was serializing fixed size container.
@@ -76,8 +74,8 @@ Create buffer and use helper functions for serialization and deserialization.
```cpp
Buffer buffer;
auto writtenSize = quickSerialization(OutputAdapter{buffer}, data);
auto state = quickDeserialization(InputAdapter{buffer.begin(), writtenSize}, res);
auto writtenSize = bitsery::quickSerialization(OutputAdapter{buffer}, data);
auto state = bitsery::quickDeserialization(InputAdapter{buffer.begin(), writtenSize}, res);
```
These helper functions use default configuration *bitsery::DefaultConfig*
@@ -93,11 +91,9 @@ deserialization state has two properties, error code and bool that indicates if
#include <bitsery/traits/vector.h>
#include <bitsery/traits/string.h>
using namespace bitsery;
using Buffer = std::vector<uint8_t>;
using OutputAdapter = OutputBufferAdapter<Buffer>;
using InputAdapter = InputBufferAdapter<Buffer>;
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
struct MyStruct {
uint32_t i;
@@ -110,17 +106,17 @@ void serialize(S& s, MyStruct& o) {
s.value4b(o.i);
s.text1b(o.str);
s.container4b(o.fs, 100);
};
}
int main() {
MyStruct data{8941, "hello", {15.0f, -8.5f, 0.045f}};
MyStruct res{};
Buffer buffer;
auto writtenSize = quickSerialization(OutputAdapter{buffer}, data);
auto state = quickDeserialization(InputAdapter{buffer.begin(), writtenSize}, res);
auto writtenSize = bitsery::quickSerialization(OutputAdapter{buffer}, data);
auto state = bitsery::quickDeserialization(InputAdapter{buffer.begin(), writtenSize}, res);
assert(state.first == ReaderError::NoError && state.second);
assert(state.first == bitsery::ReaderError::NoError && state.second);
assert(data.fs == res.fs && data.i == res.i && std::strcmp(data.str, res.str) == 0);
}
```