turorial 2in1

This commit is contained in:
Mindaugas Vinkelis
2017-08-21 20:49:34 +03:00
committed by fraillt
parent a1104a9b95
commit 79ee168893
8 changed files with 190 additions and 58 deletions

View File

@@ -15,7 +15,7 @@ Serializer/Deserializer functions (alphabetical order):
* [container](fnc_container.md)
* [custom](fnc_custom.md)
* [extension](fnc_extension.md)
* [isValid](fnc_isValid.md)
* [isValid](reference/fnc_is_valid.md)
* [object](fnc_object.md)
* [range](fnc_range.md)
* [substitution](fnc_substitution.md)

View File

View File

View File

@@ -4,6 +4,7 @@ The grand plan for this tutorial is to learn how to serialize/deserialize any ob
This tutorial will cover these main topics:
* [Hello World](hello_world.md) how to serialize a simple struct.
* [2 in 1](two_in_one.md) how to write one control flow for both, serialization and deserialization.
* [Composer](composition.md) how to make your type serializable by default or override default flow.
* [Squeeze Me!](compression.md) how to compress your data if you know what it stores.
* [Anything is Possible](extensions.md) how to extend library for your custom container, compress geometry and more.
* [Little or Big](endianness.md) how to change Endianness if you want best performance on PowerPC.

View File

View File

@@ -1,4 +1,4 @@
# The Problem
# The problem
You want to serialize *Player* structure efficiently into buffer.
@@ -65,7 +65,7 @@ Although it is simple and fast (it could be faster if we reserve buffer before w
You can improve your name serialization in various ways, but then your serialization and deserialization code gets compllicated and error prone. We can do better than this.
# **Bitsery** solution
# Bitsery solution
Let's solve the same problem with the library.
```cpp
@@ -117,10 +117,10 @@ size: 17
First of all, buffer size dropped from 64 down to 17bytes: 12 bytes (3*4) for floats and only 5bytes for the name "Yolo".
In the process you also lost all limitations that had naive solution. You even gain some features for free:
* endianess support.
* much more readable structure serialization code.
* more readable serialization code.
Let's take a look at the code, how we did this.
Let's look at the code, how we did this.
There are three distinct parts that participate in serialization process.

View File

@@ -0,0 +1,139 @@
# The Problem
Deserialization process is the same to serialization in a sense, that all serialization/deserialization operations is in the same order, except that instead of writing to buffer you read from it, so it is very desirable to have the same code express both functionality, but is it really possible? Let's find out!
To achieve this *Deserializer* has exactly the same interface as *Serializer*, EXCEPT that all methods in *Deserializer* accept data as *T&*, but *Serializer* accepts as *const T&*.
So one way to make this happen is to have *Serializer/Deserializer* as template parameter, and actual object accept as *T&* like this.
```cpp
template <typename S>
void serialize(S& s, Player& o) {
s.value4(o.pos.x);
s.value4(o.pos.y);
s.value4(o.pos.z);
s.text1(o.name);
}
```
You can use this function for serialization and deserialization, but you can`t pass *const T&*, which is huge limitation.
# Bitsery solution
In order to fix this *const T&* issue, all we need to do is use [SFINAE](http://en.cppreference.com/w/cpp/language/sfinae) technique to enable this function if T is *Object* or *const Object*, like this:
```cpp
template <typename S, typename T, typename std::enable_if<std::is_same<T, Player>::value || std::is_same<T, const Player>::value>::type* = nullptr>
void serialize (S& s, T& o) {
...
}
```
Let's modify our [hello world](hello_world.md) example and add deserialization to it.
```cpp
#include <vector>
#include <bitsery/bitsery.h>
#include <cstring>
#include <iostream>
struct Vector3f {
float x;
float y;
float z;
bool operator == (const Vector3f& o) const {
return x == o.x && y == o.y && z == o.z;
}
};
struct Player {
Vector3f pos;
char name[50];
};
using namespace bitsery;
SERIALIZE(Player) {
s.value4(o.pos.x);
s.value4(o.pos.y);
s.value4(o.pos.z);
s.text1(o.name);
}
Player createData() {
Player data;
data.pos.x = 0.45f;
data.pos.y = 50.9f;
data.pos.z = -15687.87f;
strcpy(data.name,"Yolo");
return data;
}
int main() {
const Player data = createData();
Player res{};
std::vector<uint8_t> buf;
BufferWriter bw{buf};
Serializer<BufferWriter> ser{bw};
serialize(ser, data);
bw.flush();
BufferReader br{buf};
Deserializer<BufferReader> des{br};
serialize(des, res);
std::cout << "deserializer state: " << des.isValid() << std::endl
<< "buffer completed: " << br.isCompleted() << std::endl
<< "pos equals: " << (res.pos == data.pos) << std::endl
<< "name equals: " << (strcmp(res.name, data.name) == 0);
return 0;
}
```
```bash
deserializer state: 1
buffer completed: 1
pos equals: 1
name equals: 1
```
We created *Deserializer* and modified *serialize* function to accept *Serializer* and *Deserializer*.
Deserialization is very similar as serialization, it also consists of three separate components:
* Buffer - container that we read data from, in our case vector<uint8_t>.
* BufferReader - reads bytes and bits from *Buffer*, it also makes sure that it is portable across Little and Big endian systems.
* Deserializer - same interface as *Serializer* that use *BufferReader* to read bits and bytes, and convert to specific type. Deserializer also checks for errors at runtime, because data might come from untrusted source and can terminate program with buffer-overflow or segmentation fault if we are not careful.
Since deserialization involves error checking there are two additional functions to check if everything is correct after deserialization.
* [BufferReader.isCompleted()](../reference/buf_is_completed.md) - returns true, if whole buffer was read during deserialization.
* [Deserializer.isValid()](../reference/fnc_is_valid.md) - returns true, if there was no errors during deserialization.
```cpp
BufferReader br{buf};
Deserializer<BufferReader> des{br};
```
To reduce code for *serialize* function using *SFINAE* technique, **bitsery** has macro *SERIALIZE*. Using this macro code looks much cleaner, and now this function can accept both *Player* and *const Player*.
```cpp
SERIALIZE(Player) {
s.value4(o.pos.x);
s.value4(o.pos.y);
s.value4(o.pos.z);
s.text1(o.name);
}
...
serialize(ser, data); //ser-> Serializer, data-> const Player
...
serialize(des, res); //des-> Deserializer, data-> Player
```
# Summary
You have learned how to write *serialize* function for your type, that works with serialization and deserialization. You also learned that deserialization is very similar to serialization, but has runtime error checking.
In [next chapter](composition.md) you'll learn more serialization/deserialization functions and how to compose them efficiently to add default or custom serialization/deserialization behaviour.

View File

@@ -1,70 +1,62 @@
//MIT License
//
//Copyright (c) 2017 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#include <vector>
#include <bitsery/bitsery.h>
#include <cstring>
#include <iostream>
enum class MyEnum:uint16_t { V1,V2,V3 };
struct MyStruct {
uint32_t i;
MyEnum e;
std::vector<float> fs;
struct Vector3f {
float x;
float y;
float z;
bool operator == (const Vector3f& o) const {
return x == o.x && y == o.y && z == o.z;
}
};
//define how object should be serialized/deserialized
SERIALIZE(MyStruct) {
return s.
value4(o.i).
value2(o.e).
container4(o.fs, 10);
struct Player {
Vector3f pos;
char name[50];
};
using namespace bitsery;
int main() {
//set some random data
MyStruct data{8941, MyEnum::V2, {15.0f, -8.5f, 0.045f}};
MyStruct res{};
SERIALIZE(Player) {
s.value4(o.pos.x);
s.value4(o.pos.y);
s.value4(o.pos.z);
s.text1(o.name);
return s;
}
//create serializer
//1) create buffer to store data
std::vector<uint8_t> buffer;
//2) create buffer writer that is able to write bytes or bits to buffer
BufferWriter bw{buffer};
//3) create serializer
Player createData() {
Player data;
data.pos.x = 0.45f;
data.pos.y = 50.9f;
data.pos.z = -15687.87f;
strcpy(data.name,"Yolo");
return data;
}
int main() {
const Player data = createData();
Player res{};
std::vector<uint8_t> buf;
BufferWriter bw{buf};
Serializer<BufferWriter> ser{bw};
//serialize object, can also be invoked like this: serialize(ser, data)
ser.object(data);
serialize(ser, data);
//flush to buffer, before creating buffer reader
bw.flush();
//create deserializer
//1) create buffer reader
BufferReader br{buffer};
//2) create deserializer
BufferReader br{buf};
Deserializer<BufferReader> des{br};
//deserialize same object, can also be invoked like this: serialize(des, data)
des.object(res);
assert(data.fs == res.fs && data.i == res.i && data.e == res.e);
serialize(des, res);
std::cout << "deserializer state: " << des.isValid() << std::endl
<< "buffer completed: " << br.isCompleted() << std::endl
<< "pos equals: " << (res.pos == data.pos) << std::endl
<< "name equals: " << (strcmp(res.name, data.name) == 0);
return 0;
}