separated advanced features from core functionality

This commit is contained in:
fraillt
2017-09-19 16:10:49 +03:00
parent ad7090539e
commit f0508025f6
36 changed files with 1194 additions and 964 deletions

View File

@@ -1,12 +1,12 @@
# [3.0.0](https://github.com/fraillt/bitsery/compare/v2.0.1...v3.0.0) (2017-09-02)
# [3.0.0](https://github.com/fraillt/bitsery/compare/v2.0.1...v3.0.0) (2017-09-21)
### Features
* now works with C++11 compiler.
* new function **growable**, that allows to have forward/backward compatability within this functions serialization flow. It only allows to append new data at the end of to existing flow without breaking old consumers.
* refactored interface, now works with C++11 compiler.
* new new extension **Growable**, that allows to have forward/backward compatability within this functions serialization flow. It only allows to append new data at the end of to existing flow without breaking old consumers.
* old consumer: correctly read old interfce and ignore new data.
* new consumer: get defaults (zero values) for new fields, when reading old data.
* added new extension for associative *map* containers **containerMap**.
* added new extension for associative *map* containers **ContainerMap**.
* friendly static_assert message when serializing **object**, that doesn't have **serialize** function defined.
* added **object** overload, that invokes user function/lambda with object. It is the same as calling user function directly, but makes more consistent API.
* Serializer/Deserializer now have optional *context* parameter, that might be required in some specific serialization cases.
@@ -18,19 +18,21 @@
### Breaking changes
* now all serializer/deserializer functions return void, to avoid undefined behaviour for functions parameters evaluation when using method chaining. There was no benefits apart from *nicer* syntax, but could have undefined behaviour when building complex serialization flows.
* removed **SERIALIZE** macro, and changed interface for all functions that use custom lambdas, to work with C++11. Now lambda function must capture serializer/deserializer.
* to make serializer a little bit *lighter* removed advanced features and made available as extensions via **extend** method
* removed **range** method, instead added **ValueRange** extension.
* removed **substitution** method, instead added **Entropy** extension, to sound more familiar to *entropy encoding* term.
* removed **array**, instead added fixed sizes overloads for **container**.
* removed **isValid** method from Deserializer, only BufferReader/Writer store states.
* container and text sizes representation changed, to allow much faster size reads/writes for small values.
* renamed functions:
* **ext** to **extend** and changed its interface, to make it more easy to extend.
* alias functions that write bytes directly no has *b* (meaning bytes) at the end of the name eg. *value4* now is *value4b*.
* **substitution** changed to **entropy**, to sound more familiar to *entropy encoding* term.
* now all serializer/deserializer functions return void, to avoid undefined behaviour for functions parameters evaluation when using method chaining. There was no benefits apart from *nicer* syntax, but could have undefined behaviour when building complex serialization flows.
* removed **array** and added fixed sizes overloads for **container**.
* changed BufferWriter/Reader behaviour:
* added support for fixed size buffers for better serializer performance (more than 50% improvement). Default config is resizable buffer (*std::vector<uint8_t>*).
* after serialization, call *getWrittenRange* to get valid range written to buffer, because BufferWritter for resizable buffer now always resize to *capacity* to avoid using *back_insert_iterator* for better performance.
* BufferReader has constructor with iterators (range), and raw value type pointers (begin, end).
* removed **isValid** method from Deserializer, only BufferReader/Writer store states.
* renamed BufferReader **isCompleted** to **isCompletedSuccessfully**, that returns true only when there is no errors and buffer is fully read.
### Bug fixes

View File

@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.2)
project(bitsery)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(examples)

View File

@@ -1,6 +1,7 @@
# Contributing to Bitsery
So you want to contribute something to Bitsery? That's great! Contributions are always very much appreciated, whether it's a bug fix, a new feature - or just a fix to the documentation.
So you want to contribute something to Bitsery? That's great!
Contributions are always very much appreciated, whether it's a bug fix, a new feature or documentation.
However, to make sure the process of accepting patches goes smoothly, you should try to follow these few simple guidelines when
you contribute:

View File

@@ -15,17 +15,17 @@ All cross-platform requirements are enforced at compile time, so serialized data
* Cross-platform compatible.
* Optimized for speed and space.
* No code generation required: no IDL or metadata, just use your types directly.
* Runtime error checking on deserialization,- designed to be save with untrusted network data.
* Provides forward/backward compatibility for your types.
* Runtime error checking on deserialization.
* Supports forward/backward compatibility for your types.
* 2-in-1 declarative control flow, same code for serialization and deserialization.
* Allows fine-grained serialization control using advanced serialization techniques like value ranges, entrophy encoding etc...
* Extendable for flows that requires different serialization and deserialization logic (e.g. pointers serialization, geometry compression, or versioning support).
* Allows fine-grained bit-level serialization control.
* Easily extendable.
* Configurable endianess support.
* No macros.
## Why to use bitsery
Look at the numbers, and features list, and deside for your self. *(benchmarked on Ubuntu with GCC 7.1)*
Look at the numbers and features list, and decide yourself. *(benchmarked on Ubuntu with GCC 7.1)*
| | serialize | deserialize | data size | executable size |
|-----------------------------------------------------------|-----------|-------------|-------------|-----------------|
@@ -38,11 +38,49 @@ Look at the numbers, and features list, and deside for your self. *(benchmarked
If still not convinced read more in library [motivation](doc/design/README.md) section.
## Usage example
```cpp
#include <bitsery/bitsery.h>
using namespace bitsery;
struct MyStruct {
uint32_t i;
char str[6];
std::vector<float> fs;
};
template <typename S>
void serialize(S& s, MyStruct& o) {
s.value4b(o.i);
s.text1b(o.str);
s.container4b(o.fs, 100);
};
int main() {
std::vector<uint8_t> buffer;
BufferWriter bw{buffer};
Serializer ser{bw};
MyStruct data{8941, "hello", {15.0f, -8.5f, 0.045f}};
ser.object(data); // serializes data
BufferReader br{bw.getWrittenRange()};
Deserializer des{br};
MyStruct res{};
des.object(res); //deserializes data
}
```
For more details go directly to [Quick start](doc/tutorial/hello_world.md) tutorial.
## How to use it
This documentation comprises these parts:
* [Tutorial](doc/tutorial/README.md) - getting started.
* [Reference section](doc/README.md) - all the details.
*documentation is in progress, most parts are empty, but [contributions](CONTRIBUTING.md) are welcome.*
## Requirements
Works with C++11 compiler, no additional dependencies, include `<bitsery/bitsery.h>` and you're done.
@@ -50,7 +88,10 @@ Works with C++11 compiler, no additional dependencies, include `<bitsery/bitsery
## Platforms
This library was tested on
* Windows: Visual Studio 2015, MinGW (gcc 5.2)
* Windows: Visual Studio 2015, MinGW (GCC 5.2)
* Linux: GCC 5.4, GCC 6.2, Clang 3.9
* OS X Mavericks: AppleClang 8
## License
**bitsery** is licensed under the [MIT license](LICENSE).

View File

@@ -7,22 +7,25 @@ Library design:
* `serializer/deserializer functions overloads`
* `extending library functionality`
* `errors handling`
* `forward/backward compatibility via growable`
* `forward/backward compatibility via Growable extension`
Core Serializer/Deserializer functions (alphabetical order):
* `align`
* `boolByte`
* `boolBit`
* `container`
* `extend`
* `getContext`
* `object`
* `text`
* `value`
Advanced Serializer/Deserializer functions (alphabetical order):
* `align`
* `boolBit`
* `entropy`
* `extend`
* `growable`
* `range`
Serializer/Deserializer extensions via `extend` method (alphabetical order):
* `ContainerMap`
* `Entropy`
* `Growable`
* `Optional`
* `ValueRange`
BasicBufferWriter/Reader functions:
* `writeBits/readBits`
@@ -35,14 +38,12 @@ BasicBufferWriter/Reader functions:
* `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.
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).
* when using **Growable** extension, serialized buffer size in bytes, cannot be greater than 2^(n-2) (where n = sizeof(std::size_t) * 8).
Other:
* [Contributing](../CONTRIBUTING.md)

View File

@@ -1,28 +1,70 @@
## Motivation
Inspiration to create **bitsery** came mainly because there aren't any good alternatives for C++.
I wanted serializer that is easy to use like [cereal](http://uscilab.github.io/cereal/)
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++.
I wanted serializer that is easy to use like [cereal](http://uscilab.github.io/cereal/), is cross-platform compatible, and has support for forward/backward compatibility like [flatbuffers](https://google.github.io/flatbuffers/), is save to use with untrusted (malicious) data, and most importantly is fast and has small binary footprint.
Furthermore I wanted full serialization control and ability to work on bit level, so I can further reduce data size. For example, serializing container of [quaternions](https://en.wikipedia.org/wiki/Quaternion) I can reduce size by large amount. *Size of orientation quaternion can be reduced from 128bits (4floats) down to 29bits using "smallest three" technique and still retaining decent precision*.
Most well-known serialization libraries sacrifice memory and speed efficiency by supporting multiple data formats (binary, json, xml) and multiple languages (C++, C#, Javascript, etc..), these features also adds additional library complexity.
## 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.
Often times 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).
To support JSON, additional metadata is required.
In C++ it can be achieved by two ways:
* with macros, that generate additional types like [cereal](http://uscilab.github.io/cereal/) does.
* with code generation from IDL (interface definition language) like [flatbuffers](https://google.github.io/flatbuffers/) does.
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.
Both solutions adds additional complexity to the library. In the future C++ will get reflection system, currently static reflection was proposed to standard.
Bitsery is a result of what you can get, when you sacrifice multi-language support and JSON format, but take other *goodies*.
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
*todo*
Bitsery is designed to be lightweight and simple to use, yet powerful and extendable library.
To ensure it works as intended it is unit tested, and has 100% code coverage.
Now let's review features in more detail.
* **Cross-platform compatible.** if same code compiles on Android, PS3 console, and your PC either x64 or x86 architecture, you are 100% sure it works.
To achieve this, bitsery specifically defines size of underlying data, hence syntax is *value\<2\>* (alias function *value2b*) instead or *value*, or *container2b* for element type of 16bits, eg int16_t.
Bitsery also applies endianess transformation if nessesarry.
**If** however, you don't like this verbose syntax, you can just write *serialize* functions for fundamental types, and forget about *value\<N\>*, *container\<N\>*, etc.
But do it on your own risk, or write static asserts.
* **Optimized for speed and space.** library itself doesn't do any allocations (except if you use backward/forward compatibility) so data writing/reading is fast as memcpy to/from your buffer.
It also doesn't serialize any type information, all information needed is writen in your code!
* **No code generation required: no IDL or metadata** since it doesn't support any other formats except binary, it doesn't need any metadata.
* **Runtime error checking on deserialization** library designed to be save with untrusted network data, that's why all overloads that work on containers has *maxSize* value, unless container is static size like *std::array*, this way bitsery ensures that no malicious data will not crash you.
* **Supports forward/backward compatibility for your types** library has optional forward/backward compatibility for types implemented in *BasicBufferReader/BasicBufferWriter* by allowing to have inner data sessions in inside buffer.
This is the only functionality that requires dynamic memory allocation.
*Glowable* extension use these sessions to add compatibility support for your types, in most basic form.
You can implement your own extensions if you want to be able to add default values.
* **2-in-1 declarative control flow, same code for serialization and deserialization.** only one function to define, for serialization and deserialization in same manner as *cereal* does.
It might be handy to have separate *load*, *save* functions, but Bitsery explicitly doesn't support it, to avoid any serialization deserialization path differences, because it is very hard to catch an errors if you make a bug in one of these functions.
The only way around this through extensions, write your custom flow once, and reuse where you need them.
* **Allows fine-grained serialization control** this is a feature that no other libraries provides.
Bitsery allows to use bit-level operations and has two extensions that use them:
* *ValueRange*,- if you have a *int16_t* data type, but you know that your object only stores values in \[0..1000\] range, it will write 10bits, instead of 16bits. ValueRanges also works with floats.
* *Entropy*,- full term is *entropy encoding*, which means that when you have most common value, or multiple values, it will write just few bits instead of full object.
Eg.: imagine that you have a struct Person{ int32_t Id; string Profession; }.
You know that mostly there are young persons, so the most common value will be equal to: "Student", "Child", "NoProfession", in this case you'll pay 2bits for each record, but write no data if string matches.
Using these bit-level operations and extensions you can compose your own extensions for vectors, matrices or any other types.
Further more, all other operations will not align data automatically for you, so data will be compressed as much as possible.
One more advanced and dangerous feature, is ability to have serialization context, so you can control your serialization flow at runtime, but make sure that these contexts are in sync between serializer and deserializer.
One possible use case for serialization context is to pass min/max ranges for *ValueRange* when your information changes at runtime.
* **Easily extendable** library is designed to be easily extendable for any type and flow.
You want to support your custom container, its fine there is *ContainerTraits* for this, only few methods required to implement.
To use same container for buffer writing/reading add specialization to *BufferContainerTraits*.
You want to customize serialization flow - use extensions, only two methods to define, and *ExtensionTraits* to further customize usage.
* **Configurable endianess support.** default is *Little Endian*, but if your primary target is PowerPC architecture, eg. PlayStation3, just change your configuration to be *Big Endian*.
* **No macros.** Not so much to say, if you are like me, then it's a feature :)
*project for performance benchmark will be added to separate github project, i'll give you a link to it when its done.*

View File

@@ -1 +1 @@
int char (except bool)
ints chars floats (except bool)

View File

@@ -1,14 +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](hello_world.md) serialize a simple struct.
* [2 in 1](two_in_one.md) write one control flow for both: serialization and deserialization.
* [Composer](composition.md) efficiently compose complex serialization flows.
* [Squeeze Me!](compression.md) compress your data when you know what it stores.
* [Anything is Possible](extensions.md) extend library for custom container, compress geometry and more.
* [Little or Big](endianness.md) 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).
* `Hello World` write one control flow for both: serialization and deserialization.
* `Composer` efficiently compose complex serialization flows.
* `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 +1,3 @@
*document in progress*
* explain why *value* and *object* is fundamental functions.
* write about **growable** and **customize**
* write about *Growable* extension

View File

@@ -1,167 +1,128 @@
# The problem
# Quick Start
You want to serialize *Player* structure efficiently into buffer.
This is a quick guide to get **bitsery** up and running in a matter of minutes.
The only prerequisite for running bitsery is a modern C++11 compliant compiler, such as GCC 4.9.4, clang 3.4, MSVC 2015, or newer.
Older versions might work, but it is not tested.
## Get bitsery
bitsery can be directly included in your project or installed anywhere you can access header files.
Grab the latest version, and include directory `bitsery_base_dir/include/` to your project.
There's nothing to build or make - **bitsery** is header only.
## Add serialization methods for your types
**bitsery** needs to know which data members to serialize in your classes.
Let it know by implementing a serialize method for your type:
```cpp
struct Vector3f {
float x;
float y;
float z;
};
struct Player {
Vector3f pos;
char name[50];
struct MyStruct {
uint32_t i;
char str[6];
std::vector<float> fs;
};
template <typename S>
void serialize(S& s, MyStruct& o) {
s.value4b(o.i);
s.text1b(o.str);
s.container4b(o.fs, 100);
};
```
# Poor man's implementation
**bitsery** also can serialize private class members, just move *serialize* function inside structure, and make it *friend* (*fiend void serialize(.....)*).
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.
**bitsery** has verbose syntax, because it is cross-platform compatible by default and has full control over how to serialize data (read more about it in [motivation](../design/README.md))
This example contains core functionality that you'll use all the time, so lets get through it:
* **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.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.
External serialization functions should be placed either in the same namespace as the types they serialize or in the **bitsery** namespace so that the compiler can find them properly.
## Serialization and deserialization
### Create serializer
Create a serializer and send the data you want to serialize to it.
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;
}
std::vector<uint8_t> buffer;
BufferWriter bw{buffer};
Serializer ser{bw};
```
```bash
size: 64
```
Serialization process consists of three independant parts.
* **std::vector<uint8_t> buffer;** core object, that will store the data for serialization and deserialization.
* **BufferWriter bw{buffer};** writer knows how to write bytes to buffer, and how to resize buffer, or how to use fixed-size buffer. It also applies endianess transformations if nesessary.
* **Serializer ser{bw};** serializer is a high level wrapper that knows how to convert object to stream of bytes, and write then to buffer.
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).
Serializer doesn't store any state, it only has reference to buffer, so it is safe to create many of those if nesessary.
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.
BufferWriter also doesn't own buffer, but it stores state about writing position and container size.
# Bitsery solution
One important note that when using bit-level operations, dont forget to flush buffer writer **bw.flush()** otherwise, some data might not be written to buffer.
### Serialize object
```cpp
MyStruct data{8941, "hello", {15.0f, -8.5f, 0.045f}};
ser.object(data); // serializes data
```
**ser.object(data)** is a final core function along with **value, text, container**.
This function is actually equivalent to calling *serialize(ser, data)* directly, but it displays friendly static assert message if it cannot find *serialize* function for your type.
### Deserialize object
```cpp
BufferReader br{bw.getWrittenRange()};
Deserializer des{br};
MyStruct res{};
des.object(res); //deserializes data
```
Deserialization process is equivalent to serialization, except that *BufferReader* reader has getError() method that returns deserialization state.
## Full example code
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.value4b(data.pos.x);
s.value4b(data.pos.y);
s.value4b(data.pos.z);
s.text1b(data.name);
}
struct MyStruct {
uint32_t i;
char str[6];
std::vector<float> fs;
};
template <typename S>
void serialize(S& s, MyStruct& o) {
s.value4b(o.i);
s.text1b(o.str);
s.container4b(o.fs, 100);
};
int main() {
Player data;
strcpy(data.name,"Yolo");
std::vector<uint8_t> buffer;
BufferWriter bw{buffer};
Serializer ser{bw};
std::vector<uint8_t> buf;
BufferWriter bw{buf};
Serializer<BufferWriter> ser{bw};
MyStruct data{8941, "hello", {15.0f, -8.5f, 0.045f}};
ser.object(data); // serializes data
serialize(ser, data);
BufferReader br{bw.getWrittenRange()};
Deserializer des{br};
bw.flush();
auto range = bw.getWrittenRange();
std::cout << "size: " << std::distance(range.begin(), range.end()) << std::endl;
return 0;
MyStruct res{};
des.object(res); //deserializes data
}
```
```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.
* more readable serialization code.
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 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};
Serializer<BufferWriter> ser{bw};
```
Serialization function is very readable, and explicitly express intent what and how to serialize:
* *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.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 [value4b instead of value](../design/function_n.md).
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.
```cpp
bw.flush();
auto range = bw.getWrittenRange();
```
# 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!
**currently documentation and tutorial is progress, but for more usage examples see examples folder**

View File

@@ -1,138 +0,0 @@
# 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.value4b(o.pos.x);
s.value4b(o.pos.y);
s.value4b(o.pos.z);
s.text1b(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.value4b(o.pos.x);
s.value4b(o.pos.y);
s.value4b(o.pos.z);
s.text1b(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{bw.getWrittenRange()};
Deserializer<BufferReader> des{br};
serialize(des, res);
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;
}
```
```bash
buffer completed successfully: 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 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.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 *isCompleteSuccessfully* function.
```cpp
BufferReader br{bw.getWrittenRange()};
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 how to compose complex serialization/deserialization flows efficiently.

View File

@@ -0,0 +1,101 @@
#include <bitsery/bitsery.h>
#include <bitsery/ext/growable.h>
#include <array>
namespace MyTypes {
//define data
enum Color:uint8_t { Red, Green, Blue };
struct Vec3 { float x, y, z; };
struct Weapon {
std::string name;
int16_t damage;
};
struct Monster {
Vec3 pos;
int16_t mana;
int16_t hp;
std::string name;
std::vector<uint8_t> inventory;
Color color;
std::vector<Weapon> weapons;
Weapon equipped;
std::vector<Vec3> path;
};
template <typename S>
void serialize(S& s, Vec3& o) {
s.value4b(o.x);
s.value4b(o.y);
s.value4b(o.z);
}
//define serialization functions
template <typename S>
void serialize (S& s, Weapon& o) {
//forward/backward compatibility for monsters
s.ext(o, bitsery::ext::Growable{}, [&s](Weapon& o1) {
s.text1b(o1.name, 20);
s.value2b(o1.damage);
});
}
template <typename S>
void serialize (S& s, Monster& o) {
//forward/backward compatibility for monsters
s.ext(o, bitsery::ext::Growable{}, [&s](Monster& o1) {
s.value1b(o1.color);
s.value2b(o1.mana);
s.value2b(o1.hp);
s.object(o1.equipped);
s.object(o1.pos);
s.container(o1.path, 1000);
s.container(o1.weapons, 100);
s.container1b(o1.inventory, 50);
s.text1b(o1.name, 20);
});
}
}
using namespace bitsery;
//change configuration
struct NonDefaultConfig: public bitsery::DefaultConfig {
//change underlying buffer
using BufferType = std::array<uint8_t, 1000000>;
};
int main() {
//set some random data
MyTypes::Monster data{};
data.name = "lew";
//create serializer
//1) create buffer to store data
std::array<uint8_t, 1000000> buffer{};
//2) create buffer writer that is able to write bytes or bits to buffer
BasicBufferWriter<NonDefaultConfig> bw{buffer};
//3) create serializer
BasicSerializer<NonDefaultConfig> ser{bw};
//serialize object, can also be invoked like this: serialize(ser, data)
ser.object(data);
//flush to buffer, before creating buffer reader, this will always write sessions data for forward/backward compatibility
bw.flush();
//create deserializer
//1) create buffer reader
BasicBufferReader<NonDefaultConfig> br{bw.getWrittenRange()};
//2) create deserializer
BasicDeserializer<NonDefaultConfig> des{br};
//deserialize same object, can also be invoked like this: serialize(des, data)
MyTypes::Monster res{};
des.object(res);
}

View File

@@ -25,7 +25,7 @@
#ifndef BITSERY_BUFFER_READER_H
#define BITSERY_BUFFER_READER_H
#include "common.h"
#include "details/buffer_common.h"
#include <algorithm>
#include <cstring>
@@ -151,9 +151,12 @@ namespace bitsery {
private:
ValueType* _pos;
ValueType* _end;
details::BufferSessionsReader<BasicBufferReader<Config>, ValueType*> _session;
ScratchType m_scratch{};
size_t m_scratchBits{}; ///< Number of bits currently in the scratch buffer. If the user wants to read more bits than this, we have to go fetch another dword from memory.
size_t m_scratchBits{};
typename std::conditional<Config::BufferSessionsEnabled,
details::BufferSessionsReader<BasicBufferReader<Config>, ValueType*>,
details::DisabledBufferSessionsReader<Config>>::type
_session;
template<typename T>
void directRead(T *v, size_t count) {

View File

@@ -25,7 +25,7 @@
#ifndef BITSERY_BUFFER_WRITER_H
#define BITSERY_BUFFER_WRITER_H
#include "common.h"
#include "details/buffer_common.h"
#include <cassert>
#include <utility>
@@ -171,13 +171,12 @@ namespace bitsery {
void beginSession() {
align();
_session.begin();
_session.begin(*this);
}
void endSession() {
align();
auto range = _bufferContext.getWrittenRange();
_session.end(static_cast<size_t>(std::distance(range.begin(), range.end())));
_session.end(*this);
}
private:
@@ -241,7 +240,10 @@ namespace bitsery {
BufferContext _bufferContext;
ScratchType _scratch{};
size_t _scratchBits{};
details::BufferSessionsWriter _session{};
typename std::conditional<Config::BufferSessionsEnabled,
details::BufferSessionsWriter<BasicBufferWriter<Config>>,
details::DisabledBufferSessionsWriter<Config>>::type
_session{};
};
//helper type

View File

@@ -24,15 +24,28 @@
#ifndef BITSERY_COMMON_H
#define BITSERY_COMMON_H
#include "details/buffer_common.h"
#include <vector>
namespace bitsery {
/*
* endianess
*/
enum class EndiannessType {
LittleEndian,
BigEndian
};
//default configuration for buffer writing/reading operations
struct DefaultConfig {
static constexpr EndiannessType NetworkEndianness = EndiannessType::LittleEndian;
using BufferType = std::vector<uint8_t>;//buffer value type must be unsigned, currently only uint8_t supported
//this functionality allows to support backward/forward compatibility for any type
//disabling it, saves 100+bytes per BufferReader/Writer and also reduces executable size
static constexpr bool BufferSessionsEnabled = true;
//buffer value type must be unsigned, currently only uint8_t supported
//fixed size buffer type also supported, for faster serialization performance
using BufferType = std::vector<uint8_t>;
};
}

View File

@@ -27,7 +27,6 @@
#include "common.h"
#include "details/serialization_common.h"
#include <utility>
#include <string>
namespace bitsery {
@@ -81,37 +80,35 @@ namespace bitsery {
}
/*
* growable function
*/
template <typename T, typename Fnc>
void growable(T&& obj, Fnc&& fnc) {
_reader.beginSession();
fnc(std::forward<T>(obj));
_reader.endSession();
};
/*
* extend functions
* extension functions
*/
template<typename T, typename Ext, typename Fnc>
void extend(T &obj, Ext &&ext, Fnc &&fnc) {
ext.deserialize(*this, _reader, obj, std::forward<Fnc>(fnc));
void ext(T &obj, Ext &&extension, Fnc &&fnc) {
using ExtType = typename std::decay<Ext>::type;
static_assert(details::ExtensionTraits<ExtType,T>::SupportLambdaOverload,
"extension doesn't support overload with lambda");
extension.deserialize(*this, _reader, obj, std::forward<Fnc>(fnc));
};
template<size_t VSIZE, typename T, typename Ext>
void extend(T &obj, Ext &&ext) {
static_assert(details::HasTValue<details::ExtensionTraits<Ext, T>>::value,
"this extension only supports overload with lambda");
ext.deserialize(*this, _reader, obj, [this](typename details::ExtensionTraits<Ext, T>::TValue &v) { value<VSIZE>(v); });
void ext(T &obj, Ext &&extension) {
using ExtType = typename std::decay<Ext>::type;
static_assert(details::ExtensionTraits<ExtType,T>::SupportValueOverload,
"extension doesn't support overload with `value<N>`");
using ExtVType = typename details::ExtensionTraits<ExtType, T>::TValue;
using VType = typename std::conditional<std::is_void<ExtVType>::value, details::DummyType, ExtVType>::type;
extension.deserialize(*this, _reader, obj, [this](VType &v) { value<VSIZE>(v); });
};
template<typename T, typename Ext>
void extend(T &obj, Ext &&ext) {
static_assert(details::HasTValue<details::ExtensionTraits<Ext, T>>::value,
"this extension only supports overload with lambda");
ext.deserialize(*this, _reader, obj, [this](typename details::ExtensionTraits<Ext, T>::TValue &v) { object(v); });
void ext(T &obj, Ext &&extension) {
using ExtType = typename std::decay<Ext>::type;
static_assert(details::ExtensionTraits<ExtType,T>::SupportObjectOverload,
"extension doesn't support overload with `object`");
using ExtVType = typename details::ExtensionTraits<ExtType, T>::TValue;
using VType = typename std::conditional<std::is_void<ExtVType>::value, details::DummyType, ExtVType>::type;
extension.deserialize(*this, _reader, obj, [this](VType &v) { object(v); });
};
/*
@@ -132,53 +129,6 @@ namespace bitsery {
v = tmp == 1;
}
/*
* range
*/
template<typename T>
void range(T &v, const RangeSpec<T> &range) {
_reader.readBits(reinterpret_cast<details::SAME_SIZE_UNSIGNED<T> &>(v), range.bitsRequired);
details::setRangeValue(v, range);
if (!details::isRangeValid(v, range)) {
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
v = range.min;
}
}
/*
* entropy overloads
*/
template<typename T, size_t N, typename Fnc>
void entropy(T &obj, const T (&expectedValues)[N], Fnc &&fnc) {
size_t index;
range(index, {{}, N + 1});
if (index)
obj = expectedValues[index - 1];
else
fnc(obj);
};
template<size_t VSIZE, typename T, size_t N>
void entropy(T &v, const T (&expectedValues)[N]) {
size_t index;
range(index, {{}, N + 1});
if (index)
v = expectedValues[index - 1];
else
value<VSIZE>(v);
};
template<typename T, size_t N>
void entropy(T &obj, const T (&expectedValues)[N]) {
size_t index;
range(index, {{}, N + 1});
if (index)
obj = expectedValues[index - 1];
else
object(obj);
};
/*
* text overloads
*/
@@ -188,7 +138,7 @@ namespace bitsery {
static_assert(details::TextTraits<T>::isResizable,
"use text(T&) overload without `maxSize` for static containers");
size_t size;
readSize(size, maxSize);
details::readSize(_reader, size, maxSize);
details::TextTraits<T>::resize(str, size);
auto begin = std::begin(str);
auto end = std::next(begin, size);
@@ -205,7 +155,7 @@ namespace bitsery {
auto begin = std::begin(str);
auto containerEnd = std::end(str);
assert(begin != containerEnd);
readSize(size, static_cast<size_t>(std::distance(begin, containerEnd) - 1));
details::readSize(_reader, size, static_cast<size_t>(std::distance(begin, containerEnd) - 1));
//end of string, not en
auto end = std::next(begin, size);
procContainer<VSIZE>(std::begin(str), std::end(str), std::true_type{});
@@ -216,7 +166,7 @@ namespace bitsery {
template<size_t VSIZE, typename T, size_t N>
void text(T (&str)[N]) {
size_t size;
readSize(size, N - 1);
details::readSize(_reader, size, N - 1);
auto first = std::begin(str);
procContainer<VSIZE>(first, std::next(first, size), std::true_type{});
//null-terminated string
@@ -230,11 +180,12 @@ namespace bitsery {
//dynamic size containers
template<typename T, typename Fnc>
void container(T &&obj, size_t maxSize, Fnc &&fnc) {
void container(T &obj, size_t maxSize, Fnc &&fnc) {
static_assert(details::ContainerTraits<T>::isResizable,
"use container(T&) overload without `maxSize` for static containers");
size_t size{};
readSize(size, maxSize);
details::readSize(_reader, size, maxSize);
details::ContainerTraits<T>::resize(obj, size);
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
}
@@ -244,7 +195,7 @@ namespace bitsery {
static_assert(details::ContainerTraits<T>::isResizable,
"use container(T&) overload without `maxSize` for static containers");
size_t size{};
readSize(size, maxSize);
details::readSize(_reader, size, maxSize);
details::ContainerTraits<T>::resize(obj, size);
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
}
@@ -254,14 +205,14 @@ namespace bitsery {
static_assert(details::ContainerTraits<T>::isResizable,
"use container(T&) overload without `maxSize` for static containers");
size_t size{};
readSize(size, maxSize);
details::readSize(_reader, size, maxSize);
details::ContainerTraits<T>::resize(obj, size);
procContainer(std::begin(obj), std::end(obj));
}
//fixed size containers
template<typename T, typename Fnc, typename std::enable_if<!std::is_integral<Fnc>::value>::type * = nullptr>
void container(T &&obj, Fnc &&fnc) {
void container(T &obj, Fnc &&fnc) {
static_assert(!details::ContainerTraits<T>::isResizable,
"use container(T&, size_t, Fnc) overload with `maxSize` for dynamic containers");
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
@@ -271,7 +222,7 @@ namespace bitsery {
void container(T &obj) {
static_assert(!details::ContainerTraits<T>::isResizable,
"use container(T&, size_t) overload with `maxSize` for dynamic containers");
static_assert(VSIZE > 0);
static_assert(VSIZE > 0, "");
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
}
@@ -318,28 +269,16 @@ namespace bitsery {
void value8b(T &&v) { value<8>(std::forward<T>(v)); }
template<typename T, typename Ext>
void extend1b(T &v, Ext &&ext) { extend<1>(v, std::forward<Ext>(ext)); };
void ext1b(T &v, Ext &&extension) { ext<1, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T, typename Ext>
void extend2b(T &v, Ext &&ext) { extend<2>(v, std::forward<Ext>(ext)); };
void ext2b(T &v, Ext &&extension) { ext<2, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T, typename Ext>
void extend4b(T &v, Ext &&ext) { extend<4>(v, std::forward<Ext>(ext)); };
void ext4b(T &v, Ext &&extension) { ext<4, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T, typename Ext>
void extend8b(T &v, Ext &&ext) { extend<8>(v, std::forward<Ext>(ext)); };
template<typename T, size_t N>
void entropy1b(T &v, const T (&expectedValues)[N]) { entropy<1, T, N>(v, expectedValues); };
template<typename T, size_t N>
void entropy2b(T &v, const T (&expectedValues)[N]) { entropy<2, T, N>(v, expectedValues); };
template<typename T, size_t N>
void entropy4b(T &v, const T (&expectedValues)[N]) { entropy<4, T, N>(v, expectedValues); };
template<typename T, size_t N>
void entropy8b(T &v, const T (&expectedValues)[N]) { entropy<8, T, N>(v, expectedValues); };
void ext8b(T &v, Ext &&extension) { ext<8, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T>
void text1b(T &str, size_t maxSize) { text<1>(str, maxSize); }
@@ -400,14 +339,6 @@ namespace bitsery {
BasicBufferReader<Config> &_reader;
void* _context;
void readSize(size_t &size, size_t maxSize) {
details::readSize(_reader, size);
if (size > maxSize) {
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
size = {};
}
}
//process value types
//false_type means that we must process all elements individually
template<size_t VSIZE, typename It>
@@ -438,6 +369,16 @@ namespace bitsery {
object(*first);
};
//these are dummy functions for extensions that have TValue = void
void object(details::DummyType&) {
}
template <size_t VSIZE>
void value(details::DummyType&) {
}
};
//helper type

View File

@@ -27,12 +27,19 @@
#include <cstddef>
namespace bitsery {
enum class BufferReaderError {
NO_ERROR,
BUFFER_OVERFLOW,
INVALID_BUFFER_DATA
};
namespace details {
/*
* size read/write functions
*/
template <typename Reader>
void readSize(Reader& r, size_t& size) {
void readSize(Reader& r, size_t& size, size_t maxSize) {
uint8_t hb{};
r.template readBytes<1>(hb);
if (hb < 0x80u) {
@@ -48,6 +55,10 @@ namespace bitsery {
size = ((hb & 0x7Fu) << 8) | lb;
}
}
if (size > maxSize) {
r.setError(BufferReaderError::INVALID_BUFFER_DATA);
size = {};
}
}
template <typename Writter>

View File

@@ -32,15 +32,9 @@
#include "both_common.h"
#include "traits.h"
namespace bitsery {
#include "../common.h"
/*
* endianess
*/
enum class EndiannessType {
LittleEndian,
BigEndian
};
namespace bitsery {
template<typename I>
struct BufferRange : std::pair<I, I> {
@@ -49,12 +43,6 @@ namespace bitsery {
I end() const { return this->second; }
};
enum class BufferReaderError {
NO_ERROR,
BUFFER_OVERFLOW,
INVALID_BUFFER_DATA
};
namespace details {
template<typename T>
@@ -63,7 +51,7 @@ namespace bitsery {
};
//add swap functions to class, to avoid compilation warning about unused functions
struct swapImpl {
struct SwapImpl {
static uint64_t exec(uint64_t value) {
#ifdef __GNUC__
return __builtin_bswap64(value);
@@ -98,7 +86,7 @@ namespace bitsery {
using UT = typename std::conditional<TSize == 1, uint8_t,
typename std::conditional<TSize == 2, uint16_t,
typename std::conditional<TSize == 4, uint32_t, uint64_t>::type>::type>::type;
return swapImpl::exec(static_cast<UT>(value));
return SwapImpl::exec(static_cast<UT>(value));
}
//add test data in separate struct, because some compilers only support constexpr functions with return-only body
@@ -134,26 +122,60 @@ namespace bitsery {
using type = uint64_t;
};
// struct BufferSessionInfo {
// size_t depth;
// size_t offset;
// };
template <typename Config>
struct DisabledBufferSessionsWriter {
template <typename TWriter>
void begin(TWriter& ) {
static_assert(Config::BufferSessionsEnabled, "Buffer sessions is disabled, enable it via configuration");
}
template <typename TWriter>
void end(TWriter& ) {
static_assert(Config::BufferSessionsEnabled, "Buffer sessions is disabled, enable it via configuration");
}
template <typename TWriter>
void flushSessions(TWriter& ) {
}
};
template <typename Config>
struct DisabledBufferSessionsReader {
template <typename TReader, typename TIterator>
DisabledBufferSessionsReader(TReader& , TIterator& , TIterator& ) {
}
void begin() {
static_assert(Config::BufferSessionsEnabled, "Buffer sessions is disabled, enable it via configuration");
}
void end() {
static_assert(Config::BufferSessionsEnabled, "Buffer sessions is disabled, enable it via configuration");
}
bool hasActiveSessions() const {
return false;
}
};
template <typename TWriter>
class BufferSessionsWriter {
public:
void begin() {
void begin(TWriter& ) {
//write position
_sessionIndex.push(_sessions.size());
_sessions.emplace_back(0);
}
void end(size_t pos) {
void end(TWriter& writer) {
assert(!_sessionIndex.empty());
//change position to session end
auto sessionIt = std::next(std::begin(_sessions), _sessionIndex.top());
_sessionIndex.pop();
*sessionIt = pos;
auto range = writer.getWrittenRange();
*sessionIt = static_cast<size_t>(std::distance(range.begin(), range.end()));
}
template <typename TWriter>
void flushSessions(TWriter& writer) {
if (_sessions.size()) {
assert(_sessionIndex.empty());
@@ -188,13 +210,12 @@ namespace bitsery {
template <typename TReader, typename TIterator>
struct BufferSessionsReader {
TIterator _bufBegin;
BufferSessionsReader(TReader& r, TIterator& begin, TIterator& end)
:_reader{r},
_begin{begin},
_pos{begin},
_end{end}
{
_bufBegin = begin;
}
void begin() {
if (_sessions.empty())
@@ -204,7 +225,7 @@ namespace bitsery {
if (_nextSessionIt != std::end(_sessions)) {
if (std::distance(_pos, _end) > 0) {
//set end position for new session
auto newEnd = std::next(_bufBegin, *_nextSessionIt);
auto newEnd = std::next(_begin, *_nextSessionIt);
if (std::distance(newEnd, _end) < 0)
{
//new session cannot end further than current end
@@ -234,7 +255,7 @@ namespace bitsery {
auto dist = std::distance(_pos, _end);
if (dist > 0) {
//newer version might have some inner sessions, try to find the one after current ends
auto currPos = static_cast<size_t>(std::distance(_bufBegin, _end));
auto currPos = static_cast<size_t>(std::distance(_begin, _end));
for (; _nextSessionIt != std::end(_sessions); ++_nextSessionIt) {
if (*_nextSessionIt > currPos)
break;
@@ -253,6 +274,7 @@ namespace bitsery {
private:
TReader& _reader;
TIterator _begin;
TIterator& _pos;
TIterator& _end;
@@ -263,9 +285,8 @@ namespace bitsery {
void initializeSessions() {
//save current position
auto currPos = _pos;
auto bufferSizeLeft = std::distance(_pos, _end);
//read size
if (bufferSizeLeft < 2) {
if (std::distance(_pos, _end) < 2) {
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
return;
}
@@ -277,12 +298,12 @@ namespace bitsery {
if (high >= 0x8000u) {
if (bufferSizeLeft < 4) {
endSessionsSizesIt = std::next(endSessionsSizesIt, -2);
_pos = endSessionsSizesIt;
if (std::distance(_begin, _pos) < 0) {
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
return;
}
endSessionsSizesIt = std::next(endSessionsSizesIt, -2);
_pos = endSessionsSizesIt;
uint16_t low;
_reader.template readBytes<2>(low);
//mask out last bit
@@ -291,7 +312,9 @@ namespace bitsery {
} else
sessionsOffset = high;
if (static_cast<size_t>(bufferSizeLeft) < sessionsOffset) {
auto bufferSize = std::distance(_begin, _end);
if (static_cast<size_t>(bufferSize) < sessionsOffset) {
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
return;
}
@@ -301,7 +324,7 @@ namespace bitsery {
_pos = std::next(_end, -sessionsOffset);
while (std::distance(_pos, endSessionsSizesIt) > 0) {
size_t size;
details::readSize(_reader, size);
details::readSize(_reader, size, bufferSize);
*sessionsIt++ = size;
}
_sessions.shrink_to_fit();
@@ -312,17 +335,19 @@ namespace bitsery {
}
};
//this class writes bytes and bits to underlying buffer, it has specializations for resizable and non-resizable buffers
template<typename Buffer, bool isResizable>
class WriteBufferContext {
};
template<typename Buffer>
class WriteBufferContext<Buffer, false>{
class WriteBufferContext<Buffer, false> {
public:
using ValueType = typename BufferContainerTraits<Buffer>::TValue;
using IteratorType = typename BufferContainerTraits<Buffer>::TIterator;
using DifferenceType = typename BufferContainerTraits<Buffer>::TDifference;
using TValue = typename BufferContainerTraits<Buffer>::TValue;
using TIterator = typename BufferContainerTraits<Buffer>::TIterator;
using TDifference = typename BufferContainerTraits<Buffer>::TDifference;
explicit WriteBufferContext(Buffer &buffer)
: _buffer{buffer},
@@ -331,21 +356,21 @@ namespace bitsery {
{
}
void write(const ValueType *data, size_t size) {
assert(std::distance(_outIt, _end) >= static_cast<DifferenceType>(size));
void write(const TValue *data, size_t size) {
assert(std::distance(_outIt, _end) >= static_cast<TDifference>(size));
memcpy(_outIt, data, size);
_outIt += size;
}
BufferRange<IteratorType> getWrittenRange() const {
BufferRange<TIterator> getWrittenRange() const {
auto begin = std::begin(_buffer);
return BufferRange<IteratorType>{begin, std::next(begin, _outIt - std::addressof(*begin))};
return BufferRange<TIterator>{begin, std::next(begin, _outIt - std::addressof(*begin))};
}
private:
Buffer &_buffer;
ValueType* _outIt;
ValueType* _end;
TValue* _outIt;
TValue* _end;
};
template<typename Buffer>

View File

@@ -24,7 +24,6 @@
#define BITSERY_DETAILS_SERIALIZATION_COMMON_H
#include <type_traits>
#include <array>
#include "both_common.h"
namespace bitsery {
@@ -49,153 +48,6 @@ namespace bitsery {
template<typename T>
using SAME_SIZE_UNSIGNED = typename SAME_SIZE_UNSIGNED_TYPE<T>::type;
template<typename T>
constexpr size_t getSize(T v, size_t s) {
return v > 0 ? getSize(v / 2, s + 1) : s;
}
template<typename T>
constexpr size_t calcRequiredBits(T min, T max) {
//call recursive function, because some compilers only support constexpr functions with return-only body
return getSize(max - min, 0);
}
}
/*
* range functions in bitsery namespace because these are used by user
*/
template<typename T, typename Enable = void>
struct RangeSpec {
constexpr RangeSpec(T minValue, T maxValue)
: min{minValue},
max{maxValue},
bitsRequired{details::calcRequiredBits(min, max)} {
}
const T min;
const T max;
const size_t bitsRequired;
};
template<typename T>
struct RangeSpec<T, typename std::enable_if<std::is_enum<T>::value>::type> {
constexpr RangeSpec(T minValue, T maxValue) :
min{minValue},
max{maxValue},
bitsRequired{details::calcRequiredBits(
static_cast<typename std::underlying_type<T>::type>(min),
static_cast<typename std::underlying_type<T>::type>(max))} {
}
const T min;
const T max;
const size_t bitsRequired;
};
//this class is used to make default RangeSpec float specialization always prefer constructor with precision
struct BitsConstraint {
explicit constexpr BitsConstraint(size_t bits) : value{bits} {}
const size_t value;
};
template<typename T>
struct RangeSpec<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
constexpr RangeSpec(T minValue, T maxValue, BitsConstraint bits) :
min{minValue},
max{maxValue},
bitsRequired{bits.value} {
}
constexpr RangeSpec(T minValue, T maxValue, T precision) :
min{minValue},
max{maxValue},
bitsRequired{details::calcRequiredBits<details::SAME_SIZE_UNSIGNED<T>>({}, ((max - min) / precision))} {
}
const T min;
const T max;
const size_t bitsRequired;
};
namespace details {
/*
* functions for range
*/
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
SAME_SIZE_UNSIGNED<T> getRangeValue(const T &v, const RangeSpec<T> &r) {
return static_cast<SAME_SIZE_UNSIGNED<T>>(v - r.min);
};
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
SAME_SIZE_UNSIGNED<T> getRangeValue(const T &v, const RangeSpec<T> &r) {
using VT = SAME_SIZE_UNSIGNED<T>;
return static_cast<VT>(static_cast<VT>(v) - static_cast<VT>(r.min));
};
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
SAME_SIZE_UNSIGNED<T> getRangeValue(const T &v, const RangeSpec<T> &r) {
using VT = SAME_SIZE_UNSIGNED<T>;
const VT maxUint = (static_cast<VT>(1) << r.bitsRequired) - 1;
const auto ratio = (v - r.min) / (r.max - r.min);
return static_cast<VT>(ratio * maxUint);
};
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
void setRangeValue(T &v, const RangeSpec<T> &r) {
v += r.min;
};
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
void setRangeValue(T &v, const RangeSpec<T> &r) {
using VT = typename std::underlying_type<T>::type;
reinterpret_cast<VT &>(v) += static_cast<VT>(r.min);
};
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
void setRangeValue(T &v, const RangeSpec<T> &r) {
using UIT = SAME_SIZE_UNSIGNED<T>;
const auto intRep = reinterpret_cast<UIT &>(v);
const UIT maxUint = (static_cast<UIT>(1) << r.bitsRequired) - 1;
v = r.min + (static_cast<T>(intRep) / maxUint) * (r.max - r.min);
};
template<typename T, typename std::enable_if<std::is_arithmetic<T>::value>::type * = nullptr>
bool isRangeValid(const T &v, const RangeSpec<T> &r) {
return !(r.min > v || v > r.max);
}
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
bool isRangeValid(const T &v, const RangeSpec<T> &r) {
using VT = typename std::underlying_type<T>::type;
return !(static_cast<VT>(r.min) > static_cast<VT>(v)
|| static_cast<VT>(v) > static_cast<VT>(r.max));
}
/*
* functions for entropy encoding
*/
template<typename T, size_t N>
size_t findEntropyIndex(const T &v, const T (&defValues)[N]) {
auto index{1u};
for (auto &d:defValues) {
if (d == v)
return index;
++index;
}
return 0u;
};
/*
* functions for object serialization
*/
@@ -222,21 +74,10 @@ namespace bitsery {
}
};
/**
* used to check if extension supports overloads with `object` and `value<N>`
*/
template <typename T, typename Enable = void>
struct HasTValue:public std::false_type {
//used for extensions, when extension TValue = void
struct DummyType {
};
template <typename T>
struct HasTValue<T, typename std::enable_if<
//only works when TValue is defined, and is not void
!std::is_same<void, typename T::TValue>::value
>::type>: public std::true_type {
};
/*
* delta functions
*/

View File

@@ -48,14 +48,23 @@ namespace bitsery {
//this type is used, when using extesion without custom lambda
// eg.: extension4b>(obj, myextension{}) will call s.value4b(obj) for TValue
// or extesion(obj, myextension{}) will call s.object(obj) for TValue
//if this is not defined, then these functions are disabled
//when this is void, it will compile, but value and object overloads will do nothing.
using TValue = void;
//does extension support ext<N>(...) syntax, by calling value<N> with TValue
static constexpr bool SupportValueOverload = true;
//does extension support ext(...) syntax, by calling object with TValue
static constexpr bool SupportObjectOverload = true;
//does extension support ext(..., lambda)
static constexpr bool SupportLambdaOverload = true;
};
//traits for containers
//primary traits for containers
template<typename T>
struct ContainerTraits {
using TValue = typename T::value_type;
//default behaviour is resizable if container has method T::resize(size_t)
static constexpr bool isResizable = IsResizable<T>::value;
@@ -71,6 +80,19 @@ namespace bitsery {
};
//specialization for C style array
template<typename T, size_t N>
struct ContainerTraits<T[N]> {
using TValue = T;
static constexpr bool isResizable = IsResizable<T>::value;
static void resize(T (&container)[N], size_t size) {
}
static size_t size(const T (&container)[N]) {
return N;
}
};
//traits for text
template<typename T>
struct TextTraits {
@@ -133,7 +155,6 @@ namespace bitsery {
container.resize(container.capacity());
}
using TValue = typename T::value_type;
using TDifference = typename T::difference_type;
using TIterator = typename T::iterator;
};

View File

@@ -26,27 +26,30 @@
namespace bitsery {
namespace ext {
class containerMap {
class ContainerMap {
public:
constexpr explicit ContainerMap(size_t maxSize):_maxSize{maxSize} {}
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &, Writer &writer, const T &obj, Fnc &&fnc) const {
using TKey = typename T::key_type;
using TValue = typename T::mapped_type;
auto size = obj.size();
assert(size <= _maxSize);
details::writeSize(writer, size);
details::writeSize(writer, obj.size());
for (auto& v:obj)
fnc(const_cast<TKey&>(v.first), const_cast<TValue&>(v.second));
for (auto &v:obj)
fnc(const_cast<TKey &>(v.first), const_cast<TValue &>(v.second));
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &, Reader& reader, T &obj, Fnc &&fnc) const {
void deserialize(Des &, Reader &reader, T &obj, Fnc &&fnc) const {
using TKey = typename T::key_type;
using TValue = typename T::mapped_type;
size_t size{};
details::readSize(reader, size);
details::readSize(reader, size, _maxSize);
auto hint = obj.begin();
obj.clear();
@@ -57,13 +60,18 @@ namespace bitsery {
hint = obj.emplace_hint(hint, std::move(key), std::move(value));
}
}
private:
size_t _maxSize;
};
}
namespace details {
template <typename T>
struct ExtensionTraits<ext::containerMap, T> {
//do not define TValue, because we dont have default behaviour for this key+value
template<typename T>
struct ExtensionTraits<ext::ContainerMap, T> {
using TValue = void;
static constexpr bool SupportValueOverload = false;
static constexpr bool SupportObjectOverload = false;
static constexpr bool SupportLambdaOverload = true;
};
}

View File

@@ -0,0 +1,91 @@
//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.
#ifndef BITSERY_EXT_ENTROPY_H
#define BITSERY_EXT_ENTROPY_H
#include "value_range.h"
namespace bitsery {
namespace details {
template<typename TValue, typename TContainer>
size_t findEntropyIndex(const TValue &v, const TContainer &defValues) {
size_t index{1u};
for (auto &d:defValues) {
if (d == v)
return index;
++index;
}
return 0u;
};
}
namespace ext {
template<typename TContainer>
class Entropy {
public:
constexpr explicit Entropy(TContainer& values) : _values{values} {
};
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &s, Writer &writer, const T &obj, Fnc &&fnc) const {
assert(details::ContainerTraits<TContainer>::size(_values) > 0);
auto index = details::findEntropyIndex(obj, _values);
s.ext(index, ext::ValueRange<size_t>{0u, details::ContainerTraits<TContainer>::size(_values)});
if (!index)
fnc(const_cast<T &>(obj));
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &d, Reader &reader, T &obj, Fnc &&fnc) const {
assert(details::ContainerTraits<TContainer>::size(_values) > 0);
size_t index{};
d.ext(index, ext::ValueRange<size_t>{0u, details::ContainerTraits<TContainer>::size(_values)});
if (index)
obj = *std::next(std::begin(_values), index-1);
else
fnc(obj);
}
private:
TContainer& _values;
};
}
namespace details {
template<typename TContainer, typename T>
struct ExtensionTraits<ext::Entropy<TContainer>, T> {
using TValue = T;
static constexpr bool SupportValueOverload = true;
static constexpr bool SupportObjectOverload = true;
static constexpr bool SupportLambdaOverload = true;
};
}
}
#endif //BITSERY_EXT_ENTROPY_H

View File

@@ -0,0 +1,65 @@
//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.
#ifndef BITSERY_EXT_GROWABLE_H
#define BITSERY_EXT_GROWABLE_H
namespace bitsery {
namespace ext {
/*
* enables to add additional serialization methods at the end of method, without breaking existing older code
*/
class Growable {
public:
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &s, Writer &writer, const T &obj, Fnc &&fnc) const {
writer.beginSession();
fnc(const_cast<T&>(obj));
writer.endSession();
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &d, Reader &reader, T &obj, Fnc &&fnc) const {
reader.beginSession();
fnc(obj);
reader.endSession();
}
};
}
namespace details {
template<typename T>
struct ExtensionTraits<ext::Growable, T> {
using TValue = T;
static constexpr bool SupportValueOverload = false;
static constexpr bool SupportObjectOverload = true;
static constexpr bool SupportLambdaOverload = true;
};
}
}
#endif //BITSERY_EXT_GROWABLE_H

View File

@@ -40,7 +40,7 @@ namespace bitsery {
template<typename T>
using std_optional = ::std::optional<T>;
class optional {
class Optional {
public:
template<typename T>
@@ -56,11 +56,11 @@ namespace bitsery {
assertType<T>();
ser.boolByte(static_cast<bool>(obj));
if (obj)
fnc(const_cast<typename T::value_type& >(*obj));
fnc(const_cast<typename T::value_type & >(*obj));
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &des, Reader& , T &obj, Fnc &&fnc) const {
void deserialize(Des &des, Reader &, T &obj, Fnc &&fnc) const {
assertType<T>();
bool exists{};
des.boolByte(exists);
@@ -77,9 +77,12 @@ namespace bitsery {
}
namespace details {
template <typename T>
struct ExtensionTraits<ext::optional, T> {
template<typename T>
struct ExtensionTraits<ext::Optional, T> {
using TValue = typename T::value_type;
static constexpr bool SupportValueOverload = true;
static constexpr bool SupportObjectOverload = true;
static constexpr bool SupportLambdaOverload = true;
};
}

View File

@@ -0,0 +1,206 @@
//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.
#ifndef BITSERY_EXT_VALUE_RANGE_H
#define BITSERY_EXT_VALUE_RANGE_H
#include "../details/serialization_common.h"
#include "../details/buffer_common.h"
#include <cassert>
namespace bitsery {
namespace ext {
//this class is used to make default RangeSpec float specialization always prefer constructor with precision
struct BitsConstraint {
explicit constexpr BitsConstraint(size_t bits) : value{bits} {}
const size_t value;
};
}
//implementation details for range functionality
namespace details {
template<typename T>
constexpr size_t getSize(T v, size_t s) {
return v > 0 ? getSize(v / 2, s + 1) : s;
}
template<typename T>
constexpr size_t calcRequiredBits(T min, T max) {
//call recursive function, because some compilers only support constexpr functions with return-only body
return getSize(max - min, 0);
}
template<typename T, typename Enable = void>
struct RangeSpec {
constexpr RangeSpec(T minValue, T maxValue)
: min{minValue},
max{maxValue},
bitsRequired{calcRequiredBits(min, max)} {
}
const T min;
const T max;
const size_t bitsRequired;
};
template<typename T>
struct RangeSpec<T, typename std::enable_if<std::is_enum<T>::value>::type> {
constexpr RangeSpec(T minValue, T maxValue) :
min{minValue},
max{maxValue},
bitsRequired{calcRequiredBits(
static_cast<typename std::underlying_type<T>::type>(min),
static_cast<typename std::underlying_type<T>::type>(max))} {
}
const T min;
const T max;
const size_t bitsRequired;
};
template<typename T>
struct RangeSpec<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
constexpr RangeSpec(T minValue, T maxValue, ext::BitsConstraint bits) :
min{minValue},
max{maxValue},
bitsRequired{bits.value} {
}
constexpr RangeSpec(T minValue, T maxValue, T precision) :
min{minValue},
max{maxValue},
bitsRequired{calcRequiredBits<details::SAME_SIZE_UNSIGNED<T>>({}, ((max - min) / precision))} {
}
const T min;
const T max;
const size_t bitsRequired;
};
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
details::SAME_SIZE_UNSIGNED<T> getRangeValue(const T &v, const RangeSpec<T> &r) {
return static_cast<details::SAME_SIZE_UNSIGNED<T>>(v - r.min);
};
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
details::SAME_SIZE_UNSIGNED<T> getRangeValue(const T &v, const RangeSpec<T> &r) {
using VT = details::SAME_SIZE_UNSIGNED<T>;
return static_cast<VT>(static_cast<VT>(v) - static_cast<VT>(r.min));
};
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
details::SAME_SIZE_UNSIGNED<T> getRangeValue(const T &v, const RangeSpec<T> &r) {
using VT = details::SAME_SIZE_UNSIGNED<T>;
const VT maxUint = (static_cast<VT>(1) << r.bitsRequired) - 1;
const auto ratio = (v - r.min) / (r.max - r.min);
return static_cast<VT>(ratio * maxUint);
};
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
void setRangeValue(T &v, const RangeSpec<T> &r) {
v += r.min;
};
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
void setRangeValue(T &v, const RangeSpec<T> &r) {
using VT = typename std::underlying_type<T>::type;
reinterpret_cast<VT &>(v) += static_cast<VT>(r.min);
};
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
void setRangeValue(T &v, const RangeSpec<T> &r) {
using UIT = details::SAME_SIZE_UNSIGNED<T>;
const auto intRep = reinterpret_cast<UIT &>(v);
const UIT maxUint = (static_cast<UIT>(1) << r.bitsRequired) - 1;
v = r.min + (static_cast<T>(intRep) / maxUint) * (r.max - r.min);
};
template<typename T, typename std::enable_if<std::is_arithmetic<T>::value>::type * = nullptr>
bool isRangeValid(const T &v, const RangeSpec<T> &r) {
return !(r.min > v || v > r.max);
}
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
bool isRangeValid(const T &v, const RangeSpec<T> &r) {
using VT = typename std::underlying_type<T>::type;
return !(static_cast<VT>(r.min) > static_cast<VT>(v)
|| static_cast<VT>(v) > static_cast<VT>(r.max));
}
}
namespace ext {
template<typename TValue>
class ValueRange {
public:
template<typename ... Args>
explicit constexpr ValueRange(Args &&... args):_range{std::forward<Args>(args)...} {};
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &, Writer &writer, const T &v, Fnc &&) const {
assert(details::isRangeValid(v, _range));
using BT = decltype(details::getRangeValue(v, _range));
writer.template writeBits<BT>(details::getRangeValue(v, _range), _range.bitsRequired);
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &, Reader &reader, T &v, Fnc &&) const {
reader.readBits(reinterpret_cast<details::SAME_SIZE_UNSIGNED<T> &>(v), _range.bitsRequired);
details::setRangeValue(v, _range);
if (!details::isRangeValid(v, _range)) {
reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
v = _range.min;
}
}
constexpr size_t getRequiredBits() const {
return _range.bitsRequired;
};
private:
details::RangeSpec<TValue> _range;
};
}
namespace details {
template<typename T>
struct ExtensionTraits<ext::ValueRange<T>, T> {
using TValue = void;
static constexpr bool SupportValueOverload = false;
static constexpr bool SupportObjectOverload = true;
static constexpr bool SupportLambdaOverload = false;
};
}
}
#endif //BITSERY_EXT_VALUE_RANGE_H

View File

@@ -27,7 +27,6 @@
#include "common.h"
#include "details/serialization_common.h"
#include <cassert>
#include <array>
namespace bitsery {
@@ -81,37 +80,35 @@ namespace bitsery {
}
/*
* growable function
*/
template<typename T, typename Fnc>
void growable(const T &obj, Fnc &&fnc) {
_writter.beginSession();
fnc(const_cast<T&>(obj));
_writter.endSession();
};
/*
* extend functions
* extension functions
*/
template<typename T, typename Ext, typename Fnc>
void extend(const T &obj, Ext &&ext, Fnc &&fnc) {
ext.serialize(*this, _writter, obj, std::forward<Fnc>(fnc));
void ext(const T &obj, Ext &&extension, Fnc &&fnc) {
using ExtType = typename std::decay<Ext>::type;
static_assert(details::ExtensionTraits<ExtType,T>::SupportLambdaOverload,
"extension doesn't support overload with lambda");
extension.serialize(*this, _writter, obj, std::forward<Fnc>(fnc));
};
template<size_t VSIZE, typename T, typename Ext>
void extend(const T &obj, Ext &&ext) {
static_assert(details::HasTValue<details::ExtensionTraits<Ext, T>>::value,
"this extension only supports overload with lambda");
ext.serialize(*this, _writter, obj, [this](typename details::ExtensionTraits<Ext, T>::TValue &v) { value<VSIZE>(v); });
void ext(const T &obj, Ext &&extension) {
using ExtType = typename std::decay<Ext>::type;
static_assert(details::ExtensionTraits<ExtType,T>::SupportValueOverload,
"extension doesn't support overload with `value<N>`");
using ExtVType = typename details::ExtensionTraits<ExtType, T>::TValue;
using VType = typename std::conditional<std::is_void<ExtVType>::value, details::DummyType, ExtVType>::type;
extension.serialize(*this, _writter, obj, [this](VType &v) { value<VSIZE>(v); });
};
template<typename T, typename Ext>
void extend(const T &obj, Ext &&ext) {
static_assert(details::HasTValue<details::ExtensionTraits<Ext, T>>::value,
"this extension only supports overload with lambda");
ext.serialize(*this, _writter, obj, [this](typename details::ExtensionTraits<Ext, T>::TValue &v) { object(v); });
void ext(const T &obj, Ext &&extension) {
using ExtType = typename std::decay<Ext>::type;
static_assert(details::ExtensionTraits<ExtType,T>::SupportObjectOverload,
"extension doesn't support overload with `object`");
using ExtVType = typename details::ExtensionTraits<ExtType, T>::TValue;
using VType = typename std::conditional<std::is_void<ExtVType>::value, details::DummyType, ExtVType>::type;
extension.serialize(*this, _writter, obj, [this](VType &v) { object(v); });
};
/*
@@ -126,44 +123,6 @@ namespace bitsery {
_writter.template writeBytes<1>(static_cast<unsigned char>(v ? 1 : 0));
}
/*
* range
*/
template<typename T>
void range(const T &v, const RangeSpec<T> &range) {
assert(details::isRangeValid(v, range));
using BT = decltype(details::getRangeValue(v, range));
_writter.template writeBits<BT>(details::getRangeValue(v, range), range.bitsRequired);
}
/*
* entropy overloads
*/
template<typename T, size_t N, typename Fnc>
void entropy(const T &obj, const T (&expectedValues)[N], Fnc &&fnc) {
auto index = details::findEntropyIndex(obj, expectedValues);
range(index, {{}, N + 1});
if (!index)
fnc(const_cast<T&>(obj));
};
template<size_t VSIZE, typename T, size_t N>
void entropy(const T &v, const T (&expectedValues)[N]) {
auto index = details::findEntropyIndex(v, expectedValues);
range(index, {{}, N + 1});
if (!index)
value<VSIZE>(v);
};
template<typename T, size_t N>
void entropy(const T &obj, const T (&expectedValues)[N]) {
auto index = details::findEntropyIndex(obj, expectedValues);
range(index, {{}, N + 1});
if (!index)
object(obj);
};
/*
* text overloads
*/
@@ -304,36 +263,16 @@ namespace bitsery {
void value8b(T &&v) { value<8>(std::forward<T>(v)); }
template<typename T, typename Ext>
void extend1b(const T &v, Ext &&ext) { extend<1>(v, std::forward<Ext>(ext)); };
void ext1b(const T &v, Ext &&extension) { ext<1, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T, typename Ext>
void extend2b(const T &v, Ext &&ext) { extend<2>(v, std::forward<Ext>(ext)); };
void ext2b(const T &v, Ext &&extension) { ext<2, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T, typename Ext>
void extend4b(const T &v, Ext &&ext) { extend<4>(v, std::forward<Ext>(ext)); };
void ext4b(const T &v, Ext &&extension) { ext<4, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T, typename Ext>
void extend8b(const T &v, Ext &&ext) { extend<8>(v, std::forward<Ext>(ext)); };
template<typename T, size_t N>
void entropy1b(const T &v, const T (&expectedValues)[N]) {
entropy<1, T, N>(v, expectedValues);
};
template<typename T, size_t N>
void entropy2b(const T &v, const T (&expectedValues)[N]) {
entropy<2, T, N>(v, expectedValues);
};
template<typename T, size_t N>
void entropy4b(const T &v, const T (&expectedValues)[N]) {
entropy<4, T, N>(v, expectedValues);
};
template<typename T, size_t N>
void entropy8b(const T &v, const T (&expectedValues)[N]) {
entropy<8, T, N>(v, expectedValues);
};
void ext8b(const T &v, Ext &&extension) { ext<8, T, Ext>(v, std::forward<Ext>(extension)); };
template<typename T>
void text1b(const T &str, size_t maxSize) { text<1>(str, maxSize); }
@@ -424,6 +363,16 @@ namespace bitsery {
object(*first);
};
//these are dummy functions for extensions that have TValue = void
void object(const details::DummyType&) {
}
template <size_t VSIZE>
void value(const details::DummyType&) {
}
};
//helper type

View File

@@ -24,7 +24,7 @@
#include <gmock/gmock.h>
#include <bitsery/buffer_writer.h>
#include <bitsery/buffer_reader.h>
#include <bitsery/details/serialization_common.h>
#include <bitsery/ext/value_range.h>
using testing::Eq;
using testing::ContainerEq;
@@ -38,9 +38,8 @@ constexpr EndiannessType getInverseEndianness(EndiannessType e) {
: EndiannessType::LittleEndian;
}
struct InverseEndiannessConfig {
struct InverseEndiannessConfig:public DefaultConfig {
static constexpr bitsery::EndiannessType NetworkEndianness = getInverseEndianness(DefaultConfig::NetworkEndianness);
using BufferType = DefaultConfig::BufferType;
};
struct IntegralTypes {

View File

@@ -24,7 +24,7 @@
#include <gmock/gmock.h>
#include <bitsery/buffer_writer.h>
#include <bitsery/buffer_reader.h>
#include <bitsery/details/serialization_common.h>
#include <bitsery/ext/value_range.h>
using testing::Eq;
using testing::ContainerEq;

View File

@@ -31,13 +31,11 @@ using bitsery::EndiannessType;
using bitsery::DefaultConfig;
using Buffer = bitsery::DefaultConfig::BufferType;
struct FixedBufferConfig {
static constexpr bitsery::EndiannessType NetworkEndianness = DefaultConfig::NetworkEndianness;
struct FixedBufferConfig: public DefaultConfig {
using BufferType = std::array<uint8_t, 100>;
};
struct NonFixedBufferConfig {
static constexpr bitsery::EndiannessType NetworkEndianness = DefaultConfig::NetworkEndianness;
struct NonFixedBufferConfig: public DefaultConfig {
using BufferType = std::vector<uint8_t>;
};

View File

@@ -23,11 +23,12 @@
#include <gmock/gmock.h>
#include "serialization_test_utils.h"
#include <algorithm>
#include <numeric>
#include <deque>
#include <list>
#include "serialization_test_utils.h"
using testing::ContainerEq;
using testing::Eq;

View File

@@ -1,135 +0,0 @@
//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 <gmock/gmock.h>
#include "serialization_test_utils.h"
using namespace testing;
TEST(SerializeEntropyEncoding, WhenEntropyEncodedThenOnlyWriteIndexUsingMinRequiredBits) {
int32_t v = 4849;
int32_t res;
constexpr size_t N = 3;
int32_t entropyValues[3]{485,4849,89};
SerializationContext ctx;
ctx.createSerializer().entropy<4>(v, entropyValues);
ctx.createDeserializer().entropy<4>(res, entropyValues);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
SerializationContext ctx1;
ctx1.createSerializer().entropy<4>(v, entropyValues);
auto des = ctx1.createDeserializer();
des.range(res, {0, N + 1});
EXPECT_THAT(res, Eq(2));
}
TEST(SerializeEntropyEncoding, WhenNoEntropyEncodedThenWriteZeroBitsAndValueOrObject) {
int16_t v = 8945;
int16_t res;
int16_t entropyValues[3]{485,4849,89};
SerializationContext ctx;
ctx.createSerializer().entropy<2>(v, entropyValues);
ctx.createDeserializer().entropy<2>(res, entropyValues);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(sizeof(int16_t)+1));
}
TEST(SerializeEntropyEncoding, CustomTypeEntropyEncoded) {
MyStruct1 v = {12,10};
MyStruct1 res;
constexpr size_t N = 4;
MyStruct1 entropyValues[N]{
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
SerializationContext ctx;
ctx.createSerializer().entropy(v, entropyValues);
ctx.createDeserializer().entropy(res, entropyValues);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
}
TEST(SerializeEntropyEncoding, CustomTypeNotEntropyEncoded) {
MyStruct1 v = {8945,4456};
MyStruct1 res;
constexpr size_t N = 4;
MyStruct1 entropyValues[N] {
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
SerializationContext ctx;
ctx.createSerializer().entropy(v, entropyValues);
ctx.createDeserializer().entropy(res, entropyValues);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(MyStruct1::SIZE + 1));
}
TEST(SerializeEntropyEncoding, CustomFunctionNotEntropyEncoded) {
MyStruct1 v = {8945,4456};
MyStruct1 res;
constexpr size_t N = 4;
MyStruct1 entropyValues[N] {
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
auto rangeForValue = bitsery::RangeSpec<int>(0, 10000);
auto rangeForIndex = bitsery::RangeSpec<size_t>{0, N+1};
SerializationContext ctx;
auto ser = ctx.createSerializer();
//lambdas differ only in capture clauses, it would make sense to use std::bind, but debugger crashes when it sees std::bind...
auto serLambda = [&ser, rangeForValue](MyStruct1& v) {
ser.range(v.i1, rangeForValue);
ser.range(v.i2, rangeForValue);
};
ser.entropy(v, entropyValues, serLambda);
auto des = ctx.createDeserializer();
auto desLambda = [&des, rangeForValue](MyStruct1& v) {
des.range(v.i1, rangeForValue);
des.range(v.i2, rangeForValue);
};
des.entropy(res, entropyValues, desLambda);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq((rangeForIndex.bitsRequired + rangeForValue.bitsRequired * 2 - 1) / 8 + 1 ));
}
TEST(SerializeEntropyEncoding, WhenEntropyEncodedThenCustomFunctionNotInvoked) {
MyStruct1 v = {4849,89};
MyStruct1 res;
constexpr size_t N = 4;
MyStruct1 entropyValues[N] {
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
SerializationContext ctx;
ctx.createSerializer().entropy(v, entropyValues, [](MyStruct1& ) {});
ctx.createDeserializer().entropy(res, entropyValues, []( MyStruct1& ) {});
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
}

View File

@@ -24,10 +24,11 @@
#include "serialization_test_utils.h"
#include <bitsery/ext/container_map.h>
#include <bitsery/ext/entropy.h>
#include <unordered_map>
#include <map>
using containerMap = bitsery::ext::containerMap;
using ContainerMap = bitsery::ext::ContainerMap;
using testing::Eq;
@@ -96,7 +97,7 @@ namespace bitsery {
template <typename S>
void serialize(S& s, std::unordered_map<std::string, MyStruct1>& o) {
s.extend(o, containerMap{}, [&s](std::string& key, MyStruct1& value) {
s.ext(o, ContainerMap{10}, [&s](std::string& key, MyStruct1& value) {
s.text1b(key, 100);
s.object(value);
});
@@ -104,7 +105,7 @@ namespace bitsery {
template <typename S>
void serialize(S& s, std::unordered_map<int32_t, float>& o) {
s.extend(o, containerMap{}, [&s](int32_t& key, float& value) {
s.ext(o, ContainerMap{10}, [&s](int32_t& key, float& value) {
s.value4b(key);
s.value4b(value);
});
@@ -112,7 +113,7 @@ namespace bitsery {
template <typename S>
void serialize(S& s, std::map<MyEnumClass , MyStruct1>& o) {
s.extend(o, containerMap{}, [&s](MyEnumClass& key, MyStruct1& value) {
s.ext(o, ContainerMap{10}, [&s](MyEnumClass& key, MyStruct1& value) {
s.value4b(key);
s.object(value);
});
@@ -120,10 +121,11 @@ namespace bitsery {
template <typename S>
void serialize(S& s, std::map<int32_t ,int64_t>& o) {
s.extend(o, containerMap{}, [&s](int32_t& key, int64_t& value) {
s.range(key, bitsery::RangeSpec<int32_t>{-100,100});
constexpr int64_t ev[3]{1ll, 2ll, 3ll};
s.entropy8b(value, ev);
s.ext(o, ContainerMap{10}, [&s](int32_t& key, int64_t& value) {
int64_t values[3]{1ll, 2ll, 3ll};
s.ext(key, bitsery::ext::ValueRange<int32_t>{-100,100});
s.ext8b(value, bitsery::ext::Entropy<int64_t[3]>{values});
});
}

View File

@@ -0,0 +1,143 @@
//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 <gmock/gmock.h>
#include "serialization_test_utils.h"
#include <bitsery/ext/entropy.h>
#include <vector>
#include <list>
using namespace testing;
using bitsery::ext::Entropy;
TEST(SerializeExtensionEntropy, WhenEntropyEncodedThenOnlyWriteIndexUsingMinRequiredBits) {
int32_t v = 4849;
int32_t res;
constexpr size_t N = 3;
int32_t values[3] = {485,4849,89};
SerializationContext ctx;
ctx.createSerializer().ext4b(v, Entropy<int32_t[3]>{values});
ctx.createDeserializer().ext4b(res, Entropy<int32_t[3]>{values});
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
SerializationContext ctx1;
ctx1.createSerializer().ext4b(v, Entropy<int32_t[3]>{values});
auto des = ctx1.createDeserializer();
des.ext(res, bitsery::ext::ValueRange<int32_t>{0, static_cast<int32_t>(N + 1)});
EXPECT_THAT(res, Eq(2));
}
TEST(SerializeExtensionEntropy, WhenNoEntropyEncodedThenWriteZeroBitsAndValueOrObject) {
int16_t v = 8945;
int16_t res;
std::initializer_list<int> values{485,4849,89};
SerializationContext ctx;
ctx.createSerializer().ext2b(v, Entropy<std::initializer_list<int>>{values});
ctx.createDeserializer().ext2b(res, Entropy<std::initializer_list<int>>{values});
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(sizeof(int16_t)+1));
}
TEST(SerializeExtensionEntropy, CustomTypeEntropyEncoded) {
MyStruct1 v = {12,10};
MyStruct1 res;
constexpr size_t N = 4;
MyStruct1 values[N]{
MyStruct1{12, 10}, MyStruct1{485, 454},
MyStruct1{4849, 89}, MyStruct1{0, 1}};
SerializationContext ctx;
ctx.createSerializer().ext(v, Entropy<MyStruct1[N]>{values});
ctx.createDeserializer().ext(res, Entropy<MyStruct1[N]>{values});
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
}
TEST(SerializeExtensionEntropy, CustomTypeNotEntropyEncoded) {
MyStruct1 v = {8945,4456};
MyStruct1 res;
std::initializer_list<MyStruct1> values {
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
SerializationContext ctx;
ctx.createSerializer().ext(v, Entropy<std::initializer_list<MyStruct1>>{values});
ctx.createDeserializer().ext(res, Entropy<std::initializer_list<MyStruct1>>{values});
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(MyStruct1::SIZE + 1));
}
TEST(SerializeExtensionEntropy, CustomFunctionNotEntropyEncoded) {
MyStruct1 v = {8945,4456};
MyStruct1 res;
constexpr size_t N = 4;
std::vector<MyStruct1> values{
MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
auto rangeForValue = bitsery::ext::ValueRange<int>{0, 10000};
auto rangeForIndex = bitsery::ext::ValueRange<size_t>{0u, N+1};
SerializationContext ctx;
auto ser = ctx.createSerializer();
//lambdas differ only in capture clauses, it would make sense to use std::bind, but debugger crashes when it sees std::bind...
auto serLambda = [&ser, &rangeForValue](MyStruct1& v) {
ser.ext(v.i1, rangeForValue);
ser.ext(v.i2, rangeForValue);
};
ser.ext(v, Entropy<std::vector<MyStruct1>>(values), serLambda);
auto des = ctx.createDeserializer();
auto desLambda = [&des, &rangeForValue](MyStruct1& v) {
des.ext(v.i1, rangeForValue);
des.ext(v.i2, rangeForValue);
};
des.ext(res, Entropy<std::vector<MyStruct1>>(values), desLambda);
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq((rangeForIndex.getRequiredBits() + rangeForValue.getRequiredBits() * 2 - 1) / 8 + 1 ));
}
TEST(SerializeExtensionEntropy, WhenEntropyEncodedThenCustomFunctionNotInvoked) {
MyStruct1 v = {4849,89};
MyStruct1 res;
std::list<MyStruct1> values {MyStruct1{12,10}, MyStruct1{485, 454},
MyStruct1{4849,89}, MyStruct1{0,1}};
SerializationContext ctx;
ctx.createSerializer().ext(v, Entropy<std::list<MyStruct1>>{values}, [](MyStruct1& ) {});
ctx.createDeserializer().ext(res, Entropy<std::list<MyStruct1>>{values}, []( MyStruct1& ) {});
EXPECT_THAT(res, Eq(v));
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
}

View File

@@ -22,9 +22,12 @@
#include <gmock/gmock.h>
#include "serialization_test_utils.h"
#include <bitsery/ext/growable.h>
using namespace testing;
using bitsery::ext::Growable;
using Buffer = typename bitsery::DefaultConfig::BufferType;
using DiffType = typename bitsery::details::BufferContainerTraits<Buffer>::TDifference;
@@ -44,16 +47,16 @@ struct DataV3 {
};
TEST(SerializeGrowable, WriteSessionsDataAtBufferEndAfterFlush) {
TEST(SerializeExtensionGrowable, WriteSessionsDataAtBufferEndAfterFlush) {
SerializationContext ctx;
ctx.createSerializer().growable(int8_t{}, [] (int8_t& v) { });
ctx.createSerializer().ext(int8_t{}, Growable{}, [] (int8_t& v) { });
EXPECT_THAT(ctx.getBufferSize(), Eq(0));
ctx.bw->flush();
EXPECT_THAT(ctx.getBufferSize(), Gt(0));
}
TEST(SerializeGrowable, SessionDataConsistOfSessionsEndPosAnd2BytesSessionsDataOffset) {
TEST(SerializeExtensionGrowable, SessionDataConsistOfSessionsEndPosAnd2BytesSessionsDataOffset) {
SerializationContext ctx;
@@ -61,7 +64,7 @@ TEST(SerializeGrowable, SessionDataConsistOfSessionsEndPosAnd2BytesSessionsDataO
int32_t data{};
auto ser = ctx.createSerializer();
ser.growable(data, [&ser](int32_t & v) { ser.value4b(v);});
ser.ext(data, Growable{}, [&ser](int32_t & v) { ser.value4b(v);});
ctx.createDeserializer();//to flush data and create buffer reader
EXPECT_THAT(ctx.getBufferSize(), Eq(3 + DATA_SIZE));
@@ -72,7 +75,7 @@ TEST(SerializeGrowable, SessionDataConsistOfSessionsEndPosAnd2BytesSessionsDataO
size_t sessionEnd{};
//there should start session data with first size of session
bitsery::details::readSize(br, sessionEnd);
bitsery::details::readSize(br, sessionEnd, 1000000u);
EXPECT_THAT(sessionEnd, Eq(DATA_SIZE));
//this is the the offset from the end of buffer where actual data ends
uint16_t sessionsOffset{};//bufferEnd - sessionsOffset = dataEnd
@@ -83,7 +86,7 @@ TEST(SerializeGrowable, SessionDataConsistOfSessionsEndPosAnd2BytesSessionsDataO
EXPECT_THAT(dSize, Eq(DATA_SIZE));
}
TEST(SerializeGrowable, WhenNestedSessionsThenStoreEachDepthAndSize) {
TEST(SerializeExtensionGrowable, WhenNestedSessionsThenStoreEachDepthAndSize) {
SerializationContext ctx;
DataV3 data{19457,846, 498418};
ctx.createSerializer();
@@ -107,15 +110,15 @@ TEST(SerializeGrowable, WhenNestedSessionsThenStoreEachDepthAndSize) {
EXPECT_THAT(res.v3, Eq(data.v3));
size_t sessionEnd[3];
//read sessions sizes
bitsery::details::readSize(*(ctx.br), sessionEnd[0]);
bitsery::details::readSize(*(ctx.br),sessionEnd[1]);
bitsery::details::readSize(*(ctx.br), sessionEnd[2]);
bitsery::details::readSize(*(ctx.br), sessionEnd[0],10000000u);
bitsery::details::readSize(*(ctx.br), sessionEnd[1],10000000u);
bitsery::details::readSize(*(ctx.br), sessionEnd[2],10000000u);
EXPECT_THAT(sessionEnd[0], Eq(12));
EXPECT_THAT(sessionEnd[1], Eq(8));
EXPECT_THAT(sessionEnd[2], Eq(12));
}
TEST(SerializeGrowable, WhenSessionsDataIsMoreThan0x7FFFThenWrite4BytesForSessionsOffset) {
TEST(SerializeExtensionGrowable, WhenSessionsDataIsMoreThan0x7FFFThenWrite4BytesForSessionsOffset) {
SerializationContext ctx;
ctx.createSerializer();
//create more sessions that can fit in 2 bytes
@@ -136,7 +139,7 @@ TEST(SerializeGrowable, WhenSessionsDataIsMoreThan0x7FFFThenWrite4BytesForSessio
EXPECT_THAT(ctx.br->getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
}
TEST(SerializeGrowable, MultipleSessionsReadSameVersionData) {
TEST(SerializeExtensionGrowable, MultipleSessionsReadSameVersionData) {
SerializationContext ctx;
DataV2 data{8454,987451};
ctx.createSerializer();
@@ -162,7 +165,7 @@ TEST(SerializeGrowable, MultipleSessionsReadSameVersionData) {
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
}
TEST(SerializeGrowable, MultipleSessionsReadNewerVersionData) {
TEST(SerializeExtensionGrowable, MultipleSessionsReadNewerVersionData) {
SerializationContext ctx;
DataV3 data{8454,987451,54};
ctx.createSerializer();
@@ -189,7 +192,7 @@ TEST(SerializeGrowable, MultipleSessionsReadNewerVersionData) {
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(true));
}
TEST(SerializeGrowable, MultipleSessionsReadOlderVersionData) {
TEST(SerializeExtensionGrowable, MultipleSessionsReadOlderVersionData) {
SerializationContext ctx;
DataV2 data{8454,987451};
ctx.createSerializer();
@@ -217,7 +220,7 @@ TEST(SerializeGrowable, MultipleSessionsReadOlderVersionData) {
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
}
TEST(SerializeGrowable, MultipleNestedSessionsReadSameVersionData) {
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadSameVersionData) {
SerializationContext ctx;
DataV2 data{8454,987451};
ctx.createSerializer();
@@ -253,7 +256,7 @@ TEST(SerializeGrowable, MultipleNestedSessionsReadSameVersionData) {
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
}
TEST(SerializeGrowable, MultipleNestedSessionsReadOlderVersionData) {
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadOlderVersionData) {
SerializationContext ctx;
DataV2 data{8454,987451};
ctx.createSerializer();
@@ -293,7 +296,7 @@ TEST(SerializeGrowable, MultipleNestedSessionsReadOlderVersionData) {
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
}
TEST(SerializeGrowable, MultipleNestedSessionsReadNewerVersionData1) {
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadNewerVersionData1) {
SerializationContext ctx;
DataV3 data{8454,987451,54};
ctx.createSerializer();
@@ -349,7 +352,7 @@ TEST(SerializeGrowable, MultipleNestedSessionsReadNewerVersionData1) {
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
}
TEST(SerializeGrowable, MultipleNestedSessionsReadNewerVersionData2) {
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadNewerVersionData2) {
SerializationContext ctx;
DataV3 data{8454,987451,54};
ctx.createSerializer();
@@ -411,3 +414,32 @@ TEST(SerializeGrowable, MultipleNestedSessionsReadNewerVersionData2) {
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
}
TEST(SerializeExtensionGrowable, SessionsStartsAtEndOfSerialization) {
SerializationContext ctx;
DataV2 data{8454,987451};
ctx.createSerializer();
auto& bw = (*ctx.bw);
for (auto i = 0; i < 100; ++i)
bw.writeBytes<4>(data.v1);
for (auto i = 0; i < 10; ++i) {
bw.beginSession();
bw.writeBytes<4>(data.v1);
bw.writeBytes<4>(data.v2);
bw.endSession();
}
//create more sessions that can fit in 2 bytes
ctx.createDeserializer();//to flush data and create buffer reader
DataV2 res{};
auto& br = (*ctx.br);
for (auto i = 0; i < 100; ++i)
br.readBytes<4>(res.v1);
for (auto i = 0; i < 10; ++i) {
br.beginSession();
br.readBytes<4>(res.v1);
br.readBytes<4>(res.v2);
br.endSession();
EXPECT_THAT(res.v1, Eq(data.v1));
EXPECT_THAT(res.v2, Eq(data.v2));
}
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
}

View File

@@ -24,27 +24,25 @@
#include "serialization_test_utils.h"
#if __cplusplus > 201402L
# include<optional>
#else
# include <experimental/optional>
namespace std {
template <typename T>
using optional = experimental::optional<T>;
}
#endif
#include<optional>
#include <bitsery/ext/optional.h>
using extoptional = bitsery::ext::optional;
using Optional = bitsery::ext::Optional;
using testing::Eq;
template <typename T>
void test(SerializationContext& ctx, const T& v, T& r) {
ctx.createSerializer().extend4b(v, extoptional{});
ctx.createDeserializer().extend4b(r, extoptional{});
ctx.createSerializer().ext4b(v, Optional{});
ctx.createDeserializer().ext4b(r, Optional{});
}
TEST(SerializeExtensionOptional, EmptyOptional) {
@@ -80,3 +78,5 @@ TEST(SerializeExtensionOptional, OptionalHasValue) {
EXPECT_THAT(t1.value(), Eq(r1.value()));
}
#endif

View File

@@ -23,11 +23,15 @@
#include <gmock/gmock.h>
#include "serialization_test_utils.h"
#include <bitsery/ext/value_range.h>
using namespace testing;
using bitsery::RangeSpec;
using bitsery::BitsConstraint;
using bitsery::details::RangeSpec;
using bitsery::ext::BitsConstraint;
using bitsery::ext::ValueRange;
TEST(SerializeRange, RequiredBitsIsConstexpr) {
#if __cplusplus > 201402L
TEST(SerializeExtensionValueRange, RequiredBitsIsConstexpr) {
constexpr RangeSpec<int> r1{0, 31};
static_assert(r1.bitsRequired == 5, "r1.bitsRequired == 5");
@@ -43,123 +47,125 @@ TEST(SerializeRange, RequiredBitsIsConstexpr) {
}
TEST(SerializeRange, IntegerNegative) {
#endif
TEST(SerializeExtensionValueRange, IntegerNegative) {
SerializationContext ctx;
constexpr RangeSpec<int> r1{-50, 50};
ValueRange<int> r1{-50, 50};
int t1{-8};
int res1;
ctx.createSerializer().range(t1, r1);
ctx.createDeserializer().range(res1, r1);
ctx.createSerializer().ext(t1, r1);
ctx.createDeserializer().ext(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
EXPECT_THAT(res1, Eq(t1));
}
TEST(SerializeRange, IntegerPositive) {
TEST(SerializeExtensionValueRange, IntegerPositive) {
SerializationContext ctx;
constexpr RangeSpec<unsigned> r1{4, 10};
ValueRange<unsigned> r1{4u, 10u};
unsigned t1{8};
unsigned res1;
ctx.createSerializer().range(t1, r1);
ctx.createDeserializer().range(res1, r1);
ctx.createSerializer().ext(t1, r1);
ctx.createDeserializer().ext(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
EXPECT_THAT(res1, Eq(t1));
}
TEST(SerializeRange, EnumTypes) {
TEST(SerializeExtensionValueRange, EnumTypes) {
SerializationContext ctx;
constexpr RangeSpec<MyEnumClass> r1{MyEnumClass::E2, MyEnumClass::E4};
ValueRange<MyEnumClass> r1{MyEnumClass::E2, MyEnumClass::E4};
MyEnumClass t1{MyEnumClass::E2};
MyEnumClass res1;
ctx.createSerializer().range(t1, r1);
ctx.createDeserializer().range(res1, r1);
ctx.createSerializer().ext(t1, r1);
ctx.createDeserializer().ext(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
EXPECT_THAT(res1, Eq(t1));
}
TEST(SerializeRange, FloatUsingPrecisionConstraint1) {
TEST(SerializeExtensionValueRange, FloatUsingPrecisionConstraint1) {
SerializationContext ctx;
constexpr float precision{0.01f};
constexpr float min{-1.0f};
constexpr float max{1.0f};
float t1{0.5f};
constexpr RangeSpec<float> r1{min, max, precision};
ValueRange<float> r1{min, max, precision};
float res1;
ctx.createSerializer().range(t1, r1);
ctx.createDeserializer().range(res1, r1);
ctx.createSerializer().ext(t1, r1);
ctx.createDeserializer().ext(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
EXPECT_THAT(res1, ::testing::FloatNear(t1, (max - min) * precision));
}
TEST(SerializeRange, DoubleUsingPrecisionConstraint2) {
TEST(SerializeExtensionValueRange, DoubleUsingPrecisionConstraint2) {
SerializationContext ctx;
constexpr double precision{0.000002};
constexpr double min{50.0};
constexpr double max{100000.0};
double t1{38741.0};
constexpr RangeSpec<double> r1{min, max, precision};
ValueRange<double> r1{min, max, precision};
double res1;
ctx.createSerializer().range(t1, r1);
ctx.createDeserializer().range(res1, r1);
ctx.createSerializer().ext(t1, r1);
ctx.createDeserializer().ext(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(5));
EXPECT_THAT(res1, ::testing::DoubleNear(t1, (max - min) * precision));
}
TEST(SerializeRange, FloatUsingBitsSizeConstraint1) {
TEST(SerializeExtensionValueRange, FloatUsingBitsSizeConstraint1) {
SerializationContext ctx;
constexpr size_t bits = 8;
constexpr float min{-1.0f};
constexpr float max{1.0f};
float t1{0.5f};
constexpr RangeSpec<float> r1{min, max, BitsConstraint(bits)};
ValueRange<float> r1{min, max, BitsConstraint(bits)};
float res1;
ctx.createSerializer().range(t1, r1);
ctx.createDeserializer().range(res1, r1);
ctx.createSerializer().ext(t1, r1);
ctx.createDeserializer().ext(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
EXPECT_THAT(res1, ::testing::FloatNear(t1, (max - min) / (static_cast<bitsery::details::SAME_SIZE_UNSIGNED<float>>(1) << bits)));
}
TEST(SerializeRange, DoubleUsingBitsSizeConstraint2) {
TEST(SerializeExtensionValueRange, DoubleUsingBitsSizeConstraint2) {
SerializationContext ctx;
constexpr size_t bits = 50;
constexpr double min{50.0};
constexpr double max{100000.0};
double t1{38741};
constexpr RangeSpec<double> r1{min, max, BitsConstraint(bits)};
ValueRange<double> r1{min, max, BitsConstraint(bits)};
double res1;
ctx.createSerializer().range(t1, r1);
ctx.createDeserializer().range(res1, r1);
ctx.createSerializer().ext(t1, r1);
ctx.createDeserializer().ext(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(7));
EXPECT_THAT(res1, ::testing::DoubleNear(t1, (max - min) / (static_cast<bitsery::details::SAME_SIZE_UNSIGNED<double>>(1) << bits)));
}
TEST(SerializeRange, WhenDataIsInvalidThenReturnMinimumRangeValue) {
TEST(SerializeExtensionValueRange, WhenDataIsInvalidThenReturnMinimumRangeValue) {
SerializationContext ctx;
constexpr RangeSpec<int> r1{4, 10};//6 is max, but 3bits required
ValueRange<int> r1{4, 10};//6 is max, but 3bits required
int res1;
uint8_t tmp{0xFF};//write all 1 so when reading 3 bits we get 7
ctx.createSerializer().value1b(tmp);
ctx.createDeserializer().range(res1, r1);
ctx.createDeserializer().ext(res1, r1);
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
EXPECT_THAT(res1, Eq(4));