added -growable- function

This commit is contained in:
fraillt
2017-09-08 16:22:54 +03:00
parent cba80774e4
commit 2f8ae0075c
38 changed files with 1723 additions and 1264 deletions

View File

@@ -2,34 +2,50 @@ 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/function_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)
* `valueNb instead of value`
* `fundamental types`
* `serializer/deserializer functions overloads`
* `extending library functionality`
* `errors handling`
* `forward/backward compatibility via growable`
Serializer/Deserializer functions (alphabetical order):
* [align](fnc_array.md)
* [array](fnc_array.md)
* [boolBit/Byte](fnc_bool.md)
* [container](fnc_container.md)
* [custom](fnc_custom.md)
* [extension](fnc_extension.md)
* [isValid](reference/fnc_is_valid.md)
* [object](fnc_object.md)
* [range](fnc_range.md)
* [substitution](fnc_substitution.md)
* [text](fnc_text.md)
* [value](fnc_value.md)
Core Serializer/Deserializer functions (alphabetical order):
* `boolByte`
* `container`
* `object`
* `text`
* `value`
Advanced Serializer/Deserializer functions (alphabetical order):
* `align`
* `boolBit`
* `entropy`
* `extend`
* `growable`
* `range`
BasicBufferWriter/Reader functions:
* [writeBits](bb_write_bits.md)
* `writeBits/readBits`
* `writeBytes/readBytes`
* `writeBuffer/readBuffer`
* `align`
* `beginSession/endSession`
* `flush (writer only)`
* `setError (reader only)`
* `getError (reader only)`
* `isCompletedSuccessfully (reader only)`
Tips and tricks:
* if you're getting static assert "please define 'serialize' function", most likely it is because your SERIALIZE function is not defined in same namespace as object.
Advanced topics:
Limitations:
* max **text** or **container** size can be 2^(n-2) (where n = sizeof(std::size_t) * 8) for 32-bit systems it is 1073741823 (0x3FFFFFF).
* when using **growable** serialized buffer cannot be greater than 2^(n-2) (where n = sizeof(std::size_t) * 8).
Other:
* [Why Bitsery?](why-bitsery.md)
* [Contributing](../CONTRIBUTING.md)
* [Change log](../CHANGELOG.md)

27
doc/design/README.md Normal file
View File

@@ -0,0 +1,27 @@
## Motivation
Inspiration to create **bitsery** came mainly because there aren't any good alternatives for C++.
Most well-known serialization libraries are *too fat* and tries to solve too many things by supporting multiple data formats (binary, json, xml) and multiple languages (C++, C#, Javascript, etc..) while in the process becomes hard to use, are memory or/and speed inefficient.
The best alternative that I was able to find is [flatbuffers](https://google.github.io/flatbuffers/).
It is fast, memory efficient, and [comparing with other alternatives](https://google.github.io/flatbuffers/flatbuffers_benchmarks.html) looks like *de facto* choice for games.
While Flatbuffers is designed with multiple programming languages support, bitsery is designed specifically for C++.
## A word about JSON
People use C++ because they want speed and memory efficiency, and JSON is not on the list of efficient serialization format.
Although JSON is very readable and very convenient when used together with dynamically typed languages (such as JavaScript).
When serializing data from statically typed languages, however, JSON not only has the obvious drawback of runtime inefficiency, but also forces you to write more code to access data (counterintuitively) due to its dynamic-typing serialization system.
It's also a text format,- human readable, but space inefficient.
Adding optional support for JSON doesn't come for free either.
When there is no multi-language support, we no longer require IDL(interface definition language) to define schemas so we could have consistent interface across multiple languages.
When we no longer have code generation, it becomes imposibble to support JSON *for free* without defining additional metadata, because C++ doesn't have a reflection system (although static reflection was proposed to standard recently).
So, to avoid unnecessary library complexity it is best to forget about JSON, and stick with what machines and C++ is good at,- binary format.
Bitsery is a result of what you can get, when you sacrifice multi-language support and JSON format, but take other *goodies*.
# Bitsery
*todo*

View File

@@ -0,0 +1,6 @@
*document in progress*
* NO_ERROR,
* BUFFER_OVERFLOW,
* INVALID_BUFFER_DATA
* write what happens when data is corrupted
* how growable effect error codes when deserializing old data format.

View File

@@ -0,0 +1,7 @@
### BufferReader.isCompletedSuccessfully()
Returns true when buffer was fully read, and there was no [errors](buf_get_error.md).
If buffer contains multiple serialized objects and you want to know deserialization state for each object, then use [getError](buf_get_error.md) after reading each object.
Use this function when buffer contains one object, or you know that you read all data.

View File

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

View File

@@ -88,10 +88,10 @@ struct Player {
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);
s.value4b(data.pos.x);
s.value4b(data.pos.y);
s.value4b(data.pos.z);
s.text1b(data.name);
}
int main() {
@@ -127,7 +127,7 @@ Let's 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.
* Serializer - extendable interface that converts any type to bytes or bits, and use *BufferWriter* to write them. Serializer object does not store any state, it only forwards all calls to BufferWritter, further more it 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};
@@ -135,20 +135,20 @@ There are three distinct parts that participate in serialization process.
```
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.
* *value4b* serialize [fundamental type](../design/fundamental_types.md) (ints, floats, chars, enums) of 4 bytes.
* *text1b* 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);
s.value4b(data.pos.x);
s.value4b(data.pos.y);
s.value4b(data.pos.z);
s.text1b(data.name);
```
> learn more about why you need to write [value4 instead of value](../design/function_n.md).
> learn more about why you need to write [value4b instead of value](../design/function_n.md).
Finally, before getting serialized data 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.
Finally, before getting serialized data you must *flush* BufferWriter, it writes any remaining bits to buffer and additional data for types that require forward/backward compatibility. 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.
To actually get written data you must call *getWrittenRange*, it return begin/end iterators to our buffer (*std::vector<uint8_t> buf*), for performance reasons BufferWritter always resizes underlying buffer to *capacity* so it could use containers iterator to update data, instead of back_insert_iterator to insert data.

View File

@@ -9,10 +9,10 @@ So one way to make this happen is to have *Serializer/Deserializer* as template
```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);
s.value4b(o.pos.x);
s.value4b(o.pos.y);
s.value4b(o.pos.z);
s.text1b(o.name);
}
```
@@ -53,10 +53,10 @@ struct Player {
using namespace bitsery;
SERIALIZE(Player) {
s.value4(o.pos.x);
s.value4(o.pos.y);
s.value4(o.pos.z);
s.text1(o.name);
s.value4b(o.pos.x);
s.value4b(o.pos.y);
s.value4b(o.pos.z);
s.text1b(o.name);
}
Player createData() {
@@ -86,8 +86,7 @@ int main() {
serialize(des, res);
std::cout << "deserializer state: " << des.isValid() << std::endl
<< "buffer completed: " << br.isCompleted() << std::endl
std::cout << "buffer completed successfully: " << br.isCompletedSuccessfully() << std::endl
<< "pos equals: " << (res.pos == data.pos) << std::endl
<< "name equals: " << (strcmp(res.name, data.name) == 0);
return 0;
@@ -95,8 +94,7 @@ int main() {
```
```bash
deserializer state: 1
buffer completed: 1
buffer completed successfully: 1
pos equals: 1
name equals: 1
```
@@ -105,14 +103,14 @@ We created *Deserializer* and modified *serialize* function to accept *Serialize
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.
* BufferReader - reads bytes and bits from *Buffer*, it makes sure that it is portable across Little and Big endian systems and 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.
* Deserializer - same interface as *Serializer* but forward all data to *BufferReader* to read bits and bytes.
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.
* [BufferReader.isCompletedSuccessfully()](../reference/buf_is_completed_successfully.md) - returns true, if whole buffer was read during deserialization and no errors was found.
* [BufferReader.getError()](../reference/buf_get_error.md) - returns current buffer reader state. Useful when buffer contains more than one object, and you want to check each objects deserialization state separately.
One thing to note about BufferReader is that it doesn't have constructor that accepts buffer directly. Instead it only accepts begin/end iterators, because it needs to know precise data buffer length, to correctly use *isComplete* function.
One thing to note about BufferReader is that it doesn't have constructor that accepts buffer directly. Instead it only accepts begin/end iterators, because it needs to know precise data buffer length, to correctly use *isCompleteSuccessfully* function.
```cpp
BufferReader br{bw.getWrittenRange()};