mirror of
https://github.com/fraillt/bitsery.git
synced 2026-06-08 08:13:56 +00:00
hello world tutorial complete
This commit is contained in:
@@ -18,6 +18,12 @@ All cross-platform requirements are enforced at compile time, so serialized data
|
||||
* Easy to extend for types that requires different serialization and deserialization logic (e.g. pointers, or geometry compression).
|
||||
* Error checking at runtime on deserialization, and asserts on serialization errors.
|
||||
|
||||
## How to use it
|
||||
This documentation comprises these parts:
|
||||
* [Tutorial](doc/tutorial/README.md) - getting started.
|
||||
* [Reference section](doc/README.md) - all the details.
|
||||
|
||||
|
||||
## Example
|
||||
```cpp
|
||||
#include <bitsery/bitsery.h>
|
||||
|
||||
@@ -2,9 +2,11 @@ To get the most out of **Bitsery**, start with the [tutorial](tutorial/README.md
|
||||
Once you're familiar with the library consider the following reference material.
|
||||
|
||||
Library design:
|
||||
* [*valueN* instead of *value*](design/func_n.md)
|
||||
* [fundamental types](design/fundamental_types.md)
|
||||
* [serializer/deserializer functions overloads](design/function_overload.md)
|
||||
* [extending library functionality](design/extensions.md)
|
||||
* [errors handling](design/errors.md)
|
||||
|
||||
Serializer/Deserializer functions (alphabetical order):
|
||||
* [align](fnc_array.md)
|
||||
@@ -35,4 +37,4 @@ FAQ:
|
||||
Other:
|
||||
* [Why Bitsery?](why-bitsery.md)
|
||||
* [Contributing](../CONTRIBUTING.md)
|
||||
* [Change log](../CHANGELOG.md)
|
||||
* [Change log](../CHANGELOG.md)
|
||||
|
||||
1
doc/design/errors.md
Normal file
1
doc/design/errors.md
Normal file
@@ -0,0 +1 @@
|
||||
errors handling design
|
||||
1
doc/design/function_n.md
Normal file
1
doc/design/function_n.md
Normal file
@@ -0,0 +1 @@
|
||||
valueN instead of value
|
||||
@@ -2,8 +2,13 @@ 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 set up and to serialize a [fundamental value](../design/fundamental.md).
|
||||
* [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.
|
||||
* [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
|
||||
* [Little or Big](endianness.md) how to change Endianness if you want best performance on PowerPC.
|
||||
|
||||
In order to successfully use the library you need c++14 compatible compiler. In theory you could also use c++11 compatible compiler, but c++14 generic lambdas really change the way you can work with this library, so all tutorial sections will asume that you use c++14 compatible compiler.
|
||||
|
||||
So without further ado lets start with [hello world](hello_world.md).
|
||||
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
# The Problem
|
||||
|
||||
You want to serialize *Player* structure efficiently into buffer.
|
||||
|
||||
```cpp
|
||||
struct Vector3f {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
};
|
||||
struct Player {
|
||||
Vector3f pos;
|
||||
char name[50];
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
# Poor man's implementation
|
||||
|
||||
Since you don't want to waste any space using any text serialization library like json or xml, the one of the most easiest and obvious solution is to simply write memory representation of the structure directly to buffer.
|
||||
|
||||
|
||||
Possible implementation could look like this.
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
|
||||
struct Vector3f {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
};
|
||||
struct Player {
|
||||
Vector3f pos;
|
||||
char name[50];
|
||||
};
|
||||
|
||||
void serialize(std::vector<uint8_t>& buf, const Player& data) {
|
||||
auto ptr = reinterpret_cast<const uint8_t*>(&data);
|
||||
std::copy_n(ptr, sizeof(data), std::back_inserter(buf));
|
||||
}
|
||||
|
||||
int main() {
|
||||
Player data;
|
||||
std::vector<uint8_t> buf;
|
||||
serialize(buf, data);
|
||||
assert(buf.size() == sizeof(data));
|
||||
std::cout << "size: " << buf.size() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
size: 64
|
||||
```
|
||||
|
||||
Although it is simple and fast (it could be faster if we reserve buffer before writing) it has a lot of limitations.
|
||||
* char[50] always writes to atleast 50 bytes in buffer, even if player name is *Yolo*.
|
||||
* you can't replace char[50] with std::string.
|
||||
* you can't use this solution if you need to support different endianness systems, and you should be extra careful if different systems has different size for fundamental types like int, long int, etc...
|
||||
* you pay for structure field alignment hence size is equal to 64, not 62(3*4+50).
|
||||
|
||||
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
|
||||
|
||||
Let's solve the same problem with the library.
|
||||
```cpp
|
||||
#include <vector>
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
struct Vector3f {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
};
|
||||
|
||||
struct Player {
|
||||
Vector3f pos;
|
||||
char name[50];
|
||||
};
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
void serialize(Serializer<BufferWriter>& s, const Player& data) {
|
||||
s.value4(data.pos.x);
|
||||
s.value4(data.pos.y);
|
||||
s.value4(data.pos.z);
|
||||
s.text1(data.name);
|
||||
}
|
||||
|
||||
int main() {
|
||||
Player data;
|
||||
strcpy(data.name,"Yolo");
|
||||
|
||||
std::vector<uint8_t> buf;
|
||||
BufferWriter bw{buf};
|
||||
Serializer<BufferWriter> ser{bw};
|
||||
|
||||
serialize(ser, data);
|
||||
|
||||
bw.flush();
|
||||
std::cout << "size: " << buf.size() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
|
||||
Let's take a look at the code, how we did this.
|
||||
|
||||
|
||||
There are three distinct parts that participate in serialization process.
|
||||
* Buffer - container that we store our serialized data, in our case vector<uint8_t>.
|
||||
* BufferWriter - resposible for writing bytes and bits to *Buffer*, it also makes sure that it is portable across Little and Big endian systems.
|
||||
* Serializer - extendable interface that converts any type to bytes or bits, and use *BufferWriter* to write them. Serializer interface also ensures that code is portable at compile time. This means, that if your serialization code compiles on other platform, it will be 100% correct.
|
||||
```cpp
|
||||
std::vector<uint8_t> buf;
|
||||
BufferWriter bw{buf};
|
||||
Serializer<BufferWriter> ser{bw};
|
||||
```
|
||||
|
||||
Serialization function is very readable, and explicitly express intent what and how to serialize:
|
||||
* *value4* serialize [fundamental type](../design/fundamental_types.md) (ints, floats, chars, enums) of 4 bytes.
|
||||
* *text1* effectively serialize text, and underlying text type is 1byte per letter.
|
||||
|
||||
```cpp
|
||||
s.value4(data.pos.x);
|
||||
s.value4(data.pos.y);
|
||||
s.value4(data.pos.z);
|
||||
s.text1(data.name);
|
||||
```
|
||||
|
||||
> learn more about why you need to write [value4 instead of value](../design/function_n.md).
|
||||
|
||||
|
||||
Finally before sending buffer to network you must *flush* BufferWriter, it writes any remaining bits to buffer. In our case it is not required, because we only worked with whole bytes, but it is good practice to always use it after finishing serialization.
|
||||
|
||||
```cpp
|
||||
bw.flush();
|
||||
```
|
||||
|
||||
# Summary
|
||||
|
||||
You have learned how to serialize simple structure to buffer that occupies no unnecessary bytes, and is portable across any system. You also learned that serialization process consist of three independant parts: buffer, buffer writer, and serializer. You used serializer to explicitly declare what and how to serialize and learned that you should always call BufferWriter.flush() before using buffer data.
|
||||
|
||||
In [next chapter](two_in_one.md) you'll learn how to use this expressive, declarative serialization function and use it to deserialize buffer to object, and in the process gain runtime error checking for free!
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -27,15 +27,11 @@ struct MyStruct {
|
||||
uint32_t i;
|
||||
MyEnum e;
|
||||
std::vector<float> fs;
|
||||
char16_t x;
|
||||
bool y;
|
||||
};
|
||||
|
||||
//define how object should be serialized/deserialized
|
||||
SERIALIZE(MyStruct) {
|
||||
return s.
|
||||
value1(o.y).
|
||||
value2(o.x).
|
||||
value4(o.i).
|
||||
value2(o.e).
|
||||
container4(o.fs, 10);
|
||||
@@ -71,4 +67,4 @@ int main() {
|
||||
//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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user