mirror of
https://github.com/fraillt/bitsery.git
synced 2026-06-18 13:19:11 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0508025f6 | ||
|
|
ad7090539e | ||
|
|
2f8ae0075c | ||
|
|
cba80774e4 | ||
|
|
241f1f2940 | ||
|
|
fe2bccf28f | ||
|
|
944197db97 | ||
|
|
a852174329 | ||
|
|
fb89af8112 | ||
|
|
4df4df9317 | ||
|
|
5d192f2002 | ||
|
|
79ee168893 | ||
|
|
5c696f14bd | ||
|
|
a1104a9b95 | ||
|
|
6e8898317e | ||
|
|
19555528bb | ||
|
|
1986491173 | ||
|
|
4ad65f8384 |
23
.travis.yml
Normal file
23
.travis.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
language: cpp
|
||||
|
||||
compiler:
|
||||
- gcc
|
||||
# clang
|
||||
|
||||
before_install:
|
||||
# C++14
|
||||
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||
- sudo apt-get update -qq
|
||||
|
||||
install:
|
||||
- sudo apt-get install -qq g++-5 lcov
|
||||
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-5 90
|
||||
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 90
|
||||
|
||||
before_script:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake ..
|
||||
|
||||
script: make && make test
|
||||
|
||||
88
CHANGELOG.md
88
CHANGELOG.md
@@ -1 +1,87 @@
|
||||
#
|
||||
# [3.0.0](https://github.com/fraillt/bitsery/compare/v2.0.1...v3.0.0) (2017-09-21)
|
||||
|
||||
### Features
|
||||
|
||||
* 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**.
|
||||
* 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.
|
||||
* added traits for custom types specialization, in *details/traits.h*
|
||||
* improved serialization performance: added support for fixed size buffer for BufferWriter for best performance.
|
||||
* improved performance for reading/writing container sizes.
|
||||
* added new method to BufferReader **getError** which returns on of three values: NO_ERROR, BUFFER_OVERFLOW, INVALID_BUFFER_DATA, also added setError method, that is only used by Deserializer.
|
||||
|
||||
|
||||
### 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*.
|
||||
* 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).
|
||||
* renamed BufferReader **isCompleted** to **isCompletedSuccessfully**, that returns true only when there is no errors and buffer is fully read.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* **readBytes** was reading in aligned mode, when scratch value from previous bit operations was zero.
|
||||
* **writeBits** had incorrect assert when writing negative int64_t.
|
||||
|
||||
<a name="2.0.1"></a>
|
||||
# [2.0.1](https://github.com/fraillt/bitsery/compare/v2.0.0...v2.0.1) (2017-08-12)
|
||||
|
||||
### Other notes
|
||||
* added travis build status
|
||||
|
||||
<a name="2.0.0"></a>
|
||||
# [2.0.0](https://github.com/fraillt/bitsery/compare/v1.1.1...v2.0.0) (2017-07-25)
|
||||
|
||||
### Features
|
||||
|
||||
* Endianness support, default network configuration is *little endian*
|
||||
* added user extensible function **ext**, to work with objects that require different serialization/deserialization path (e.g. pointers)
|
||||
* **optional** extension (for *ext* function), to work with *std::optional* types
|
||||
|
||||
### Bug Fixes
|
||||
* *align* method fixed in *BufferReader*
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* file structure changed, added *details* folder.
|
||||
* no longer support for implicit size converions for all functions (*value*, *array*, *container*), instead added helper functions with specific size, to avoid typing *s.template value<1>...* within serialization function body
|
||||
* changed parameters order for all functions that use custom function (lambda)
|
||||
* *BufferReader* and *BufferWriter* is now alias types for real types *BasicBufferReader/Writer<DefaultConfig>* (*DefaultConfig* is defined in *common.h*)
|
||||
|
||||
|
||||
<a name="1.1.1"></a>
|
||||
# [1.1.1](https://github.com/fraillt/bitsery/compare/v1.0.0...v1.1.1) (2017-02-23)
|
||||
|
||||
### Notes
|
||||
* changed folder structure
|
||||
* added more BufferReader constructors
|
||||
|
||||
# 1.0.0 (2017-02-22)
|
||||
|
||||
### Features
|
||||
|
||||
Serialization functions:
|
||||
* **value** - [fundamental types](doc/design/fundamental_types.md)
|
||||
* **container** - dynamic size containers
|
||||
* **array** - fixed size containers
|
||||
* **text** - for c-array and std::string
|
||||
* **range** - compresion for fundamental types (e.g. int between [255..512] will take up 8bits
|
||||
* **substitution** - default value from list (e.g. 4d vector, that is most of the time equals to [0,0,0,1] can store only 1bit)
|
||||
* **boolBit**/**boolByte** - serialize bool, as 1bit or 1byte.
|
||||
|
||||
@@ -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)
|
||||
|
||||
27
CONTRIBUTING.md
Normal file
27
CONTRIBUTING.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# 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 documentation.
|
||||
However, to make sure the process of accepting patches goes smoothly, you should try to follow these few simple guidelines when
|
||||
you contribute:
|
||||
|
||||
1. Fork the repository.
|
||||
2. Create new branch based on the *master* branch (`git checkout -b your_branch master`). If your contribution is a bug fix, you should name your branch `bugfix/xxx`; for a feature, it should be `feature/xxx`. Otherwise, just use your good judgment. Consistent naming of branches is appreciated since it makes the output of `git branch` easier to understand with a single glance.
|
||||
3. Do your modifications on that branch. Except for special cases, your contribution should include proper unit tests and documentation.
|
||||
4. Make sure your modifications did not break anything by building, running tests and checking code coverage (test coverage should not be less than 100%):
|
||||
```shell
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make
|
||||
ctest
|
||||
make tests_coverage
|
||||
x-www-browser ./coverage/index.html
|
||||
```
|
||||
5. Commit your changes, and push to your fork (`git push origin your_branch`). Commit message should be one line short description. When applicable, please squash adjacent *wip* commits into a single *logical* commit.
|
||||
6. Open a pull request against Bitsery *master* branch. Currently ongoing development is on *master*. At some point an integration branch will be set-up, and pull-requests should target that, but for now its all against master. You may see feature branches come and go, too.
|
||||
|
||||
|
||||
## Style guide
|
||||
|
||||
Just use your own judgment and stick to the style of the surrounding code.
|
||||
122
README.md
122
README.md
@@ -1,77 +1,97 @@
|
||||
# Bitsery
|
||||
|
||||
[](https://travis-ci.org/fraillt/bitsery)
|
||||
[](https://gitter.im/bitsery/Lobby)
|
||||
|
||||
Header only C++ binary serialization library.
|
||||
It is designed around the networking requirements for multiplayer real-time fast paced games as first person shooters.
|
||||
All cross-platform requirements are enforced at compile time, so serialized data do not store any run-time type information and is as small as possible.
|
||||
It is designed around the networking requirements for real-time data delivery, especially for games.
|
||||
|
||||
## Status
|
||||
All cross-platform requirements are enforced at compile time, so serialized data do not store any meta-data information and is as small as possible.
|
||||
|
||||
**bitsery** is in pre-release state and is looking for your feedback.
|
||||
It has basic features, serialize arithmetic types, enums, containers and text, and few advanced features like value ranges and default values (substitution), but is still missing functions for delta changes and geometry compression.
|
||||
> Current version do not handle Big/Little Endianness.
|
||||
> Error handling on deserialization is not tested.
|
||||
> **bitsery** is looking for your feedback on [gitter](https://gitter.im/bitsery/Lobby)
|
||||
|
||||
## Example
|
||||
## Features
|
||||
|
||||
* 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.
|
||||
* Supports forward/backward compatibility for your types.
|
||||
* 2-in-1 declarative control flow, same code for serialization and deserialization.
|
||||
* 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 decide yourself. *(benchmarked on Ubuntu with GCC 7.1)*
|
||||
|
||||
| | serialize | deserialize | data size | executable size |
|
||||
|-----------------------------------------------------------|-----------|-------------|-------------|-----------------|
|
||||
| flatbuffers | 1852 ms. | 777 ms. | 27252 bytes | 74544 bytes |
|
||||
| cereal | 1069 ms. | 1385 ms. | 20208 bytes | 72336 bytes |
|
||||
| bitsery | 808 ms. | 737 ms. | 14803 bytes | 69784 bytes |
|
||||
| bitsery fixed-size buffer | 297 ms. | 738 ms. | 14803 bytes | 69928 bytes |
|
||||
| bitsery optimized serialization flow | 686 ms. | 997 ms. | 6601 bytes | 69320 bytes |
|
||||
| bitsery optimized serialization flow + fixed-size buffer | 446 ms. | 996 ms. | 6601 bytes | 69464 bytes |
|
||||
|
||||
If still not convinced read more in library [motivation](doc/design/README.md) section.
|
||||
|
||||
## Usage example
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <bitsery/bitsery.h>
|
||||
|
||||
|
||||
enum class MyEnum { V1,V2,V3 };
|
||||
struct MyStruct {
|
||||
int i;
|
||||
MyEnum e;
|
||||
std::vector<float> fs;
|
||||
};
|
||||
|
||||
//define how object should be serialized/deserialized
|
||||
SERIALIZE(MyStruct) {
|
||||
return s.
|
||||
value(o.i).
|
||||
value(o.e).
|
||||
container(o.fs, 100);
|
||||
}
|
||||
|
||||
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() {
|
||||
//set some random data
|
||||
MyStruct data{8941, MyEnum::V2, {15.0f, -8.5f, 0.045f}};
|
||||
MyStruct res{};
|
||||
|
||||
//create serializer
|
||||
//1) create buffer to store data
|
||||
std::vector<uint8_t> buffer;
|
||||
//2) create buffer writer that is able to write bytes or bits to buffer
|
||||
BufferWriter bw{buffer};
|
||||
//3) create serializer
|
||||
Serializer<BufferWriter> ser{bw};
|
||||
Serializer ser{bw};
|
||||
|
||||
//serialize object, can also be invoked like this: serialize(ser, data)
|
||||
ser.object(data);
|
||||
MyStruct data{8941, "hello", {15.0f, -8.5f, 0.045f}};
|
||||
ser.object(data); // serializes data
|
||||
|
||||
//flush to buffer, before creating buffer reader
|
||||
bw.flush();
|
||||
BufferReader br{bw.getWrittenRange()};
|
||||
Deserializer des{br};
|
||||
|
||||
//create deserializer
|
||||
//1) create buffer reader
|
||||
BufferReader br{buffer};
|
||||
//2) create deserializer
|
||||
Deserializer<BufferReader> des{br};
|
||||
|
||||
//deserialize same object, can also be invoked like this: serialize(des, data)
|
||||
des.object(res);
|
||||
|
||||
//check is equal
|
||||
std::cout << "is equal: " << (data.fs == res.fs && data.i == res.i && data.e == res.e) << std::endl;
|
||||
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.
|
||||
|
||||
## 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).
|
||||
52
doc/README.md
Normal file
52
doc/README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
To get the most out of **Bitsery**, start with the [tutorial](tutorial/README.md).
|
||||
Once you're familiar with the library consider the following reference material.
|
||||
|
||||
Library design:
|
||||
* `valueNb instead of value`
|
||||
* `fundamental types`
|
||||
* `serializer/deserializer functions overloads`
|
||||
* `extending library functionality`
|
||||
* `errors handling`
|
||||
* `forward/backward compatibility via Growable extension`
|
||||
|
||||
Core Serializer/Deserializer functions (alphabetical order):
|
||||
* `align`
|
||||
* `boolByte`
|
||||
* `boolBit`
|
||||
* `container`
|
||||
* `extend`
|
||||
* `getContext`
|
||||
* `object`
|
||||
* `text`
|
||||
* `value`
|
||||
|
||||
Serializer/Deserializer extensions via `extend` method (alphabetical order):
|
||||
* `ContainerMap`
|
||||
* `Entropy`
|
||||
* `Growable`
|
||||
* `Optional`
|
||||
* `ValueRange`
|
||||
|
||||
BasicBufferWriter/Reader functions:
|
||||
* `writeBits/readBits`
|
||||
* `writeBytes/readBytes`
|
||||
* `writeBuffer/readBuffer`
|
||||
* `align`
|
||||
* `beginSession/endSession`
|
||||
* `flush (writer only)`
|
||||
* `setError (reader only)`
|
||||
* `getError (reader only)`
|
||||
* `isCompletedSuccessfully (reader only)`
|
||||
|
||||
Tips and tricks:
|
||||
* if you're getting static assert "please define 'serialize' function", most likely it is because your SERIALIZE function is not defined in same namespace as object.
|
||||
|
||||
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** extension, serialized buffer size in bytes, cannot be greater than 2^(n-2) (where n = sizeof(std::size_t) * 8).
|
||||
|
||||
Other:
|
||||
* [Contributing](../CONTRIBUTING.md)
|
||||
* [Change log](../CHANGELOG.md)
|
||||
|
||||
|
||||
70
doc/design/README.md
Normal file
70
doc/design/README.md
Normal file
@@ -0,0 +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/), 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
|
||||
|
||||
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.
|
||||
|
||||
Adding optional support for JSON doesn't come for free either.
|
||||
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.
|
||||
|
||||
Both solutions adds additional complexity to the library. In the future C++ will get reflection system, currently static reflection was proposed to standard.
|
||||
|
||||
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
|
||||
|
||||
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.*
|
||||
1
doc/design/errors.md
Normal file
1
doc/design/errors.md
Normal file
@@ -0,0 +1 @@
|
||||
errors handling design
|
||||
0
doc/design/extensions.md
Normal file
0
doc/design/extensions.md
Normal file
1
doc/design/function_n.md
Normal file
1
doc/design/function_n.md
Normal file
@@ -0,0 +1 @@
|
||||
valueN instead of value
|
||||
0
doc/design/function_overload.md
Normal file
0
doc/design/function_overload.md
Normal file
1
doc/design/fundamental_types.md
Normal file
1
doc/design/fundamental_types.md
Normal file
@@ -0,0 +1 @@
|
||||
ints chars floats (except bool)
|
||||
6
doc/reference/buf_get_error.md
Normal file
6
doc/reference/buf_get_error.md
Normal file
@@ -0,0 +1,6 @@
|
||||
*document in progress*
|
||||
* NO_ERROR,
|
||||
* BUFFER_OVERFLOW,
|
||||
* INVALID_BUFFER_DATA
|
||||
* write what happens when data is corrupted
|
||||
* how growable effect error codes when deserializing old data format.
|
||||
7
doc/reference/buf_is_completed_successfully.md
Normal file
7
doc/reference/buf_is_completed_successfully.md
Normal file
@@ -0,0 +1,7 @@
|
||||
### BufferReader.isCompletedSuccessfully()
|
||||
|
||||
Returns true when buffer was fully read, and there was no [errors](buf_get_error.md).
|
||||
|
||||
If buffer contains multiple serialized objects and you want to know deserialization state for each object, then use [getError](buf_get_error.md) after reading each object.
|
||||
|
||||
Use this function when buffer contains one object, or you know that you read all data.
|
||||
8
doc/tutorial/README.md
Normal file
8
doc/tutorial/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
The grand plan for this tutorial is to learn how to serialize/deserialize any object efficiently in time and space, so you could focus on other, more interesting things.
|
||||
|
||||
This tutorial will cover these main topics:
|
||||
* `Hello World` write one control flow for both: serialization and deserialization.
|
||||
* `Composer` efficiently compose complex serialization flows.
|
||||
* `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.
|
||||
3
doc/tutorial/composition.md
Normal file
3
doc/tutorial/composition.md
Normal file
@@ -0,0 +1,3 @@
|
||||
*document in progress*
|
||||
* explain why *value* and *object* is fundamental functions.
|
||||
* write about *Growable* extension
|
||||
128
doc/tutorial/hello_world.md
Normal file
128
doc/tutorial/hello_world.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Quick Start
|
||||
|
||||
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 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);
|
||||
};
|
||||
```
|
||||
|
||||
**bitsery** also can serialize private class members, just move *serialize* function inside structure, and make it *friend* (*fiend void serialize(.....)*).
|
||||
|
||||
**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.
|
||||
|
||||
```cpp
|
||||
std::vector<uint8_t> buffer;
|
||||
BufferWriter bw{buffer};
|
||||
Serializer ser{bw};
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
Serializer doesn't store any state, it only has reference to buffer, so it is safe to create many of those if nesessary.
|
||||
|
||||
BufferWriter also doesn't own buffer, but it stores state about writing position and container size.
|
||||
|
||||
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
|
||||
|
||||
```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
|
||||
}
|
||||
```
|
||||
|
||||
**currently documentation and tutorial is progress, but for more usage examples see examples folder**
|
||||
0
doc/tutorial/two_in_one.md
Normal file
0
doc/tutorial/two_in_one.md
Normal file
@@ -1,44 +1,19 @@
|
||||
//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 <iostream>
|
||||
#include <vector>
|
||||
#include <bitsery/bitsery.h>
|
||||
|
||||
|
||||
enum class MyEnum { V1,V2,V3 };
|
||||
enum class MyEnum:uint16_t { V1,V2,V3 };
|
||||
struct MyStruct {
|
||||
int i;
|
||||
uint32_t i;
|
||||
MyEnum e;
|
||||
std::vector<float> fs;
|
||||
};
|
||||
|
||||
//define how object should be serialized/deserialized
|
||||
SERIALIZE(MyStruct) {
|
||||
return s.
|
||||
value4(o.i).
|
||||
value4(o.e).
|
||||
container4(o.fs, 100);
|
||||
}
|
||||
template <typename S>
|
||||
void serialize(S& s, MyStruct& o) {
|
||||
s.value4b(o.i);
|
||||
s.value2b(o.e);
|
||||
s.container4b(o.fs, 10);
|
||||
};
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
@@ -53,7 +28,7 @@ int main() {
|
||||
//2) create buffer writer that is able to write bytes or bits to buffer
|
||||
BufferWriter bw{buffer};
|
||||
//3) create serializer
|
||||
Serializer<BufferWriter> ser{bw};
|
||||
Serializer ser{bw};
|
||||
|
||||
//serialize object, can also be invoked like this: serialize(ser, data)
|
||||
ser.object(data);
|
||||
@@ -63,13 +38,11 @@ int main() {
|
||||
|
||||
//create deserializer
|
||||
//1) create buffer reader
|
||||
BufferReader br{buffer};
|
||||
BufferReader br{bw.getWrittenRange()};
|
||||
//2) create deserializer
|
||||
Deserializer<BufferReader> des{br};
|
||||
Deserializer des{br};
|
||||
|
||||
//deserialize same object, can also be invoked like this: serialize(des, data)
|
||||
des.object(res);
|
||||
|
||||
//check is equal
|
||||
std::cout << "is equal: " << (data.fs == res.fs && data.i == res.i && data.e == res.e) << std::endl;
|
||||
}
|
||||
assert(data.fs == res.fs && data.i == res.i && data.e == res.e);
|
||||
}
|
||||
|
||||
101
examples/flatbuffers_tutorial_data.cpp
Normal file
101
examples/flatbuffers_tutorial_data.cpp
Normal 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);
|
||||
|
||||
}
|
||||
@@ -24,9 +24,9 @@
|
||||
#ifndef BITSERY_BITSERY_H
|
||||
#define BITSERY_BITSERY_H
|
||||
|
||||
#define BITSERY_MAJOR_VERSION 1
|
||||
#define BITSERY_MINOR_VERSION 1
|
||||
#define BITSERY_PATCH_VERSION 1
|
||||
#define BITSERY_MAJOR_VERSION 3
|
||||
#define BITSERY_MINOR_VERSION 0
|
||||
#define BITSERY_PATCH_VERSION 0
|
||||
|
||||
#define BITSERY_QUOTE_MACRO(name) #name
|
||||
#define BITSERY_BUILD_VERSION_STR(major,minor, patch) \
|
||||
|
||||
@@ -25,119 +25,164 @@
|
||||
#ifndef BITSERY_BUFFER_READER_H
|
||||
#define BITSERY_BUFFER_READER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <cassert>
|
||||
#include "details/buffer_common.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
template <typename Config>
|
||||
template<typename Config>
|
||||
struct BasicBufferReader {
|
||||
using ValueType = typename Config::BufferValueType;
|
||||
using ScratchType = typename Config::BufferScrathType;
|
||||
|
||||
BasicBufferReader(const ValueType* data, size_t size) : _pos{data}, _end{data + size}
|
||||
using BufferType = typename Config::BufferType;
|
||||
using ValueType = typename details::BufferContainerTraits<BufferType>::TValue;
|
||||
using BufferIteratorType = typename details::BufferContainerTraits<BufferType>::TIterator;
|
||||
using ScratchType = typename details::SCRATCH_TYPE<ValueType>::type;
|
||||
|
||||
BasicBufferReader(ValueType* begin, ValueType* end)
|
||||
:_pos{begin},
|
||||
_end{end},
|
||||
_session{*this, _pos, _end}
|
||||
{
|
||||
static_assert(std::is_unsigned<ValueType>(), "Config::BufferValueType must be unsigned");
|
||||
static_assert(std::is_unsigned<ScratchType>(), "Config::BufferScrathType must be unsigned");
|
||||
static_assert(sizeof(ValueType)*2 == sizeof(ScratchType), "ScratchType must be 2x bigger than value type");
|
||||
static_assert(sizeof(ValueType) * 2 == sizeof(ScratchType),
|
||||
"ScratchType must be 2x bigger than value type");
|
||||
static_assert(sizeof(ValueType) == 1, "currently only supported BufferValueType is 1 byte");
|
||||
}
|
||||
|
||||
explicit BasicBufferReader(const std::vector<ValueType> &buf) : BasicBufferReader(buf.data(), buf.size()) {
|
||||
BasicBufferReader(BufferRange<BufferIteratorType> range)
|
||||
:BasicBufferReader(std::addressof(*range.begin()), std::addressof(*range.end())) {
|
||||
static_assert(std::is_same<
|
||||
typename std::iterator_traits<BufferIteratorType>::iterator_category,
|
||||
std::random_access_iterator_tag>::value,
|
||||
"BufferReader only accepts random access iterators");
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit BasicBufferReader(const ValueType (&data)[N]): BasicBufferReader(data, N)
|
||||
{
|
||||
}
|
||||
BasicBufferReader(const BasicBufferReader &) = delete;
|
||||
|
||||
BasicBufferReader &operator=(const BasicBufferReader &) = delete;
|
||||
|
||||
BasicBufferReader(BasicBufferReader &&) noexcept = default;
|
||||
|
||||
BasicBufferReader &operator=(BasicBufferReader &&) noexcept = default;
|
||||
|
||||
BasicBufferReader(const BasicBufferReader&) = delete;
|
||||
BasicBufferReader& operator=(const BasicBufferReader& ) = delete;
|
||||
BasicBufferReader(BasicBufferReader&&) noexcept = default;
|
||||
BasicBufferReader& operator=(BasicBufferReader&&) noexcept = default;
|
||||
~BasicBufferReader() noexcept = default;
|
||||
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
bool readBytes(T &v) {
|
||||
void readBytes(T &v) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
using UT = typename std::make_unsigned<T>::type;
|
||||
return !m_scratch
|
||||
? directRead(&v, 1)
|
||||
: readBits(reinterpret_cast<UT &>(v), details::BITS_SIZE<T>);
|
||||
if (!m_scratchBits)
|
||||
directRead(&v, 1);
|
||||
else
|
||||
readBits(reinterpret_cast<UT &>(v), details::BITS_SIZE<T>::value);
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
bool readBuffer(T *buf, size_t count) {
|
||||
void readBuffer(T *buf, size_t count) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
|
||||
if (!m_scratchBits)
|
||||
return directRead(buf, count);
|
||||
|
||||
using UT = typename std::make_unsigned<T>::type;
|
||||
//todo improve implementation
|
||||
const auto end = buf + count;
|
||||
for (auto it = buf; it != end; ++it) {
|
||||
if (!readBits(reinterpret_cast<UT &>(*it), details::BITS_SIZE<T>))
|
||||
return false;
|
||||
if (!m_scratchBits) {
|
||||
directRead(buf, count);
|
||||
} else {
|
||||
using UT = typename std::make_unsigned<T>::type;
|
||||
//todo improve implementation
|
||||
const auto end = buf + count;
|
||||
for (auto it = buf; it != end; ++it)
|
||||
readBits(reinterpret_cast<UT &>(*it), details::BITS_SIZE<T>::value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
bool readBits(T &v, size_t bitsCount) {
|
||||
void readBits(T &v, size_t bitsCount) {
|
||||
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
|
||||
assert(bitsCount <= details::BITS_SIZE<T>);
|
||||
|
||||
const auto bytesRequired = bitsCount > m_scratchBits
|
||||
? ((bitsCount - 1 - m_scratchBits) >> 3) + 1u
|
||||
: 0u;
|
||||
if (static_cast<size_t>(std::distance(_pos, _end)) < bytesRequired)
|
||||
return false;
|
||||
readBitsInternal(v, bitsCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool align() {
|
||||
void align() {
|
||||
if (m_scratchBits) {
|
||||
ScratchType tmp{};
|
||||
readBitsInternal(tmp, m_scratchBits);
|
||||
return tmp == 0;
|
||||
if (tmp)
|
||||
setError(BufferReaderError::INVALID_BUFFER_DATA);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isCompleted() const {
|
||||
return _pos == _end;
|
||||
bool isCompletedSuccessfully() const {
|
||||
return _pos == _end && !_session.hasActiveSessions();
|
||||
}
|
||||
|
||||
BufferReaderError getError() const {
|
||||
auto res = std::distance(_end, _pos);
|
||||
if (res > 0) {
|
||||
auto err = static_cast<BufferReaderError>(res);
|
||||
if (_session.hasActiveSessions() && err == BufferReaderError::BUFFER_OVERFLOW)
|
||||
return BufferReaderError::NO_ERROR;
|
||||
return err;
|
||||
}
|
||||
return BufferReaderError::NO_ERROR;
|
||||
}
|
||||
|
||||
void setError(BufferReaderError error) {
|
||||
_end = _pos;
|
||||
//to avoid creating temporary for error state, mark an error by passing _pos after the _end
|
||||
std::advance(_pos, static_cast<size_t>(error));
|
||||
}
|
||||
|
||||
void beginSession() {
|
||||
align();
|
||||
if (getError() != BufferReaderError::INVALID_BUFFER_DATA) {
|
||||
_session.begin();
|
||||
}
|
||||
}
|
||||
|
||||
void endSession() {
|
||||
align();
|
||||
if (getError() != BufferReaderError::INVALID_BUFFER_DATA) {
|
||||
_session.end();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const ValueType* _pos;
|
||||
const ValueType* _end;
|
||||
ValueType* _pos;
|
||||
ValueType* _end;
|
||||
ScratchType m_scratch{};
|
||||
size_t m_scratchBits{};
|
||||
typename std::conditional<Config::BufferSessionsEnabled,
|
||||
details::BufferSessionsReader<BasicBufferReader<Config>, ValueType*>,
|
||||
details::DisabledBufferSessionsReader<Config>>::type
|
||||
_session;
|
||||
|
||||
template<typename T>
|
||||
bool directRead(T *v, size_t count) {
|
||||
void directRead(T *v, size_t count) {
|
||||
static_assert(!std::is_const<T>::value, "");
|
||||
const auto bytesCount = sizeof(T) * count;
|
||||
if (static_cast<size_t>(std::distance(_pos, _end)) < bytesCount)
|
||||
return false;
|
||||
//read from buffer, to data ptr,
|
||||
std::copy_n(_pos, bytesCount, reinterpret_cast<ValueType *>(v));
|
||||
std::advance(_pos, bytesCount);
|
||||
//swap each byte if nessesarry
|
||||
_swapDataBits(v, count, std::integral_constant<bool,
|
||||
Config::NetworkEndianness != details::getSystemEndianness()>{});
|
||||
return true;
|
||||
|
||||
if (std::distance(_pos, _end) >= static_cast<typename details::BufferContainerTraits<BufferType>::TDifference>(bytesCount)) {
|
||||
|
||||
std::memcpy(reinterpret_cast<ValueType *>(v), _pos, bytesCount);
|
||||
_pos += bytesCount;
|
||||
|
||||
//swap each byte if nessesarry
|
||||
_swapDataBits(v, count, std::integral_constant<bool,
|
||||
Config::NetworkEndianness != details::getSystemEndianness()>{});
|
||||
} else {
|
||||
//set everything to zeros
|
||||
std::memset(v, 0, bytesCount);
|
||||
|
||||
if (getError() == BufferReaderError::NO_ERROR)
|
||||
setError(BufferReaderError::BUFFER_OVERFLOW);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void _swapDataBits(T *v, size_t count, std::true_type) {
|
||||
std::for_each(v, std::next(v, count), [this](T& v) { v = details::swap(v); });
|
||||
std::for_each(v, std::next(v, count), [this](T &x) { x = details::swap(x); });
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@@ -150,12 +195,12 @@ namespace bitsery {
|
||||
auto bitsLeft = size;
|
||||
T res{};
|
||||
while (bitsLeft > 0) {
|
||||
auto bits = std::min(bitsLeft, details::BITS_SIZE<ValueType>);
|
||||
auto bits = std::min(bitsLeft, details::BITS_SIZE<ValueType>::value);
|
||||
if (m_scratchBits < bits) {
|
||||
ValueType tmp;
|
||||
directRead(&tmp, 1);
|
||||
m_scratch |= static_cast<ScratchType>(tmp) << m_scratchBits;
|
||||
m_scratchBits += details::BITS_SIZE<ValueType>;
|
||||
m_scratchBits += details::BITS_SIZE<ValueType>::value;
|
||||
}
|
||||
auto shiftedRes =
|
||||
static_cast<T>(m_scratch & ((static_cast<ScratchType>(1) << bits) - 1)) << (size - bitsLeft);
|
||||
@@ -167,9 +212,6 @@ namespace bitsery {
|
||||
v = res;
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
};
|
||||
//helper type
|
||||
using BufferReader = BasicBufferReader<DefaultConfig>;
|
||||
|
||||
@@ -25,10 +25,8 @@
|
||||
#ifndef BITSERY_BUFFER_WRITER_H
|
||||
#define BITSERY_BUFFER_WRITER_H
|
||||
|
||||
#include "common.h"
|
||||
#include "details/buffer_common.h"
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
namespace bitsery {
|
||||
@@ -39,13 +37,13 @@ namespace bitsery {
|
||||
void writeBytes(const T &) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
_bitsCount += details::BITS_SIZE<T>;
|
||||
_bitsCount += details::BITS_SIZE<T>::value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void writeBits(const T &, size_t bitsCount) {
|
||||
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
|
||||
assert(bitsCount <= details::BITS_SIZE<T>);
|
||||
assert(bitsCount <= details::BITS_SIZE<T>::value);
|
||||
_bitsCount += bitsCount;
|
||||
}
|
||||
|
||||
@@ -53,35 +51,71 @@ namespace bitsery {
|
||||
void writeBuffer(const T *, size_t count) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
_bitsCount += details::BITS_SIZE<T> * count;
|
||||
_bitsCount += details::BITS_SIZE<T>::value * count;
|
||||
}
|
||||
|
||||
void align() {
|
||||
auto _scratch = (_bitsCount % 8);
|
||||
_bitsCount += (8 - _scratch) % 8;
|
||||
}
|
||||
|
||||
void flush() {
|
||||
align();
|
||||
//flush sessions count
|
||||
if (_sessionsBytesCount > 0) {
|
||||
auto sessionsDataSizeBytesCount = (_sessionsBytesCount < 0x8000u ? 2 : 4);
|
||||
_bitsCount += (_sessionsBytesCount + sessionsDataSizeBytesCount) * 8;
|
||||
_sessionsBytesCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void beginSession() {
|
||||
|
||||
}
|
||||
|
||||
void endSession() {
|
||||
auto endPos = getWrittenBytesCount();
|
||||
details::writeSize(*this, endPos);
|
||||
auto sessionEndBytesCount = getWrittenBytesCount() - endPos;
|
||||
//remove written bytes, because we'll write them at the end
|
||||
_bitsCount -= sessionEndBytesCount * 8;
|
||||
_sessionsBytesCount += sessionEndBytesCount;
|
||||
}
|
||||
|
||||
//get size in bytes
|
||||
size_t getSize() const {
|
||||
size_t getWrittenBytesCount() const {
|
||||
return _bitsCount / 8;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _bitsCount{};
|
||||
|
||||
size_t _sessionsBytesCount{};
|
||||
};
|
||||
|
||||
template <typename Config>
|
||||
template<typename Config>
|
||||
struct BasicBufferWriter {
|
||||
using ValueType = typename Config::BufferValueType;
|
||||
using ScratchType = typename Config::BufferScrathType;
|
||||
using BufferType = typename Config::BufferType;
|
||||
using ValueType = typename details::BufferContainerTraits<BufferType>::TValue;
|
||||
using ScratchType = typename details::SCRATCH_TYPE<ValueType>::type;
|
||||
using BufferContext = details::WriteBufferContext<BufferType, details::BufferContainerTraits<BufferType>::isResizable>;
|
||||
|
||||
explicit BasicBufferWriter(std::vector<ValueType> &buffer) : _outIt{std::back_inserter(buffer)} {
|
||||
static_assert(std::is_unsigned<ValueType>(), "Config::BufferValueType must be unsigned");
|
||||
static_assert(std::is_unsigned<ScratchType>(), "Config::BufferScrathType must be unsigned");
|
||||
static_assert(sizeof(ValueType)*2 == sizeof(ScratchType), "ScratchType must be 2x bigger than value type");
|
||||
explicit BasicBufferWriter(BufferType &buffer)
|
||||
: _bufferContext{buffer}
|
||||
{
|
||||
static_assert(std::is_unsigned<ValueType>(), "Config::BufferType value type must be unsigned");
|
||||
static_assert(sizeof(ValueType) * 2 == sizeof(ScratchType),
|
||||
"ScratchType must be 2x bigger than value type");
|
||||
static_assert(sizeof(ValueType) == 1, "currently only supported BufferValueType is 1 byte");
|
||||
}
|
||||
|
||||
BasicBufferWriter(const BasicBufferWriter&) = delete;
|
||||
BasicBufferWriter& operator=(const BasicBufferWriter& ) = delete;
|
||||
BasicBufferWriter(BasicBufferWriter&&) noexcept = default;
|
||||
BasicBufferWriter& operator=(BasicBufferWriter&&) noexcept = default;
|
||||
BasicBufferWriter(const BasicBufferWriter &) = delete;
|
||||
|
||||
BasicBufferWriter &operator=(const BasicBufferWriter &) = delete;
|
||||
|
||||
BasicBufferWriter(BasicBufferWriter &&) noexcept = default;
|
||||
|
||||
BasicBufferWriter &operator=(BasicBufferWriter &&) noexcept = default;
|
||||
|
||||
~BasicBufferWriter() noexcept = default;
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
@@ -93,7 +127,7 @@ namespace bitsery {
|
||||
directWrite(&v, 1);
|
||||
} else {
|
||||
using UT = typename std::make_unsigned<T>::type;
|
||||
writeBits(reinterpret_cast<const UT &>(v), details::BITS_SIZE<T>);
|
||||
writeBits(reinterpret_cast<const UT &>(v), details::BITS_SIZE<T>::value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,57 +142,67 @@ namespace bitsery {
|
||||
//todo improve implementation
|
||||
const auto end = buf + count;
|
||||
for (auto it = buf; it != end; ++it)
|
||||
writeBits(reinterpret_cast<const UT &>(*it), details::BITS_SIZE<T>);
|
||||
writeBits(reinterpret_cast<const UT &>(*it), details::BITS_SIZE<T>::value);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void writeBits(const T &v, size_t bitsCount) {
|
||||
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
|
||||
assert(0 < bitsCount && bitsCount <= details::BITS_SIZE<T>);
|
||||
assert(v <= ((1ULL << bitsCount) - 1));
|
||||
assert(0 < bitsCount && bitsCount <= details::BITS_SIZE<T>::value);
|
||||
assert(v <= (bitsCount < 64
|
||||
? (1ULL << bitsCount) - 1
|
||||
: (1ULL << (bitsCount-1)) + ((1ULL << (bitsCount-1)) -1)));
|
||||
writeBitsInternal(v, bitsCount);
|
||||
}
|
||||
|
||||
void align() {
|
||||
if (_scratchBits)
|
||||
writeBitsInternal(ValueType{}, details::BITS_SIZE<ValueType> - _scratchBits);
|
||||
writeBitsInternal(ValueType{}, (details::BITS_SIZE<ValueType>::value - _scratchBits) % 8);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
if (_scratchBits) {
|
||||
auto tmp = static_cast<ValueType>( _scratch & _MASK );
|
||||
directWrite(&tmp, 1);
|
||||
_scratch >>= _scratchBits;
|
||||
_scratchBits -= _scratchBits;
|
||||
}
|
||||
align();
|
||||
_session.flushSessions(*this);
|
||||
}
|
||||
|
||||
BufferRange<typename details::BufferContainerTraits<BufferType>::TIterator> getWrittenRange() const {
|
||||
return _bufferContext.getWrittenRange();
|
||||
}
|
||||
|
||||
void beginSession() {
|
||||
align();
|
||||
_session.begin(*this);
|
||||
}
|
||||
|
||||
void endSession() {
|
||||
align();
|
||||
_session.end(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<typename T>
|
||||
void directWrite(T&& v, size_t count) {
|
||||
void directWrite(T &&v, size_t count) {
|
||||
_directWriteSwapTag(std::forward<T>(v), count, std::integral_constant<bool,
|
||||
Config::NetworkEndianness != details::getSystemEndianness()>{});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void _directWriteSwapTag(const T *v, size_t count, std::true_type) {
|
||||
std::for_each(v, std::next(v, count), [this](const T& v) {
|
||||
std::for_each(v, std::next(v, count), [this](const T &v) {
|
||||
const auto res = details::swap(v);
|
||||
std::copy_n(reinterpret_cast<const ValueType*>(&res), sizeof(T), _outIt);
|
||||
_bufferContext.write(reinterpret_cast<const ValueType *>(&res), sizeof(T));
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void _directWriteSwapTag(const T *v, size_t count, std::false_type) {
|
||||
std::copy_n(reinterpret_cast<const ValueType*>(v), count * sizeof(T), _outIt);
|
||||
_bufferContext.write(reinterpret_cast<const ValueType *>(v), count * sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void writeBitsInternal(const T &v, size_t size) {
|
||||
constexpr size_t valueSize = details::BITS_SIZE<ValueType>;
|
||||
constexpr size_t valueSize = details::BITS_SIZE<ValueType>::value;
|
||||
auto value = v;
|
||||
auto bitsLeft = size;
|
||||
while (bitsLeft > 0) {
|
||||
@@ -182,20 +226,24 @@ namespace bitsery {
|
||||
if (size > 0) {
|
||||
_scratch |= static_cast<ScratchType>( v ) << _scratchBits;
|
||||
_scratchBits += size;
|
||||
if (_scratchBits >= details::BITS_SIZE<ValueType>) {
|
||||
if (_scratchBits >= details::BITS_SIZE<ValueType>::value) {
|
||||
auto tmp = static_cast<ValueType>(_scratch & _MASK);
|
||||
directWrite(&tmp, 1);
|
||||
_scratch >>= details::BITS_SIZE<ValueType>;
|
||||
_scratchBits -= details::BITS_SIZE<ValueType>;
|
||||
_scratch >>= details::BITS_SIZE<ValueType>::value;
|
||||
_scratchBits -= details::BITS_SIZE<ValueType>::value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const ValueType _MASK = std::numeric_limits<ValueType>::max();
|
||||
std::back_insert_iterator<std::vector<ValueType>> _outIt;
|
||||
BufferContext _bufferContext;
|
||||
ScratchType _scratch{};
|
||||
size_t _scratchBits{};
|
||||
typename std::conditional<Config::BufferSessionsEnabled,
|
||||
details::BufferSessionsWriter<BasicBufferWriter<Config>>,
|
||||
details::DisabledBufferSessionsWriter<Config>>::type
|
||||
_session{};
|
||||
};
|
||||
|
||||
//helper type
|
||||
|
||||
@@ -24,24 +24,30 @@
|
||||
#ifndef BITSERY_COMMON_H
|
||||
#define BITSERY_COMMON_H
|
||||
|
||||
#include "bitsery/details/buffer_common.h"
|
||||
#include <vector>
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
struct DefaultConfig {
|
||||
static constexpr EndiannessType NetworkEndianness = EndiannessType::LittleEndian;
|
||||
using BufferValueType = uint8_t;
|
||||
using BufferScrathType = uint16_t;
|
||||
/*
|
||||
* endianess
|
||||
*/
|
||||
enum class EndiannessType {
|
||||
LittleEndian,
|
||||
BigEndian
|
||||
};
|
||||
|
||||
/*
|
||||
* serializer macro, serialize function specialization that accepts T& and const T&
|
||||
*/
|
||||
//default configuration for buffer writing/reading operations
|
||||
struct DefaultConfig {
|
||||
static constexpr EndiannessType NetworkEndianness = EndiannessType::LittleEndian;
|
||||
//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>;
|
||||
|
||||
|
||||
#define SERIALIZE(ObjectType) \
|
||||
template <typename S, typename T, typename std::enable_if<std::is_same<T, ObjectType>::value || std::is_same<T, const ObjectType>::value>::type* = nullptr> \
|
||||
S& serialize(S& s, T& o)
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_COMMON_H
|
||||
|
||||
@@ -1,220 +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.
|
||||
|
||||
|
||||
|
||||
#ifndef BITSERY_DELTADESERIALIZER_H
|
||||
#define BITSERY_DELTADESERIALIZER_H
|
||||
|
||||
#include <array>
|
||||
#include <stack>
|
||||
#include <algorithm>
|
||||
#include "deserializer.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
template<typename Reader, typename TObj>
|
||||
class DeltaDeserializer {
|
||||
public:
|
||||
DeltaDeserializer(Reader &r, const TObj &oldObj, const TObj &newObj)
|
||||
: _deserializer{r},
|
||||
_reader{r},
|
||||
_oldObj{oldObj},
|
||||
_newObj{newObj},
|
||||
_objMemPos(std::deque<details::ObjectMemoryPosition>(1, details::ObjectMemoryPosition{oldObj, newObj})),
|
||||
_isNewElement{false} {
|
||||
};
|
||||
|
||||
template<size_t SIZE = 0, typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
|
||||
DeltaDeserializer &value(T &v) {
|
||||
if (getChangedState(v)) {
|
||||
constexpr size_t ValueSize = SIZE == 0 ? sizeof(T) : SIZE;
|
||||
_reader.template readBytes<ValueSize>(v);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
DeltaDeserializer &object(T &&obj) {
|
||||
if (getChangedState(obj))
|
||||
return serialize(*this, std::forward<T>(obj));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<size_t VSIZE = 1, typename T>
|
||||
DeltaDeserializer &text(std::basic_string<T> &str, size_t maxSize) {
|
||||
if (getChangedState(str)) {
|
||||
_deserializer.template text<VSIZE>(str, maxSize);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<size_t VSIZE = 1, typename T, size_t N>
|
||||
DeltaDeserializer &text(T (&str)[N]) {
|
||||
if (getChangedState(str)) {
|
||||
_deserializer.template text<VSIZE>(str);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
template<typename T, size_t N, typename Fnc>
|
||||
DeltaDeserializer &array(std::array<T, N> &arr, Fnc &&fnc) {
|
||||
if (getChangedState(arr)) {
|
||||
if (!_isNewElement) {
|
||||
const auto old = *_objMemPos.top().getOldObjectField(arr);
|
||||
processContainer(std::begin(old), std::end(old), std::begin(arr), std::end(arr), fnc);
|
||||
} else {
|
||||
for (auto &v:arr)
|
||||
fnc(*this, v);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
template<typename T, size_t N, typename Fnc>
|
||||
DeltaDeserializer &array(T (&arr)[N], Fnc &&fnc) {
|
||||
if (getChangedState(arr)) {
|
||||
if (!_isNewElement) {
|
||||
const auto old = *_objMemPos.top().getOldObjectField(arr);
|
||||
T *tmp = arr;
|
||||
processContainer(old, old + N, tmp, tmp + N, fnc);
|
||||
} else {
|
||||
T *tmp = arr;
|
||||
for (auto i = 0u; i < N; ++i, ++tmp)
|
||||
fnc(*this, *tmp);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, typename Fnc>
|
||||
DeltaDeserializer &container(T &obj, size_t maxSize, Fnc &&fnc) {
|
||||
if (getChangedState(obj)) {
|
||||
size_t newSize{};
|
||||
_reader.readBits(newSize, 32);
|
||||
if (!_isNewElement) {
|
||||
auto old = *_objMemPos.top().getOldObjectField(obj);
|
||||
if (old.size() != newSize)
|
||||
obj.resize(newSize);
|
||||
processContainer(std::begin(old), std::end(old), std::begin(obj), std::end(obj),
|
||||
std::forward<Fnc>(fnc));
|
||||
} else {
|
||||
obj.resize(newSize);
|
||||
for (auto &v:obj)
|
||||
fnc(*this, v);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
Deserializer <Reader> _deserializer;
|
||||
Reader &_reader;
|
||||
|
||||
const TObj &_oldObj;
|
||||
const TObj &_newObj;
|
||||
std::stack<details::ObjectMemoryPosition> _objMemPos;
|
||||
bool _isNewElement;
|
||||
|
||||
template<typename T>
|
||||
bool getChangedState(T &obj) {
|
||||
if (!_isNewElement) {
|
||||
if (!readChangedState()) {
|
||||
obj = *_objMemPos.top().getOldObjectField(obj);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
bool getChangedState(T (&arr)[N]) {
|
||||
if (!_isNewElement) {
|
||||
if (!readChangedState()) {
|
||||
auto old = *_objMemPos.top().getOldObjectField(arr);
|
||||
auto end = arr + N;
|
||||
auto pOld = old;
|
||||
for (auto p = arr; p != end; ++p, ++pOld)
|
||||
*p = *pOld;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename TConstIt, typename TIt, typename Fnc>
|
||||
bool processContainer(TConstIt oldBegin, TConstIt oldEnd, TIt begin, TIt end, Fnc &&fnc) {
|
||||
auto offset = readIndexOffset();
|
||||
auto p = begin;
|
||||
auto pOld = oldBegin;
|
||||
for (; p != end && pOld != oldEnd; ++p, ++pOld) {
|
||||
if (offset) {
|
||||
*p = *pOld;
|
||||
--offset;
|
||||
} else {
|
||||
_objMemPos.emplace(details::ObjectMemoryPosition{*pOld, *p});
|
||||
fnc(*this, *p);
|
||||
_objMemPos.pop();
|
||||
offset = readIndexOffset();
|
||||
}
|
||||
}
|
||||
if (offset != 0 && pOld != oldEnd)
|
||||
return false;
|
||||
_isNewElement = true;
|
||||
for (; p != end; ++p, --offset)
|
||||
fnc(*this, *p);
|
||||
_isNewElement = false;
|
||||
return offset == 0;
|
||||
|
||||
}
|
||||
|
||||
bool readChangedState() {
|
||||
unsigned char res{};
|
||||
_reader.readBits(res, 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
size_t readIndexOffset() {
|
||||
//special case, if items are updated sequentialy
|
||||
unsigned char tmp{};
|
||||
_reader.readBits(tmp, 1);
|
||||
if (tmp) {
|
||||
return 0u;
|
||||
} else {
|
||||
size_t res{};
|
||||
_reader.readBits(tmp, 1);
|
||||
if (tmp > 0)
|
||||
_reader.readBits(res, 4);
|
||||
else
|
||||
_reader.readBits(res, 32);
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
#endif //BITSERY_DELTADESERIALIZER_H
|
||||
@@ -1,220 +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.
|
||||
|
||||
|
||||
|
||||
#ifndef BITSERY_DELTASERIALIZER_H
|
||||
#define BITSERY_DELTASERIALIZER_H
|
||||
|
||||
#include <array>
|
||||
#include <stack>
|
||||
#include <algorithm>
|
||||
#include "serializer.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
template<typename Writter, typename TObj>
|
||||
class DeltaSerializer {
|
||||
public:
|
||||
DeltaSerializer(Writter &w, const TObj &oldObj, const TObj &newObj)
|
||||
: _serializer{w},
|
||||
_writter{w},
|
||||
_oldObj{oldObj},
|
||||
_newObj{newObj},
|
||||
_objMemPos(std::deque<details::ObjectMemoryPosition>(1, details::ObjectMemoryPosition{oldObj, newObj})),
|
||||
_isNewElement{false} {
|
||||
|
||||
};
|
||||
|
||||
template<size_t SIZE = 0, typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
|
||||
DeltaSerializer &value(const T &v) {
|
||||
if (setChangedState(v)) {
|
||||
constexpr size_t ValueSize = SIZE == 0 ? sizeof(T) : SIZE;
|
||||
_writter.template writeBytes<ValueSize>(v);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
DeltaSerializer &object(T &&obj) {
|
||||
if (setChangedState(obj)) {
|
||||
serialize(*this, std::forward<T>(obj));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<size_t VSIZE = 1, typename T>
|
||||
DeltaSerializer &text(const std::basic_string<T> &str, size_t maxSize) {
|
||||
if (setChangedState(str)) {
|
||||
_serializer.template text<VSIZE>(str, maxSize);
|
||||
}
|
||||
return *this;
|
||||
|
||||
}
|
||||
|
||||
template<size_t VSIZE = 1, typename T, size_t N>
|
||||
DeltaSerializer &text(const T (&str)[N]) {
|
||||
if (setChangedState(str)) {
|
||||
_serializer.template text<VSIZE>(str);
|
||||
}
|
||||
return *this;
|
||||
|
||||
}
|
||||
|
||||
template<typename T, size_t N, typename Fnc>
|
||||
DeltaSerializer &array(const std::array<T, N> &arr, Fnc &&fnc) {
|
||||
if (setChangedState(arr)) {
|
||||
if (!_isNewElement) {
|
||||
const auto &old = *_objMemPos.top().getOldObjectField(arr);
|
||||
processContainer(std::begin(old), std::end(old), std::begin(arr), std::end(arr), fnc);
|
||||
} else {
|
||||
for (auto &v:arr)
|
||||
fnc(*this, v);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
template<typename T, size_t N, typename Fnc>
|
||||
DeltaSerializer &array(const T (&arr)[N], Fnc &&fnc) {
|
||||
if (setChangedState(arr)) {
|
||||
if (!_isNewElement) {
|
||||
auto old = *_objMemPos.top().getOldObjectField(arr);
|
||||
const T *tmp = arr;
|
||||
processContainer(old, old + N, tmp, tmp + N, fnc);
|
||||
} else {
|
||||
const T *tmp = arr;
|
||||
for (auto i = 0u; i < N; ++i, ++tmp)
|
||||
fnc(*this, *tmp);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, typename Fnc>
|
||||
DeltaSerializer &container(T &&obj, size_t maxSize, Fnc &&fnc) {
|
||||
if (setChangedState(obj)) {
|
||||
_writter.writeBits(obj.size(), 32);
|
||||
if (!_isNewElement) {
|
||||
auto old = *_objMemPos.top().getOldObjectField(obj);
|
||||
processContainer(std::begin(old), std::end(old), std::begin(obj), std::end(obj),
|
||||
std::forward<Fnc>(fnc));
|
||||
} else {
|
||||
for (auto &v:obj)
|
||||
fnc(*this, v);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
Serializer <Writter> _serializer;
|
||||
Writter &_writter;
|
||||
const TObj &_oldObj;
|
||||
const TObj &_newObj;
|
||||
std::stack<details::ObjectMemoryPosition> _objMemPos;
|
||||
bool _isNewElement;
|
||||
|
||||
template<typename T>
|
||||
bool setChangedState(const T &obj) {
|
||||
if (!_isNewElement) {
|
||||
auto res = !_objMemPos.top().isFieldsEquals(obj);
|
||||
writeChangedState(res);
|
||||
return res;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
bool setChangedState(const T (&arr)[N]) {
|
||||
if (!_isNewElement) {
|
||||
auto old = *_objMemPos.top().getOldObjectField(arr);
|
||||
auto end = arr + N;
|
||||
bool changed{};
|
||||
for (auto p = arr, pOld = old; p != end; ++p, ++pOld) {
|
||||
if (!(*p == *pOld)) {
|
||||
changed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
writeChangedState(changed);
|
||||
return changed;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
template<typename T, typename Fnc>
|
||||
void processContainer(const T oldBegin, const T oldEnd, const T begin, const T end, Fnc &&fnc) {
|
||||
auto misMatch = std::mismatch(oldBegin, oldEnd, begin, end);
|
||||
auto lastChanged = begin;
|
||||
while (misMatch.first != oldEnd && misMatch.second != end) {
|
||||
writeIndexOffset(std::distance(lastChanged, misMatch.second));
|
||||
_objMemPos.emplace(details::ObjectMemoryPosition{*misMatch.first, *misMatch.second});
|
||||
fnc(*this, *misMatch.second);
|
||||
_objMemPos.pop();
|
||||
++misMatch.first;
|
||||
++misMatch.second;
|
||||
lastChanged = misMatch.second;
|
||||
misMatch = std::mismatch(misMatch.first, oldEnd, misMatch.second, end);
|
||||
}
|
||||
auto p = misMatch.second;
|
||||
//write items left
|
||||
writeIndexOffset(std::distance(lastChanged, end));
|
||||
//write old elements
|
||||
for (auto pOld = misMatch.first; p != end && pOld != oldEnd; ++p, ++pOld) {
|
||||
_objMemPos.emplace(details::ObjectMemoryPosition{*pOld, *p});
|
||||
fnc(*this, *p);
|
||||
_objMemPos.pop();
|
||||
}
|
||||
|
||||
//write new elements
|
||||
_isNewElement = true;
|
||||
for (; p != end; ++p)
|
||||
fnc(*this, *p);
|
||||
_isNewElement = false;
|
||||
}
|
||||
|
||||
void writeChangedState(bool state) {
|
||||
_writter.writeBits(state ? 1u : 0u, 1);
|
||||
}
|
||||
|
||||
void writeIndexOffset(const size_t offset) {
|
||||
//special case, if items are updated sequentialy
|
||||
if (offset == 0) {
|
||||
_writter.writeBits(1u, 1);
|
||||
} else {
|
||||
_writter.writeBits(0u, 1);
|
||||
auto smallOffset = offset < 16;
|
||||
_writter.writeBits(smallOffset ? 1u : 0u, 1);
|
||||
if (smallOffset)
|
||||
_writter.writeBits(offset, 4);
|
||||
else
|
||||
_writter.writeBits(offset, 32);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
#endif //BITSERY_DELTASERIALIZER_H
|
||||
@@ -31,321 +31,319 @@
|
||||
namespace bitsery {
|
||||
|
||||
|
||||
template<typename Reader>
|
||||
class Deserializer {
|
||||
template<typename Config>
|
||||
class BasicDeserializer {
|
||||
public:
|
||||
Deserializer(Reader& r):_reader{r}, _isValid{true} {};
|
||||
explicit BasicDeserializer(BasicBufferReader<Config> &r, void* context = nullptr) : _reader{r}, _context{context} {};
|
||||
|
||||
template <typename T>
|
||||
Deserializer& object(T&& obj) {
|
||||
return serialize(*this, std::forward<T>(obj));
|
||||
/*
|
||||
* get serialization context.
|
||||
* this is optional, but might be required for some specific deserialization flows.
|
||||
*/
|
||||
void* getContext() {
|
||||
return _context;
|
||||
}
|
||||
|
||||
//in c++17 change "class" to typename
|
||||
template <template <typename> class Extension, typename TValue, typename Fnc>
|
||||
Deserializer& ext(TValue& v, Fnc&& fnc) {
|
||||
static_assert(!std::is_const<TValue>(), "");
|
||||
Extension<TValue> ext{v};
|
||||
ext.deserialize(*this, std::forward<Fnc>(fnc));
|
||||
return *this;
|
||||
/*
|
||||
* object function
|
||||
*/
|
||||
|
||||
template<typename T>
|
||||
void object(T &&obj) {
|
||||
details::SerializeFunction<BasicDeserializer, T>::invoke(*this, std::forward<T>(obj));
|
||||
}
|
||||
|
||||
template<typename T, typename Fnc>
|
||||
void object(T &&obj, Fnc &&fnc) {
|
||||
fnc(std::forward<T>(obj));
|
||||
};
|
||||
|
||||
/*
|
||||
* value overloads
|
||||
*/
|
||||
|
||||
template<size_t VSIZE, typename T, typename std::enable_if<std::is_floating_point<T>::value>::type* = nullptr>
|
||||
Deserializer& value(T& v) {
|
||||
template<size_t VSIZE, typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
|
||||
void value(T &v) {
|
||||
static_assert(std::numeric_limits<T>::is_iec559, "");
|
||||
if (_isValid) {
|
||||
_isValid = _reader.template readBytes<VSIZE>(reinterpret_cast<details::SAME_SIZE_UNSIGNED<T>&>(v));
|
||||
}
|
||||
return *this;
|
||||
_reader.template readBytes<VSIZE>(reinterpret_cast<details::SAME_SIZE_UNSIGNED<T> &>(v));
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
|
||||
Deserializer& value(T& v) {
|
||||
using UT = std::underlying_type_t<T>;
|
||||
if (_isValid) {
|
||||
_isValid = _reader.template readBytes<VSIZE>(reinterpret_cast<UT&>(v));
|
||||
}
|
||||
return *this;
|
||||
template<size_t VSIZE, typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
|
||||
void value(T &v) {
|
||||
using UT = typename std::underlying_type<T>::type;
|
||||
_reader.template readBytes<VSIZE>(reinterpret_cast<UT &>(v));
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
|
||||
Deserializer& value(T& v) {
|
||||
if (_isValid) {
|
||||
_isValid = _reader.template readBytes<VSIZE>(v);
|
||||
}
|
||||
return *this;
|
||||
template<size_t VSIZE, typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
|
||||
void value(T &v) {
|
||||
_reader.template readBytes<VSIZE>(v);
|
||||
}
|
||||
|
||||
/*
|
||||
* extension functions
|
||||
*/
|
||||
|
||||
template<typename T, typename Ext, typename 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 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 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); });
|
||||
};
|
||||
|
||||
/*
|
||||
* bool
|
||||
*/
|
||||
|
||||
Deserializer& boolBit(bool& v) {
|
||||
if (_isValid) {
|
||||
unsigned char tmp;
|
||||
_isValid = _reader.readBits(tmp, 1);
|
||||
v = tmp == 1;
|
||||
}
|
||||
return *this;
|
||||
void boolBit(bool &v) {
|
||||
uint8_t tmp{};
|
||||
_reader.readBits(tmp, 1);
|
||||
v = tmp == 1;
|
||||
}
|
||||
|
||||
Deserializer& boolByte(bool& v) {
|
||||
if (_isValid) {
|
||||
unsigned char tmp;
|
||||
_isValid = _reader.template readBytes<1>(tmp);
|
||||
if (_isValid)
|
||||
_isValid = tmp < 2;
|
||||
v = tmp == 1;
|
||||
}
|
||||
return *this;
|
||||
void boolByte(bool &v) {
|
||||
unsigned char tmp;
|
||||
_reader.template readBytes<1>(tmp);
|
||||
if (tmp > 1)
|
||||
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
|
||||
v = tmp == 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* range
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
Deserializer& range(T& v, const RangeSpec<T>& range) {
|
||||
if (_isValid) {
|
||||
_isValid = _reader.readBits(reinterpret_cast<details::SAME_SIZE_UNSIGNED<T>&>(v), range.bitsRequired);
|
||||
details::setRangeValue(v, range);
|
||||
if (_isValid)
|
||||
_isValid = details::isRangeValid(v, range);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* substitution overloads
|
||||
*/
|
||||
template<typename T, size_t N, typename Fnc>
|
||||
Deserializer& substitution(T& v, const std::array<T,N>& expectedValues, Fnc&& fnc) {
|
||||
size_t index;
|
||||
range(index, {{}, N + 1});
|
||||
if (_isValid) {
|
||||
if (index)
|
||||
v = expectedValues[index-1];
|
||||
else
|
||||
fnc(*this, v);
|
||||
}
|
||||
return *this;
|
||||
};
|
||||
|
||||
template<size_t VSIZE, typename T, size_t N>
|
||||
Deserializer& substitution(T& v, const std::array<T,N>& expectedValues) {
|
||||
size_t index;
|
||||
range(index, {{}, N + 1});
|
||||
if (_isValid) {
|
||||
if (index)
|
||||
v = expectedValues[index-1];
|
||||
else
|
||||
value<VSIZE>(v);
|
||||
}
|
||||
return *this;
|
||||
};
|
||||
|
||||
template<typename T, size_t N>
|
||||
Deserializer& substitution(T& v, const std::array<T,N>& expectedValues) {
|
||||
size_t index;
|
||||
range(index, {{}, N + 1});
|
||||
if (_isValid) {
|
||||
if (index)
|
||||
v = expectedValues[index-1];
|
||||
else
|
||||
object(v);
|
||||
}
|
||||
return *this;
|
||||
};
|
||||
|
||||
/*
|
||||
* text overloads
|
||||
*/
|
||||
|
||||
template <size_t VSIZE, typename T>
|
||||
Deserializer& text(std::basic_string<T>& str, size_t maxSize) {
|
||||
template<size_t VSIZE, typename T>
|
||||
void text(T &str, size_t maxSize) {
|
||||
static_assert(details::TextTraits<T>::isResizable,
|
||||
"use text(T&) overload without `maxSize` for static containers");
|
||||
size_t size;
|
||||
readSize(size, maxSize);
|
||||
if (_isValid) {
|
||||
str.resize(size);
|
||||
procContainer<VSIZE>(std::begin(str), std::end(str), std::true_type{});
|
||||
}
|
||||
return *this;
|
||||
details::readSize(_reader, size, maxSize);
|
||||
details::TextTraits<T>::resize(str, size);
|
||||
auto begin = std::begin(str);
|
||||
auto end = std::next(begin, size);
|
||||
procContainer<VSIZE>(begin, end, std::true_type{});
|
||||
//null terminated character at the end
|
||||
*end = {};
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T>
|
||||
void text(T &str) {
|
||||
static_assert(!details::TextTraits<T>::isResizable,
|
||||
"use text(T&, size_t) overload with `maxSize` for dynamic containers");
|
||||
size_t size;
|
||||
auto begin = std::begin(str);
|
||||
auto containerEnd = std::end(str);
|
||||
assert(begin != containerEnd);
|
||||
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{});
|
||||
//null terminated character at the end
|
||||
*end = {};
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, size_t N>
|
||||
Deserializer& text(T (&str)[N]) {
|
||||
void text(T (&str)[N]) {
|
||||
size_t size;
|
||||
readSize(size, N-1);
|
||||
if (_isValid) {
|
||||
auto first = std::begin(str);
|
||||
procContainer<VSIZE>(first, std::next(first, size), std::true_type{});
|
||||
//null-terminated string
|
||||
str[size] = {};
|
||||
}
|
||||
return *this;
|
||||
details::readSize(_reader, size, N - 1);
|
||||
auto first = std::begin(str);
|
||||
procContainer<VSIZE>(first, std::next(first, size), std::true_type{});
|
||||
//null-terminated string
|
||||
str[size] = {};
|
||||
}
|
||||
|
||||
/*
|
||||
* container overloads
|
||||
*/
|
||||
|
||||
template <typename T, typename Fnc>
|
||||
Deserializer& container(T&& obj, size_t maxSize, Fnc&& fnc) {
|
||||
decltype(obj.size()) size{};
|
||||
readSize(size, maxSize);
|
||||
if (_isValid) {
|
||||
obj.resize(size);
|
||||
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
|
||||
}
|
||||
return *this;
|
||||
//dynamic size containers
|
||||
|
||||
template<typename T, typename 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{};
|
||||
details::readSize(_reader, size, maxSize);
|
||||
details::ContainerTraits<T>::resize(obj, size);
|
||||
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
|
||||
}
|
||||
|
||||
template <size_t VSIZE, typename T>
|
||||
Deserializer& container(T& obj, size_t maxSize) {
|
||||
decltype(obj.size()) size{};
|
||||
readSize(size, maxSize);
|
||||
if (_isValid) {
|
||||
obj.resize(size);
|
||||
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
|
||||
}
|
||||
return *this;
|
||||
template<size_t VSIZE, typename T>
|
||||
void container(T &obj, size_t maxSize) {
|
||||
static_assert(details::ContainerTraits<T>::isResizable,
|
||||
"use container(T&) overload without `maxSize` for static containers");
|
||||
size_t size{};
|
||||
details::readSize(_reader, size, maxSize);
|
||||
details::ContainerTraits<T>::resize(obj, size);
|
||||
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Deserializer& container(T& obj, size_t maxSize) {
|
||||
decltype(obj.size()) size{};
|
||||
readSize(size, maxSize);
|
||||
if (_isValid) {
|
||||
obj.resize(size);
|
||||
procContainer(std::begin(obj), std::end(obj));
|
||||
}
|
||||
return *this;
|
||||
template<typename T>
|
||||
void container(T &obj, size_t maxSize) {
|
||||
static_assert(details::ContainerTraits<T>::isResizable,
|
||||
"use container(T&) overload without `maxSize` for static containers");
|
||||
size_t size{};
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
|
||||
/*
|
||||
* array overloads (fixed size array (std::array, and c-style array))
|
||||
*/
|
||||
|
||||
//std::array overloads
|
||||
|
||||
template<typename T, size_t N, typename Fnc>
|
||||
Deserializer& array(std::array<T,N> &arr, Fnc && fnc) {
|
||||
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
|
||||
return *this;
|
||||
template<size_t VSIZE, typename T>
|
||||
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, "");
|
||||
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, size_t N>
|
||||
Deserializer& array(std::array<T,N> &arr) {
|
||||
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
Deserializer& array(std::array<T,N> &arr) {
|
||||
procContainer(std::begin(arr), std::end(arr));
|
||||
return *this;
|
||||
template<typename T>
|
||||
void container(T &obj) {
|
||||
static_assert(!details::ContainerTraits<T>::isResizable,
|
||||
"use container(T&, size_t) overload with `maxSize` for dynamic containers");
|
||||
procContainer(std::begin(obj), std::end(obj));
|
||||
}
|
||||
|
||||
//c-style array overloads
|
||||
|
||||
template<typename T, size_t N, typename Fnc>
|
||||
Deserializer& array(T (&arr)[N], Fnc&& fnc) {
|
||||
void container(T (&arr)[N], Fnc &&fnc) {
|
||||
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, size_t N>
|
||||
Deserializer& array(T (&arr)[N]) {
|
||||
void container(T (&arr)[N]) {
|
||||
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
Deserializer& array(T (&arr)[N]) {
|
||||
void container(T (&arr)[N]) {
|
||||
procContainer(std::begin(arr), std::end(arr));
|
||||
return *this;
|
||||
}
|
||||
bool isValid() const {
|
||||
return _isValid;
|
||||
}
|
||||
|
||||
Deserializer& align() {
|
||||
void align() {
|
||||
_reader.align();
|
||||
return *this;
|
||||
}
|
||||
|
||||
//overloads for functions with explicit type size
|
||||
template<typename T> Deserializer& value1(T &&v) { return value<1>(std::forward<T>(v)); }
|
||||
template<typename T> Deserializer& value2(T &&v) { return value<2>(std::forward<T>(v)); }
|
||||
template<typename T> Deserializer& value4(T &&v) { return value<4>(std::forward<T>(v)); }
|
||||
template<typename T> Deserializer& value8(T &&v) { return value<8>(std::forward<T>(v)); }
|
||||
|
||||
template<typename T, size_t N> Deserializer& substitution1
|
||||
(T &v, const std::array<T, N> &expectedValues) { return substitution<1>(v, expectedValues); };
|
||||
template<typename T, size_t N> Deserializer& substitution2
|
||||
(T &v, const std::array<T, N> &expectedValues) { return substitution<2>(v, expectedValues); };
|
||||
template<typename T, size_t N> Deserializer& substitution4
|
||||
(T &v, const std::array<T, N> &expectedValues) { return substitution<4>(v, expectedValues); };
|
||||
template<typename T, size_t N> Deserializer& substitution8
|
||||
(T &v, const std::array<T, N> &expectedValues) { return substitution<8>(v, expectedValues); };
|
||||
template<typename T>
|
||||
void value1b(T &&v) { value<1>(std::forward<T>(v)); }
|
||||
|
||||
template<typename T> Deserializer& text1(std::basic_string<T> &str, size_t maxSize) {
|
||||
return text<1>(str, maxSize); }
|
||||
template<typename T> Deserializer& text2(std::basic_string<T> &str, size_t maxSize) {
|
||||
return text<2>(str, maxSize); }
|
||||
template<typename T> Deserializer& text4(std::basic_string<T> &str, size_t maxSize) {
|
||||
return text<4>(str, maxSize); }
|
||||
template<typename T>
|
||||
void value2b(T &&v) { value<2>(std::forward<T>(v)); }
|
||||
|
||||
template<typename T, size_t N> Deserializer& text1(T (&str)[N]) { return text<1>(str); }
|
||||
template<typename T, size_t N> Deserializer& text2(T (&str)[N]) { return text<2>(str); }
|
||||
template<typename T, size_t N> Deserializer& text4(T (&str)[N]) { return text<4>(str); }
|
||||
template<typename T>
|
||||
void value4b(T &&v) { value<4>(std::forward<T>(v)); }
|
||||
|
||||
template<typename T> Deserializer& container1(T &&obj, size_t maxSize) {
|
||||
return container<1>(std::forward<T>(obj), maxSize); }
|
||||
template<typename T> Deserializer& container2(T &&obj, size_t maxSize) {
|
||||
return container<2>(std::forward<T>(obj), maxSize); }
|
||||
template<typename T> Deserializer& container4(T &&obj, size_t maxSize) {
|
||||
return container<4>(std::forward<T>(obj), maxSize); }
|
||||
template<typename T> Deserializer& container8(T &&obj, size_t maxSize) {
|
||||
return container<8>(std::forward<T>(obj), maxSize); }
|
||||
template<typename T>
|
||||
void value8b(T &&v) { value<8>(std::forward<T>(v)); }
|
||||
|
||||
template<typename T, typename Ext>
|
||||
void ext1b(T &v, Ext &&extension) { ext<1, T, Ext>(v, std::forward<Ext>(extension)); };
|
||||
|
||||
template<typename T, typename Ext>
|
||||
void ext2b(T &v, Ext &&extension) { ext<2, T, Ext>(v, std::forward<Ext>(extension)); };
|
||||
|
||||
template<typename T, typename Ext>
|
||||
void ext4b(T &v, Ext &&extension) { ext<4, T, Ext>(v, std::forward<Ext>(extension)); };
|
||||
|
||||
template<typename T, typename Ext>
|
||||
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); }
|
||||
|
||||
template<typename T>
|
||||
void text2b(T &str, size_t maxSize) { text<2>(str, maxSize); }
|
||||
|
||||
template<typename T>
|
||||
void text4b(T &str, size_t maxSize) { text<4>(str, maxSize); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
void text1b(T (&str)[N]) { text<1>(str); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
void text2b(T (&str)[N]) { text<2>(str); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
void text4b(T (&str)[N]) { text<4>(str); }
|
||||
|
||||
template<typename T>
|
||||
void container1b(T &&obj, size_t maxSize) { container<1>(std::forward<T>(obj), maxSize); }
|
||||
|
||||
template<typename T>
|
||||
void container2b(T &&obj, size_t maxSize) { container<2>(std::forward<T>(obj), maxSize); }
|
||||
|
||||
template<typename T>
|
||||
void container4b(T &&obj, size_t maxSize) { container<4>(std::forward<T>(obj), maxSize); }
|
||||
|
||||
template<typename T>
|
||||
void container8b(T &&obj, size_t maxSize) { container<8>(std::forward<T>(obj), maxSize); }
|
||||
|
||||
template<typename T>
|
||||
void container1b(T &&obj) { container<1>(std::forward<T>(obj)); }
|
||||
|
||||
template<typename T>
|
||||
void container2b(T &&obj) { container<2>(std::forward<T>(obj)); }
|
||||
|
||||
template<typename T>
|
||||
void container4b(T &&obj) { container<4>(std::forward<T>(obj)); }
|
||||
|
||||
template<typename T>
|
||||
void container8b(T &&obj) { container<8>(std::forward<T>(obj)); }
|
||||
|
||||
|
||||
template<typename T, size_t N>
|
||||
void container1b(T (&arr)[N]) { container<1>(arr); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
void container2b(T (&arr)[N]) { container<2>(arr); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
void container4b(T (&arr)[N]) { container<4>(arr); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
void container8b(T (&arr)[N]) { container<8>(arr); }
|
||||
|
||||
private:
|
||||
Reader& _reader;
|
||||
bool _isValid;
|
||||
void readSize(size_t &size, size_t maxSize) {
|
||||
size = {};
|
||||
if (_isValid) {
|
||||
unsigned char firstBit;
|
||||
_isValid = _reader.readBits(firstBit, 1);
|
||||
if (_isValid) {
|
||||
if (firstBit) {
|
||||
_isValid = _reader.readBits(size, 7);
|
||||
} else {
|
||||
unsigned char secondBit;
|
||||
_isValid = _reader.readBits(secondBit, 1);
|
||||
if (_isValid) {
|
||||
if (secondBit) {
|
||||
_isValid = _reader.readBits(size,14);
|
||||
} else {
|
||||
_isValid = _reader.readBits(size,30);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_isValid)
|
||||
_isValid = size <= maxSize;
|
||||
}
|
||||
}
|
||||
BasicBufferReader<Config> &_reader;
|
||||
void* _context;
|
||||
|
||||
//process value types
|
||||
//false_type means that we must process all elements individually
|
||||
template<size_t VSIZE, typename It>
|
||||
void procContainer(It first, It last, std::false_type) {
|
||||
for (;_isValid && first != last; ++first)
|
||||
for (; first != last; ++first)
|
||||
value<VSIZE>(*first);
|
||||
};
|
||||
|
||||
@@ -353,26 +351,38 @@ namespace bitsery {
|
||||
//true_type means, that we can copy whole buffer
|
||||
template<size_t VSIZE, typename It>
|
||||
void procContainer(It first, It last, std::true_type) {
|
||||
if (_isValid && first != last)
|
||||
_isValid = _reader.template readBuffer<VSIZE>(&(*first), std::distance(first, last));
|
||||
if (first != last)
|
||||
_reader.template readBuffer<VSIZE>(&(*first), std::distance(first, last));
|
||||
};
|
||||
|
||||
//process by calling functions
|
||||
template<typename It, typename Fnc>
|
||||
void procContainer(It first, It last, Fnc fnc) {
|
||||
for (;_isValid && first != last; ++first)
|
||||
fnc(*this, *first);
|
||||
for (; first != last; ++first)
|
||||
fnc(*first);
|
||||
};
|
||||
|
||||
//process object types
|
||||
template<typename It>
|
||||
void procContainer(It first, It last) {
|
||||
for (;_isValid && first != last; ++first)
|
||||
for (; first != last; ++first)
|
||||
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
|
||||
using Deserializer = BasicDeserializer<DefaultConfig>;
|
||||
|
||||
}
|
||||
|
||||
|
||||
84
include/bitsery/details/both_common.h
Normal file
84
include/bitsery/details/both_common.h
Normal file
@@ -0,0 +1,84 @@
|
||||
//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_DETAILS_BOTH_COMMON_H
|
||||
#define BITSERY_DETAILS_BOTH_COMMON_H
|
||||
|
||||
#include <cstdint>
|
||||
#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, size_t maxSize) {
|
||||
uint8_t hb{};
|
||||
r.template readBytes<1>(hb);
|
||||
if (hb < 0x80u) {
|
||||
size = hb;
|
||||
} else {
|
||||
uint8_t lb{};
|
||||
r.template readBytes<1>(lb);
|
||||
if (hb & 0x40u) {
|
||||
uint16_t lw{};
|
||||
r.template readBytes<2>(lw);
|
||||
size = ((((hb & 0x3Fu) << 8) | lb) << 16) | lw;
|
||||
} else {
|
||||
size = ((hb & 0x7Fu) << 8) | lb;
|
||||
}
|
||||
}
|
||||
if (size > maxSize) {
|
||||
r.setError(BufferReaderError::INVALID_BUFFER_DATA);
|
||||
size = {};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Writter>
|
||||
void writeSize(Writter& w, const size_t size) {
|
||||
if (size < 0x80u) {
|
||||
w.template writeBytes<1>(static_cast<uint8_t>(size));
|
||||
} else {
|
||||
if (size < 0x4000u) {
|
||||
w.template writeBytes<1>(static_cast<uint8_t>((size >> 8) | 0x80u));
|
||||
w.template writeBytes<1>(static_cast<uint8_t>(size));
|
||||
} else {
|
||||
assert(size < 0x40000000u);
|
||||
w.template writeBytes<1>(static_cast<uint8_t>((size >> 24) | 0xC0u));
|
||||
w.template writeBytes<1>(static_cast<uint8_t>(size >> 16));
|
||||
w.template writeBytes<2>(static_cast<uint16_t>(size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_DETAILS_BOTH_COMMON_H
|
||||
@@ -20,28 +20,41 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#ifndef BITSERY_BUFFER_COMMON_H
|
||||
#define BITSERY_BUFFER_COMMON_H
|
||||
#ifndef BITSERY_DETAILS_BUFFER_COMMON_H
|
||||
#define BITSERY_DETAILS_BUFFER_COMMON_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <stack>
|
||||
#include <cstring>
|
||||
#include "both_common.h"
|
||||
#include "traits.h"
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
/*
|
||||
* endianess
|
||||
*/
|
||||
enum class EndiannessType {
|
||||
LittleEndian,
|
||||
BigEndian
|
||||
template<typename I>
|
||||
struct BufferRange : std::pair<I, I> {
|
||||
using std::pair<I, I>::pair;
|
||||
I begin() const { return this->first; }
|
||||
I end() const { return this->second; }
|
||||
};
|
||||
|
||||
namespace details {
|
||||
|
||||
template<typename T>
|
||||
constexpr size_t BITS_SIZE = sizeof(T) << 3;
|
||||
struct BITS_SIZE:public std::integral_constant<size_t, sizeof(T) << 3> {
|
||||
|
||||
};
|
||||
|
||||
//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 );
|
||||
return __builtin_bswap64(value);
|
||||
#else
|
||||
value = ( value & 0x00000000FFFFFFFF ) << 32 | ( value & 0xFFFFFFFF00000000 ) >> 32;
|
||||
value = ( value & 0x0000FFFF0000FFFF ) << 16 | ( value & 0xFFFF0000FFFF0000 ) >> 16;
|
||||
@@ -50,18 +63,16 @@ namespace bitsery {
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint32_t exec(uint32_t value)
|
||||
{
|
||||
static uint32_t exec(uint32_t value) {
|
||||
#ifdef __GNUC__
|
||||
return __builtin_bswap32( value );
|
||||
return __builtin_bswap32(value);
|
||||
#else
|
||||
return ( value & 0x000000ff ) << 24 | ( value & 0x0000ff00 ) << 8 | ( value & 0x00ff0000 ) >> 8 | ( value & 0xff000000 ) >> 24;
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint16_t exec(uint16_t value)
|
||||
{
|
||||
return ( value & 0x00ff ) << 8 | ( value & 0xff00 ) >> 8;
|
||||
static uint16_t exec(uint16_t value) {
|
||||
return (value & 0x00ff) << 8 | (value & 0xff00) >> 8;
|
||||
}
|
||||
|
||||
static uint8_t exec(uint8_t value) {
|
||||
@@ -69,28 +80,345 @@ namespace bitsery {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TValue>
|
||||
template<typename TValue>
|
||||
TValue swap(TValue value) {
|
||||
constexpr size_t TSize = sizeof(TValue);
|
||||
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));
|
||||
typename std::conditional<TSize == 4, uint32_t, uint64_t>::type>::type>::type;
|
||||
return SwapImpl::exec(static_cast<UT>(value));
|
||||
}
|
||||
|
||||
//add test data in separate struct, because some compilers only support constexpr functions with return-only body
|
||||
struct EndiannessTestData {
|
||||
static constexpr uint32_t _sample4Bytes = 0x01020304;
|
||||
static constexpr uint8_t _sample1stByte = (const uint8_t&)_sample4Bytes;
|
||||
static constexpr uint8_t _sample1stByte = (const uint8_t &) _sample4Bytes;
|
||||
};
|
||||
|
||||
constexpr EndiannessType getSystemEndianness() {
|
||||
static_assert(EndiannessTestData::_sample1stByte == 0x04 || EndiannessTestData::_sample1stByte == 0x01, "system must be either little or big endian");
|
||||
return EndiannessTestData::_sample1stByte == 0x04 ? EndiannessType::LittleEndian : EndiannessType::BigEndian;
|
||||
static_assert(EndiannessTestData::_sample1stByte == 0x04 || EndiannessTestData::_sample1stByte == 0x01,
|
||||
"system must be either little or big endian");
|
||||
return EndiannessTestData::_sample1stByte == 0x04 ? EndiannessType::LittleEndian
|
||||
: EndiannessType::BigEndian;
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct SCRATCH_TYPE {
|
||||
};
|
||||
|
||||
template<>
|
||||
struct SCRATCH_TYPE<uint8_t> {
|
||||
using type = uint16_t;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct SCRATCH_TYPE<uint16_t> {
|
||||
using type = uint32_t;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct SCRATCH_TYPE<uint32_t> {
|
||||
using type = uint64_t;
|
||||
};
|
||||
|
||||
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(TWriter& ) {
|
||||
//write position
|
||||
_sessionIndex.push(_sessions.size());
|
||||
_sessions.emplace_back(0);
|
||||
}
|
||||
|
||||
void end(TWriter& writer) {
|
||||
assert(!_sessionIndex.empty());
|
||||
//change position to session end
|
||||
auto sessionIt = std::next(std::begin(_sessions), _sessionIndex.top());
|
||||
_sessionIndex.pop();
|
||||
auto range = writer.getWrittenRange();
|
||||
*sessionIt = static_cast<size_t>(std::distance(range.begin(), range.end()));
|
||||
}
|
||||
|
||||
void flushSessions(TWriter& writer) {
|
||||
if (_sessions.size()) {
|
||||
assert(_sessionIndex.empty());
|
||||
auto range = writer.getWrittenRange();
|
||||
auto dataSize = static_cast<size_t>(std::distance(range.begin(), range.end()));
|
||||
for(auto& s:_sessions) {
|
||||
details::writeSize(writer, s);
|
||||
}
|
||||
_sessions.clear();
|
||||
|
||||
range = writer.getWrittenRange();
|
||||
auto totalSize = static_cast<size_t>(std::distance(range.begin(), range.end()));
|
||||
//write offset where actual data ends
|
||||
auto sessionsOffset = totalSize - dataSize + 2;//2 bytes for offset data
|
||||
if (sessionsOffset < 0x8000u) {
|
||||
writer.template writeBytes<2>(static_cast<uint16_t>(sessionsOffset));
|
||||
} else {
|
||||
//size doesnt fit in 2 bytes, write 4 bytes instead
|
||||
sessionsOffset+=2;
|
||||
uint16_t low = static_cast<uint16_t>(sessionsOffset);
|
||||
//mark most significant bit, that size is 4 bytes
|
||||
uint16_t high = static_cast<uint16_t>(0x8000u | (sessionsOffset >> 16));
|
||||
writer.template writeBytes<2>(low);
|
||||
writer.template writeBytes<2>(high);
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
std::vector<size_t> _sessions{};
|
||||
std::stack<size_t> _sessionIndex;
|
||||
};
|
||||
|
||||
template <typename TReader, typename TIterator>
|
||||
struct BufferSessionsReader {
|
||||
BufferSessionsReader(TReader& r, TIterator& begin, TIterator& end)
|
||||
:_reader{r},
|
||||
_begin{begin},
|
||||
_pos{begin},
|
||||
_end{end}
|
||||
{
|
||||
}
|
||||
void begin() {
|
||||
if (_sessions.empty())
|
||||
initializeSessions();
|
||||
//save end position for current session
|
||||
_sessionsStack.push(_end);
|
||||
if (_nextSessionIt != std::end(_sessions)) {
|
||||
if (std::distance(_pos, _end) > 0) {
|
||||
//set end position for new session
|
||||
auto newEnd = std::next(_begin, *_nextSessionIt);
|
||||
if (std::distance(newEnd, _end) < 0)
|
||||
{
|
||||
//new session cannot end further than current end
|
||||
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
|
||||
return;
|
||||
}
|
||||
_end = newEnd;
|
||||
++_nextSessionIt;
|
||||
}
|
||||
//if we reached the end, means that there is no more data to read, hence there is no more sessions to advance to
|
||||
} else {
|
||||
//there is no data to read anymore
|
||||
//pos == end or buffer overflow while session is active
|
||||
if (!(_pos == _end || _reader.getError() == BufferReaderError::NO_ERROR)) {
|
||||
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void end() {
|
||||
if (!_sessionsStack.empty()) {
|
||||
//move position to the end of session
|
||||
//can additionaly be checked for session data versioning
|
||||
//_pos == _end : same versions
|
||||
//distance(_pos,_end) > 0: reading newer version
|
||||
//getError() == BUFFER_OVERFLOW: reading older version
|
||||
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(_begin, _end));
|
||||
for (; _nextSessionIt != std::end(_sessions); ++_nextSessionIt) {
|
||||
if (*_nextSessionIt > currPos)
|
||||
break;
|
||||
}
|
||||
}
|
||||
_pos = _end;
|
||||
//restore end position
|
||||
_end = _sessionsStack.top();
|
||||
_sessionsStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
bool hasActiveSessions() const {
|
||||
return _sessionsStack.size() > 0;
|
||||
}
|
||||
|
||||
private:
|
||||
TReader& _reader;
|
||||
TIterator _begin;
|
||||
TIterator& _pos;
|
||||
TIterator& _end;
|
||||
|
||||
std::vector<size_t> _sessions{};
|
||||
std::vector<size_t>::iterator _nextSessionIt{};
|
||||
std::stack<TIterator> _sessionsStack{};
|
||||
|
||||
void initializeSessions() {
|
||||
//save current position
|
||||
auto currPos = _pos;
|
||||
//read size
|
||||
if (std::distance(_pos, _end) < 2) {
|
||||
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
|
||||
return;
|
||||
}
|
||||
auto endSessionsSizesIt = std::next(_end, -2);
|
||||
_pos = endSessionsSizesIt;
|
||||
size_t sessionsOffset{};
|
||||
uint16_t high;
|
||||
_reader.template readBytes<2>(high);
|
||||
|
||||
|
||||
if (high >= 0x8000u) {
|
||||
endSessionsSizesIt = std::next(endSessionsSizesIt, -2);
|
||||
_pos = endSessionsSizesIt;
|
||||
if (std::distance(_begin, _pos) < 0) {
|
||||
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
|
||||
return;
|
||||
}
|
||||
uint16_t low;
|
||||
_reader.template readBytes<2>(low);
|
||||
//mask out last bit
|
||||
high &= 0x7FFFu;
|
||||
sessionsOffset = static_cast<size_t>((high << 16) | low);
|
||||
|
||||
} else
|
||||
sessionsOffset = high;
|
||||
|
||||
auto bufferSize = std::distance(_begin, _end);
|
||||
if (static_cast<size_t>(bufferSize) < sessionsOffset) {
|
||||
_reader.setError(BufferReaderError::INVALID_BUFFER_DATA);
|
||||
return;
|
||||
}
|
||||
//we can initialy resizes to this value, and we'll shrink it after reading
|
||||
//read session sizes
|
||||
auto sessionsIt = std::back_inserter(_sessions);
|
||||
_pos = std::next(_end, -sessionsOffset);
|
||||
while (std::distance(_pos, endSessionsSizesIt) > 0) {
|
||||
size_t size;
|
||||
details::readSize(_reader, size, bufferSize);
|
||||
*sessionsIt++ = size;
|
||||
}
|
||||
_sessions.shrink_to_fit();
|
||||
//set iterators to data
|
||||
_pos = currPos;
|
||||
_end = std::next(_end, -sessionsOffset);
|
||||
_nextSessionIt = std::begin(_sessions);//set before first session;
|
||||
}
|
||||
};
|
||||
|
||||
//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> {
|
||||
public:
|
||||
using TValue = typename BufferContainerTraits<Buffer>::TValue;
|
||||
using TIterator = typename BufferContainerTraits<Buffer>::TIterator;
|
||||
using TDifference = typename BufferContainerTraits<Buffer>::TDifference;
|
||||
|
||||
|
||||
explicit WriteBufferContext(Buffer &buffer)
|
||||
: _buffer{buffer},
|
||||
_outIt{std::addressof(*std::begin(buffer))},
|
||||
_end{std::addressof(*std::end(buffer))}
|
||||
{
|
||||
}
|
||||
|
||||
void write(const TValue *data, size_t size) {
|
||||
assert(std::distance(_outIt, _end) >= static_cast<TDifference>(size));
|
||||
memcpy(_outIt, data, size);
|
||||
_outIt += size;
|
||||
}
|
||||
|
||||
BufferRange<TIterator> getWrittenRange() const {
|
||||
auto begin = std::begin(_buffer);
|
||||
return BufferRange<TIterator>{begin, std::next(begin, _outIt - std::addressof(*begin))};
|
||||
}
|
||||
|
||||
private:
|
||||
Buffer &_buffer;
|
||||
TValue* _outIt;
|
||||
TValue* _end;
|
||||
};
|
||||
|
||||
template<typename Buffer>
|
||||
class WriteBufferContext<Buffer, true> {
|
||||
public:
|
||||
using TValue = typename BufferContainerTraits<Buffer>::TValue;
|
||||
using TIterator = typename BufferContainerTraits<Buffer>::TIterator;
|
||||
using TDifference = typename BufferContainerTraits<Buffer>::TDifference;
|
||||
|
||||
explicit WriteBufferContext(Buffer &buffer)
|
||||
: _buffer{buffer}
|
||||
{
|
||||
getIterators(0);
|
||||
}
|
||||
|
||||
void write(const TValue *data, size_t size) {
|
||||
if ((_end - _outIt) >= static_cast< TDifference >(size)) {
|
||||
std::memcpy(_outIt, data, size);
|
||||
_outIt += size;
|
||||
} else {
|
||||
//get current position before invalidating iterators
|
||||
auto pos = std::distance(std::addressof(*std::begin(_buffer)), _outIt);
|
||||
//increase container size
|
||||
BufferContainerTraits<Buffer>::increaseBufferSize(_buffer);
|
||||
//restore iterators
|
||||
getIterators(pos);
|
||||
write(data, size);
|
||||
}
|
||||
}
|
||||
|
||||
BufferRange<TIterator> getWrittenRange() const {
|
||||
auto begin = std::begin(_buffer);
|
||||
return BufferRange<TIterator>{begin, std::next(begin, _outIt - std::addressof(*begin))};
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void getIterators(TDifference writePos) {
|
||||
_end = std::addressof(*std::end(_buffer));
|
||||
_outIt = std::addressof(*std::next(std::begin(_buffer), writePos));
|
||||
}
|
||||
Buffer &_buffer;
|
||||
TValue* _outIt;
|
||||
TValue* _end;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif //BITSERY_BUFFER_COMMON_H
|
||||
#endif //BITSERY_DETAILS_BUFFER_COMMON_H
|
||||
|
||||
@@ -20,12 +20,11 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#ifndef BITSERY_SERIALIZATION_COMMON_H
|
||||
#define BITSERY_SERIALIZATION_COMMON_H
|
||||
#ifndef BITSERY_DETAILS_SERIALIZATION_COMMON_H
|
||||
#define BITSERY_DETAILS_SERIALIZATION_COMMON_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
#include <array>
|
||||
#include "both_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
@@ -33,168 +32,51 @@ namespace bitsery {
|
||||
|
||||
template<typename T, typename Enable = void>
|
||||
struct SAME_SIZE_UNSIGNED_TYPE {
|
||||
typedef std::make_unsigned_t<T> type;
|
||||
using type = typename std::make_unsigned<T>::type;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct SAME_SIZE_UNSIGNED_TYPE<T, typename std::enable_if<std::is_enum<T>::value>::type> {
|
||||
typedef std::make_unsigned_t<std::underlying_type_t<T>> type;
|
||||
using type = typename std::make_unsigned<typename std::underlying_type<T>::type>::type;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct SAME_SIZE_UNSIGNED_TYPE<T, typename std::enable_if<std::is_floating_point<T>::value>::type> {
|
||||
typedef std::conditional_t<std::is_same<T, float>::value, uint32_t, uint64_t> type;
|
||||
using type = typename std::conditional<std::is_same<T, float>::value, uint32_t, uint64_t>::type;
|
||||
};
|
||||
|
||||
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
|
||||
* functions for object serialization
|
||||
*/
|
||||
|
||||
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<std::underlying_type_t<T>>(min),
|
||||
static_cast<std::underlying_type_t<T>>(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>
|
||||
auto 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>
|
||||
auto getRangeValue(const T &v, const RangeSpec<T> &r) {
|
||||
return static_cast<SAME_SIZE_UNSIGNED<T>>(v) - static_cast<SAME_SIZE_UNSIGNED<T>>(r.min);
|
||||
};
|
||||
|
||||
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
|
||||
auto 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 = std::underlying_type_t<T>;
|
||||
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 = std::underlying_type_t<T>;
|
||||
return !(static_cast<VT>(r.min) > static_cast<VT>(v)
|
||||
|| static_cast<VT>(v) > static_cast<VT>(r.max));
|
||||
}
|
||||
|
||||
/*
|
||||
* functions for substitution
|
||||
*/
|
||||
|
||||
template<typename T, size_t N>
|
||||
size_t findSubstitutionIndex(const T &v, const std::array<T, N> &defValues) {
|
||||
auto index{1u};
|
||||
for (auto &d:defValues) {
|
||||
if (d == v)
|
||||
return index;
|
||||
++index;
|
||||
template<typename S, typename T, typename Enabled = void>
|
||||
struct SerializeFunction {
|
||||
static void invoke(S &s, T &v) {
|
||||
static_assert(!std::is_void<Enabled>::value,
|
||||
"\nPlease define 'serialize' function for your type:\n"
|
||||
" template<typename S>\n"
|
||||
" void serialize(S& s, <YourType>& o)\n"
|
||||
" {\n"
|
||||
" ...\n"
|
||||
" }\n");
|
||||
}
|
||||
return 0u;
|
||||
};
|
||||
|
||||
template<typename S, typename T>
|
||||
struct SerializeFunction<S, T, typename std::enable_if<
|
||||
std::is_same<void, decltype(serialize(std::declval<S &>(), std::declval<T &>()))>::value
|
||||
>::type> {
|
||||
static void invoke(S &s, T &v) {
|
||||
serialize(s, v);
|
||||
}
|
||||
};
|
||||
|
||||
//used for extensions, when extension TValue = void
|
||||
struct DummyType {
|
||||
};
|
||||
|
||||
/*
|
||||
* delta functions
|
||||
@@ -205,7 +87,8 @@ namespace bitsery {
|
||||
|
||||
template<typename T>
|
||||
ObjectMemoryPosition(const T &oldObj, const T &newObj)
|
||||
:ObjectMemoryPosition{reinterpret_cast<const char *>(&oldObj), reinterpret_cast<const char *>(&newObj),
|
||||
:ObjectMemoryPosition{reinterpret_cast<const char *>(&oldObj),
|
||||
reinterpret_cast<const char *>(&newObj),
|
||||
sizeof(T)} {
|
||||
}
|
||||
|
||||
@@ -236,4 +119,4 @@ namespace bitsery {
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_SERIALIZATION_COMMON_H
|
||||
#endif //BITSERY_DETAILS_SERIALIZATION_COMMON_H
|
||||
|
||||
165
include/bitsery/details/traits.h
Normal file
165
include/bitsery/details/traits.h
Normal file
@@ -0,0 +1,165 @@
|
||||
//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_DETAILS_TRAITS_H
|
||||
#define BITSERY_DETAILS_TRAITS_H
|
||||
|
||||
#include <type_traits>
|
||||
#include <string>
|
||||
|
||||
namespace bitsery {
|
||||
namespace details {
|
||||
|
||||
/*
|
||||
* helper traits that is used internaly, or by other traits
|
||||
*/
|
||||
template <typename T, typename = int>
|
||||
struct IsResizable : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct IsResizable <T, decltype((void)std::declval<T>().resize(1u), 0)> : std::true_type {};
|
||||
|
||||
/*
|
||||
* core library traits, used to extend library for custom types
|
||||
*/
|
||||
|
||||
//traits for extension
|
||||
template<typename Extension, typename T>
|
||||
struct ExtensionTraits {
|
||||
//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
|
||||
//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;
|
||||
};
|
||||
|
||||
//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;
|
||||
|
||||
//resize function, called only if container is resizable
|
||||
static void resize(T& container, size_t size) {
|
||||
container.resize(size);
|
||||
}
|
||||
|
||||
//get container size
|
||||
static size_t size(const T& container) {
|
||||
return container.size();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//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 {
|
||||
|
||||
static constexpr bool isResizable = true;
|
||||
|
||||
//resize is without null-terminated character as with std::string,
|
||||
//but null terminated character will always be written
|
||||
//if you container doesn't add null-terminated character automaticaly, resize it to size+1;
|
||||
static void resize(T& container, size_t size) {
|
||||
container.resize(size);
|
||||
}
|
||||
|
||||
//used for serialization to get text length
|
||||
//length is until null-terminated character, size and length might not be equal
|
||||
static size_t length(const T& container) {
|
||||
auto begin = std::begin(container);
|
||||
using TValue = typename std::decay<decltype(*begin)>::type;
|
||||
return std::char_traits<TValue>::length(std::addressof(*begin));
|
||||
}
|
||||
};
|
||||
|
||||
//text traits specialization for std::string
|
||||
//for std::string return length as size(), for faster performance, so we don't need to traverse string to find null-terminated characeter
|
||||
//although it is not correct behaviour, meaning that string might have null-terminated characters in the middle,
|
||||
//but in this case it your decision if you store buffer in string and serialize it as a text.
|
||||
template<typename ... Args>
|
||||
struct TextTraits<std::basic_string<Args...>> {
|
||||
|
||||
static constexpr bool isResizable = true;
|
||||
|
||||
//resize is without null-terminated character as with std::string,
|
||||
//but null terminated character will always be written
|
||||
//if you container doesn't add null-terminated character automaticaly, resize it to size+1;
|
||||
static void resize(std::basic_string<Args...>& container, size_t size) {
|
||||
container.resize(size);
|
||||
}
|
||||
|
||||
//used for serialization to get text length
|
||||
//length is until null-terminated character, size and length might not be equal
|
||||
static size_t length(const std::basic_string<Args...>& container) {
|
||||
return container.size();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//traits only for buffer reader/writer
|
||||
template <typename T>
|
||||
struct BufferContainerTraits: public ContainerTraits<T> {
|
||||
//this function is only used by BufferWriter, when writing data to buffer,
|
||||
//it is called only current buffer size is not enough to write.
|
||||
//it is used to dramaticaly improve performance by updating buffer directly
|
||||
//instead of using back_insert_iterator to append each byte to buffer.
|
||||
//thats why BufferWriter return range iterators
|
||||
static void increaseBufferSize(T& container) {
|
||||
//use default implementation behaviour;
|
||||
//call push_back to use default resize strategy
|
||||
container.push_back({});
|
||||
//after allocation resize to take all capacity
|
||||
container.resize(container.capacity());
|
||||
}
|
||||
|
||||
using TDifference = typename T::difference_type;
|
||||
using TIterator = typename T::iterator;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_DETAILS_TRAITS_H
|
||||
81
include/bitsery/ext/container_map.h
Normal file
81
include/bitsery/ext/container_map.h
Normal file
@@ -0,0 +1,81 @@
|
||||
//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_CONTAINER_MAP_H
|
||||
#define BITSERY_EXT_CONTAINER_MAP_H
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
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);
|
||||
|
||||
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 {
|
||||
using TKey = typename T::key_type;
|
||||
using TValue = typename T::mapped_type;
|
||||
|
||||
size_t size{};
|
||||
details::readSize(reader, size, _maxSize);
|
||||
auto hint = obj.begin();
|
||||
obj.clear();
|
||||
|
||||
for (auto i = 0u; i < size; ++i) {
|
||||
TKey key;
|
||||
TValue value;
|
||||
fnc(key, value);
|
||||
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> {
|
||||
using TValue = void;
|
||||
static constexpr bool SupportValueOverload = false;
|
||||
static constexpr bool SupportObjectOverload = false;
|
||||
static constexpr bool SupportLambdaOverload = true;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif //BITSERY_EXT_CONTAINER_MAP_H
|
||||
91
include/bitsery/ext/entropy.h
Normal file
91
include/bitsery/ext/entropy.h
Normal 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
|
||||
65
include/bitsery/ext/growable.h
Normal file
65
include/bitsery/ext/growable.h
Normal 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
|
||||
@@ -24,43 +24,68 @@
|
||||
#ifndef BITSERY_EXT_OPTIONAL_H
|
||||
#define BITSERY_EXT_OPTIONAL_H
|
||||
|
||||
|
||||
//this module do not include optional, but expects it to be declared in std::optional
|
||||
//if you're using experimental optional from <experimental/optional>
|
||||
//add it in std namespace like this:
|
||||
//namespace std {
|
||||
// template <typename T>
|
||||
// using optional = experimental::optional<T>;
|
||||
//}
|
||||
#include <type_traits>
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
using std_optional = ::std::optional<T>;
|
||||
|
||||
template <typename T>
|
||||
class optional {
|
||||
class Optional {
|
||||
public:
|
||||
explicit optional(T& v):_value{v} {
|
||||
|
||||
template<typename T>
|
||||
constexpr void assertType() const {
|
||||
using TOpt = typename std::remove_cv<T>::type;
|
||||
using TVal = typename TOpt::value_type;
|
||||
static_assert(std::is_same<TOpt, std_optional<TVal>>(), "");
|
||||
static_assert(std::is_default_constructible<TVal>::value, "");
|
||||
};
|
||||
template <typename TSerializer, typename Fnc>
|
||||
void serialize(TSerializer& ser, const Fnc& fnc) {
|
||||
ser.boolByte(static_cast<bool>(_value));
|
||||
if (_value)
|
||||
fnc(ser, *_value);
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &, const T &obj, Fnc &&fnc) const {
|
||||
assertType<T>();
|
||||
ser.boolByte(static_cast<bool>(obj));
|
||||
if (obj)
|
||||
fnc(const_cast<typename T::value_type & >(*obj));
|
||||
}
|
||||
template <typename TSerializer, typename Fnc>
|
||||
void deserialize(TSerializer& ser, const Fnc& fnc) {
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &des, Reader &, T &obj, Fnc &&fnc) const {
|
||||
assertType<T>();
|
||||
bool exists{};
|
||||
ser.boolByte(exists);
|
||||
des.boolByte(exists);
|
||||
if (exists) {
|
||||
typename T::value_type tmp{};
|
||||
fnc(ser, tmp);
|
||||
_value = tmp;
|
||||
fnc(tmp);
|
||||
obj = tmp;
|
||||
} else {
|
||||
_value = T{};
|
||||
//experimental optional doesnt have .reset method
|
||||
obj = T{};
|
||||
}
|
||||
}
|
||||
private:
|
||||
T& _value;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace details {
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
206
include/bitsery/ext/value_range.h
Normal file
206
include/bitsery/ext/value_range.h
Normal 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
|
||||
@@ -21,284 +21,322 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
|
||||
#ifndef BITSERY_SERIALIZER_H
|
||||
#define BITSERY_SERIALIZER_H
|
||||
|
||||
#include "common.h"
|
||||
#include <array>
|
||||
#include "details/serialization_common.h"
|
||||
#include <cassert>
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
template<typename Writter>
|
||||
class Serializer {
|
||||
public:
|
||||
Serializer(Writter &w) : _writter{w} {};
|
||||
|
||||
template<typename T>
|
||||
Serializer& object(const T &obj) {
|
||||
return serialize(*this, obj);
|
||||
|
||||
|
||||
template<typename Config>
|
||||
class BasicSerializer {
|
||||
public:
|
||||
explicit BasicSerializer(BasicBufferWriter<Config> &w, void* context = nullptr) : _writter{w}, _context{context} {};
|
||||
|
||||
/*
|
||||
* get serialization context.
|
||||
* this is optional, but might be required for some specific serialization flows.
|
||||
*/
|
||||
void* getContext() {
|
||||
return _context;
|
||||
}
|
||||
|
||||
//in c++17 change "class" to typename
|
||||
template <template <typename> class Extension, typename TValue, typename Fnc>
|
||||
Serializer& ext(const TValue& v, Fnc&& fnc ) {
|
||||
Extension<const TValue> ext{v};
|
||||
ext.serialize(*this, std::forward<Fnc>(fnc));
|
||||
return *this;
|
||||
};
|
||||
/*
|
||||
* object function
|
||||
*/
|
||||
template<typename T>
|
||||
void object(const T &obj) {
|
||||
details::SerializeFunction<BasicSerializer, T>::invoke(*this, const_cast<T& >(obj));
|
||||
}
|
||||
|
||||
template<typename T, typename Fnc>
|
||||
void object(const T &obj, Fnc &&fnc) {
|
||||
fnc(const_cast<T& >(obj));
|
||||
};
|
||||
|
||||
/*
|
||||
* value overloads
|
||||
*/
|
||||
|
||||
template<size_t VSIZE, typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
|
||||
Serializer& value(const T &v) {
|
||||
void value(const T &v) {
|
||||
static_assert(std::numeric_limits<T>::is_iec559, "");
|
||||
_writter.template writeBytes<VSIZE>(reinterpret_cast<const details::SAME_SIZE_UNSIGNED<T> &>(v));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
|
||||
Serializer& value(const T &v) {
|
||||
_writter.template writeBytes<VSIZE>(reinterpret_cast<const std::underlying_type_t<T> &>(v));
|
||||
return *this;
|
||||
void value(const T &v) {
|
||||
_writter.template writeBytes<VSIZE>(reinterpret_cast<const typename std::underlying_type<T>::type &>(v));
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
|
||||
Serializer& value(const T &v) {
|
||||
void value(const T &v) {
|
||||
_writter.template writeBytes<VSIZE>(v);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* extension functions
|
||||
*/
|
||||
|
||||
template<typename T, typename Ext, typename 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 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 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); });
|
||||
};
|
||||
|
||||
/*
|
||||
* bool
|
||||
*/
|
||||
|
||||
Serializer& boolBit(bool v) {
|
||||
void boolBit(bool v) {
|
||||
_writter.writeBits(static_cast<unsigned char>(v ? 1 : 0), 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Serializer& boolByte(bool v) {
|
||||
void boolByte(bool v) {
|
||||
_writter.template writeBytes<1>(static_cast<unsigned char>(v ? 1 : 0));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* range
|
||||
*/
|
||||
|
||||
template<typename T>
|
||||
Serializer& range(const T &v, const RangeSpec<T> &range) {
|
||||
assert(details::isRangeValid(v, range));
|
||||
_writter.template writeBits<decltype(details::getRangeValue(v, range))>(details::getRangeValue(v, range), range.bitsRequired);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* substitution overloads
|
||||
*/
|
||||
template<typename T, size_t N, typename Fnc>
|
||||
Serializer& substitution(const T &v, const std::array<T, N> &expectedValues, Fnc &&fnc) {
|
||||
auto index = details::findSubstitutionIndex(v, expectedValues);
|
||||
range(index, {{}, N +1});
|
||||
if (!index)
|
||||
fnc(*this, v);
|
||||
return *this;
|
||||
};
|
||||
|
||||
template<size_t VSIZE, typename T, size_t N>
|
||||
Serializer& substitution(const T &v, const std::array<T, N> &expectedValues) {
|
||||
auto index = details::findSubstitutionIndex(v, expectedValues);
|
||||
range(index, {{}, N +1});
|
||||
if (!index)
|
||||
value<VSIZE>(v);
|
||||
return *this;
|
||||
};
|
||||
|
||||
template<typename T, size_t N>
|
||||
Serializer& substitution(const T &v, const std::array<T, N> &expectedValues) {
|
||||
auto index = details::findSubstitutionIndex(v, expectedValues);
|
||||
range(index, {{}, N +1});
|
||||
if (!index)
|
||||
object(v);
|
||||
return *this;
|
||||
};
|
||||
|
||||
/*
|
||||
* text overloads
|
||||
*/
|
||||
|
||||
template<size_t VSIZE, typename T>
|
||||
Serializer& text(const std::basic_string<T> &str, size_t maxSize) {
|
||||
assert(str.size() <= maxSize);
|
||||
auto first = std::begin(str);
|
||||
auto last = std::end(str);
|
||||
writeSize(std::distance(first, last));
|
||||
procContainer<VSIZE>(first, last, std::true_type{});
|
||||
return *this;
|
||||
void text(const T &str, size_t maxSize) {
|
||||
static_assert(details::TextTraits<T>::isResizable,
|
||||
"use text(const T&) overload without `maxSize` for static container");
|
||||
auto size = details::TextTraits<T>::length(str);
|
||||
//size can be equal to maxSize
|
||||
assert(size <= maxSize);
|
||||
details::writeSize(_writter, size);
|
||||
auto begin = std::begin(str);
|
||||
procContainer<VSIZE>(begin, std::next(begin, size), std::true_type{});
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T>
|
||||
void text(const T &str) {
|
||||
static_assert(!details::TextTraits<T>::isResizable,
|
||||
"use text(const T&, size_t) overload with `maxSize` for dynamic containers");
|
||||
auto size = details::TextTraits<T>::length(str);
|
||||
auto begin = std::begin(str);
|
||||
auto end = std::end(str);
|
||||
//size must be less than container capacity, because we need to store null-terminated character
|
||||
assert(size < std::distance(begin, end));
|
||||
details::writeSize(_writter, size);
|
||||
|
||||
procContainer<VSIZE>(begin, std::next(begin, size), std::true_type{});
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, size_t N>
|
||||
Serializer& text(const T (&str)[N]) {
|
||||
auto first = std::begin(str);
|
||||
auto last = std::next(first, std::min(std::char_traits<T>::length(str), N - 1));
|
||||
writeSize(std::distance(first, last));
|
||||
procContainer<VSIZE>(first, last, std::true_type{});
|
||||
return *this;
|
||||
void text(const T (&str)[N]) {
|
||||
auto size = details::TextTraits<T[N]>::length(str);
|
||||
assert(size < N);
|
||||
details::writeSize(_writter, size);
|
||||
auto begin = std::begin(str);
|
||||
procContainer<VSIZE>(begin, std::next(begin, size), std::true_type{});
|
||||
}
|
||||
|
||||
/*
|
||||
* container overloads
|
||||
*/
|
||||
|
||||
//dynamic size containers
|
||||
|
||||
template<typename T, typename Fnc>
|
||||
Serializer& container(const T &obj, size_t maxSize, Fnc &&fnc) {
|
||||
assert(obj.size() <= maxSize);
|
||||
writeSize(obj.size());
|
||||
void container(const T &obj, size_t maxSize, Fnc &&fnc) {
|
||||
|
||||
static_assert(details::ContainerTraits<T>::isResizable,
|
||||
"use container(const T&, Fnc) overload without `maxSize` for static containers");
|
||||
auto size = details::ContainerTraits<T>::size(obj);
|
||||
assert(size <= maxSize);
|
||||
details::writeSize(_writter, size);
|
||||
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T>
|
||||
Serializer& container(const T &obj, size_t maxSize) {
|
||||
void container(const T &obj, size_t maxSize) {
|
||||
static_assert(details::ContainerTraits<T>::isResizable,
|
||||
"use container(const T&) overload without `maxSize` for static containers");
|
||||
static_assert(VSIZE > 0, "");
|
||||
assert(obj.size() <= maxSize);
|
||||
writeSize(obj.size());
|
||||
auto size = details::ContainerTraits<T>::size(obj);
|
||||
assert(size <= maxSize);
|
||||
details::writeSize(_writter, size);
|
||||
//todo optimisation is possible for contigous containers, but currently there is no compile-time check for this
|
||||
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
Serializer& container(const T &obj, size_t maxSize) {
|
||||
assert(obj.size() <= maxSize);
|
||||
writeSize(obj.size());
|
||||
void container(const T &obj, size_t maxSize) {
|
||||
static_assert(details::ContainerTraits<T>::isResizable,
|
||||
"use container(const T&) overload without `maxSize` for static containers");
|
||||
auto size = details::ContainerTraits<T>::size(obj);
|
||||
assert(size <= maxSize);
|
||||
details::writeSize(_writter, size);
|
||||
procContainer(std::begin(obj), std::end(obj));
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* array overloads (fixed size array (std::array, and c-style array))
|
||||
*/
|
||||
//fixed size containers
|
||||
|
||||
//std::array overloads
|
||||
|
||||
template<typename T, size_t N, typename Fnc>
|
||||
Serializer& array(const std::array<T, N> &arr, Fnc &&fnc) {
|
||||
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
|
||||
return *this;
|
||||
template<typename T, typename Fnc, typename std::enable_if<!std::is_integral<Fnc>::value>::type * = nullptr>
|
||||
void container(const T &obj, Fnc &&fnc) {
|
||||
static_assert(!details::ContainerTraits<T>::isResizable,
|
||||
"use container(const T&, size_t, Fnc) overload with `maxSize` for dynamic containers");
|
||||
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, size_t N>
|
||||
Serializer& array(const std::array<T, N> &arr) {
|
||||
template<size_t VSIZE, typename T>
|
||||
void container(const T &obj) {
|
||||
static_assert(!details::ContainerTraits<T>::isResizable,
|
||||
"use container(const T&, size_t) overload with `maxSize` for dynamic containers");
|
||||
static_assert(VSIZE > 0, "");
|
||||
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
|
||||
return *this;
|
||||
//todo optimisation is possible for contigous containers, but currently there is no compile-time check for this
|
||||
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::false_type{});
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
Serializer& array(const std::array<T, N> &arr) {
|
||||
procContainer(std::begin(arr), std::end(arr));
|
||||
return *this;
|
||||
template<typename T>
|
||||
void container(const T &obj) {
|
||||
static_assert(!details::ContainerTraits<T>::isResizable,
|
||||
"use container(const T&, size_t) overload with `maxSize` for dynamic containers");
|
||||
procContainer(std::begin(obj), std::end(obj));
|
||||
}
|
||||
|
||||
//c-style array overloads
|
||||
|
||||
template<typename T, size_t N, typename Fnc>
|
||||
Serializer& array(const T (&arr)[N], Fnc &&fnc) {
|
||||
void container(const T (&arr)[N], Fnc &&fnc) {
|
||||
procContainer(std::begin(arr), std::end(arr), std::forward<Fnc>(fnc));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, size_t N>
|
||||
Serializer& array(const T (&arr)[N]) {
|
||||
void container(const T (&arr)[N]) {
|
||||
static_assert(VSIZE > 0, "");
|
||||
procContainer<VSIZE>(std::begin(arr), std::end(arr), std::true_type{});
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
Serializer& array(const T (&arr)[N]) {
|
||||
void container(const T (&arr)[N]) {
|
||||
procContainer(std::begin(arr), std::end(arr));
|
||||
return *this;
|
||||
}
|
||||
|
||||
Serializer& align() {
|
||||
void align() {
|
||||
_writter.align();
|
||||
return *this;
|
||||
}
|
||||
|
||||
//overloads for functions with explicit type size
|
||||
template<typename T> Serializer& value1(T &&v) { return value<1>(std::forward<T>(v)); }
|
||||
template<typename T> Serializer& value2(T &&v) { return value<2>(std::forward<T>(v)); }
|
||||
template<typename T> Serializer& value4(T &&v) { return value<4>(std::forward<T>(v)); }
|
||||
template<typename T> Serializer& value8(T &&v) { return value<8>(std::forward<T>(v)); }
|
||||
|
||||
template<typename T, size_t N> Serializer& substitution1
|
||||
(const T &v, const std::array<T, N> &expectedValues) { return substitution<1>(v, expectedValues); };
|
||||
template<typename T, size_t N> Serializer& substitution2
|
||||
(const T &v, const std::array<T, N> &expectedValues) { return substitution<2>(v, expectedValues); };
|
||||
template<typename T, size_t N> Serializer& substitution4
|
||||
(const T &v, const std::array<T, N> &expectedValues) { return substitution<4>(v, expectedValues); };
|
||||
template<typename T, size_t N> Serializer& substitution8
|
||||
(const T &v, const std::array<T, N> &expectedValues) { return substitution<8>(v, expectedValues); };
|
||||
template<typename T>
|
||||
void value1b(T &&v) { value<1>(std::forward<T>(v)); }
|
||||
|
||||
template<typename T> Serializer& text1(const std::basic_string<T> &str, size_t maxSize) {
|
||||
return text<1>(str, maxSize); }
|
||||
template<typename T> Serializer& text2(const std::basic_string<T> &str, size_t maxSize) {
|
||||
return text<2>(str, maxSize); }
|
||||
template<typename T> Serializer& text4(const std::basic_string<T> &str, size_t maxSize) {
|
||||
return text<4>(str, maxSize); }
|
||||
template<typename T>
|
||||
void value2b(T &&v) { value<2>(std::forward<T>(v)); }
|
||||
|
||||
template<typename T, size_t N> Serializer& text1(const T (&str)[N]) { return text<1>(str); }
|
||||
template<typename T, size_t N> Serializer& text2(const T (&str)[N]) { return text<2>(str); }
|
||||
template<typename T, size_t N> Serializer& text4(const T (&str)[N]) { return text<4>(str); }
|
||||
template<typename T>
|
||||
void value4b(T &&v) { value<4>(std::forward<T>(v)); }
|
||||
|
||||
template<typename T> Serializer& container1(T &&obj, size_t maxSize) {
|
||||
return container<1>(std::forward<T>(obj), maxSize); }
|
||||
template<typename T> Serializer& container2(T &&obj, size_t maxSize) {
|
||||
return container<2>(std::forward<T>(obj), maxSize); }
|
||||
template<typename T> Serializer& container4(T &&obj, size_t maxSize) {
|
||||
return container<4>(std::forward<T>(obj), maxSize); }
|
||||
template<typename T> Serializer& container8(T &&obj, size_t maxSize) {
|
||||
return container<8>(std::forward<T>(obj), maxSize); }
|
||||
template<typename T>
|
||||
void value8b(T &&v) { value<8>(std::forward<T>(v)); }
|
||||
|
||||
template<typename T, size_t N> Serializer& array1(const std::array<T, N> &arr) { return array<1>(arr); }
|
||||
template<typename T, size_t N> Serializer& array2(const std::array<T, N> &arr) { return array<2>(arr); }
|
||||
template<typename T, size_t N> Serializer& array4(const std::array<T, N> &arr) { return array<4>(arr); }
|
||||
template<typename T, size_t N> Serializer& array8(const std::array<T, N> &arr) { return array<8>(arr); }
|
||||
template<typename T, typename Ext>
|
||||
void ext1b(const T &v, Ext &&extension) { ext<1, T, Ext>(v, std::forward<Ext>(extension)); };
|
||||
|
||||
template<typename T, size_t N> Serializer& array1(const T (&arr)[N]) { return array<1>(arr); }
|
||||
template<typename T, size_t N> Serializer& array2(const T (&arr)[N]) { return array<2>(arr); }
|
||||
template<typename T, size_t N> Serializer& array4(const T (&arr)[N]) { return array<4>(arr); }
|
||||
template<typename T, size_t N> Serializer& array8(const T (&arr)[N]) { return array<8>(arr); }
|
||||
template<typename T, typename Ext>
|
||||
void ext2b(const T &v, Ext &&extension) { ext<2, T, Ext>(v, std::forward<Ext>(extension)); };
|
||||
|
||||
template<typename T, typename Ext>
|
||||
void ext4b(const T &v, Ext &&extension) { ext<4, T, Ext>(v, std::forward<Ext>(extension)); };
|
||||
|
||||
template<typename T, typename Ext>
|
||||
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); }
|
||||
|
||||
template<typename T>
|
||||
void text2b(const T &str, size_t maxSize) { text<2>(str, maxSize); }
|
||||
|
||||
template<typename T>
|
||||
void text4b(const T &str, size_t maxSize) { text<4>(str, maxSize); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
void text1b(const T (&str)[N]) { text<1, T, N>(str); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
void text2b(const T (&str)[N]) { text<2, T, N>(str); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
void text4b(const T (&str)[N]) { text<4, T, N>(str); }
|
||||
|
||||
template<typename T>
|
||||
void container1b(T &&obj, size_t maxSize) { container<1>(std::forward<T>(obj), maxSize); }
|
||||
|
||||
template<typename T>
|
||||
void container2b(T &&obj, size_t maxSize) { container<2>(std::forward<T>(obj), maxSize); }
|
||||
|
||||
template<typename T>
|
||||
void container4b(T &&obj, size_t maxSize) { container<4>(std::forward<T>(obj), maxSize); }
|
||||
|
||||
template<typename T>
|
||||
void container8b(T &&obj, size_t maxSize) { container<8>(std::forward<T>(obj), maxSize); }
|
||||
|
||||
template<typename T>
|
||||
void container1b(T &&obj) { container<1>(std::forward<T>(obj)); }
|
||||
|
||||
template<typename T>
|
||||
void container2b(T &&obj) { container<2>(std::forward<T>(obj)); }
|
||||
|
||||
template<typename T>
|
||||
void container4b(T &&obj) { container<4>(std::forward<T>(obj)); }
|
||||
|
||||
template<typename T>
|
||||
void container8b(T &&obj) { container<8>(std::forward<T>(obj)); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
void container1b(const T (&arr)[N]) { container<1>(arr); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
void container2b(const T (&arr)[N]) { container<2>(arr); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
void container4b(const T (&arr)[N]) { container<4>(arr); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
void container8b(const T (&arr)[N]) { container<8>(arr); }
|
||||
|
||||
private:
|
||||
Writter &_writter;
|
||||
|
||||
void writeSize(const size_t size) {
|
||||
if (size < 0x80u) {
|
||||
_writter.writeBits(1u, 1);
|
||||
_writter.writeBits(size, 7);
|
||||
} else if (size < 0x4000u) {
|
||||
_writter.writeBits(2u,2);
|
||||
_writter.writeBits(size, 14);
|
||||
} else {
|
||||
assert(size < 0x40000000u);
|
||||
_writter.writeBits(0u,2);
|
||||
_writter.writeBits(size, 30);
|
||||
}
|
||||
}
|
||||
BasicBufferWriter<Config> &_writter;
|
||||
void* _context;
|
||||
|
||||
//process value types
|
||||
//false_type means that we must process all elements individually
|
||||
template<size_t VSIZE, typename It>
|
||||
void procContainer(It first, It last, std::false_type) {
|
||||
for (;first != last; ++first)
|
||||
for (; first != last; ++first)
|
||||
value<VSIZE>(*first);
|
||||
};
|
||||
|
||||
@@ -306,25 +344,39 @@ namespace bitsery {
|
||||
//true_type means, that we can copy whole buffer
|
||||
template<size_t VSIZE, typename It>
|
||||
void procContainer(It first, It last, std::true_type) {
|
||||
if (first != last)
|
||||
_writter.template writeBuffer<VSIZE>(&(*first), std::distance(first, last));
|
||||
_writter.template writeBuffer<VSIZE>(&(*first), std::distance(first, last));
|
||||
};
|
||||
|
||||
//process by calling functions
|
||||
template<typename It, typename Fnc>
|
||||
void procContainer(It first, It last, Fnc fnc) {
|
||||
for (;first != last; ++first)
|
||||
fnc(*this, *first);
|
||||
using TValue = typename std::decay<decltype(*first)>::type;
|
||||
for (; first != last; ++first) {
|
||||
fnc(const_cast<TValue&>(*first));
|
||||
}
|
||||
};
|
||||
|
||||
//process object types
|
||||
template<typename It>
|
||||
void procContainer(It first, It last) {
|
||||
for (;first != last; ++first)
|
||||
for (; first != last; ++first)
|
||||
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
|
||||
using Serializer = BasicSerializer<DefaultConfig>;
|
||||
|
||||
}
|
||||
#endif //BITSERY_SERIALIZER_H
|
||||
|
||||
@@ -1,205 +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 <bitsery/buffer_writer.h>
|
||||
#include <bitsery/buffer_reader.h>
|
||||
#include <bitsery/details/serialization_common.h>
|
||||
|
||||
using testing::Eq;
|
||||
using testing::ContainerEq;
|
||||
using bitsery::BufferWriter;
|
||||
using bitsery::BufferReader;
|
||||
using Buffer = std::vector<bitsery::DefaultConfig::BufferValueType>;
|
||||
|
||||
struct IntegralUnsignedTypes {
|
||||
uint32_t a;
|
||||
uint16_t b;
|
||||
uint8_t c;
|
||||
uint8_t d;
|
||||
uint64_t e;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
constexpr size_t getBits(T v) {
|
||||
return bitsery::details::calcRequiredBits<T>({}, v);
|
||||
};
|
||||
|
||||
TEST(BufferBitsOperations, WriteAndReadBits) {
|
||||
//setup data
|
||||
constexpr IntegralUnsignedTypes data{
|
||||
485454,//bits 19
|
||||
45978,//bits 16
|
||||
0,//bits 1
|
||||
36,//bits 6
|
||||
479845648946//bits 39
|
||||
};
|
||||
|
||||
constexpr size_t aBITS = getBits(data.a) + 2;
|
||||
constexpr size_t bBITS = getBits(data.b) + 0;
|
||||
constexpr size_t cBITS = getBits(data.c) + 2;
|
||||
constexpr size_t dBITS = getBits(data.d) + 1;
|
||||
constexpr size_t eBITS = getBits(data.e) + 8;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBits(data.a, aBITS);
|
||||
bw.writeBits(data.b, bBITS);
|
||||
bw.writeBits(data.c, cBITS);
|
||||
bw.writeBits(data.d, dBITS);
|
||||
bw.writeBits(data.e, eBITS);
|
||||
bw.flush();
|
||||
auto bytesCount = ((aBITS + bBITS + cBITS + dBITS + eBITS) / 8) +1 ;
|
||||
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(bytesCount));
|
||||
//read from buffer
|
||||
BufferReader br{buf};
|
||||
IntegralUnsignedTypes res;
|
||||
|
||||
br.readBits(res.a, aBITS);
|
||||
br.readBits(res.b, bBITS);
|
||||
br.readBits(res.c, cBITS);
|
||||
br.readBits(res.d, dBITS);
|
||||
br.readBits(res.e, eBITS);
|
||||
|
||||
EXPECT_THAT(res.a, Eq(data.a));
|
||||
EXPECT_THAT(res.b, Eq(data.b));
|
||||
EXPECT_THAT(res.c, Eq(data.c));
|
||||
EXPECT_THAT(res.d, Eq(data.d));
|
||||
EXPECT_THAT(res.e, Eq(data.e));
|
||||
|
||||
}
|
||||
|
||||
TEST(BufferBitsOperations, WhenFinishedFlushWriter) {
|
||||
|
||||
Buffer buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBits(3u, 2);
|
||||
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(0));
|
||||
bw.flush();
|
||||
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(1));
|
||||
|
||||
}
|
||||
|
||||
TEST(BufferBitsOperations, BufferSizeIsCountedPerByteNotPerBit) {
|
||||
//setup data
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBits(7u,3);
|
||||
bw.flush();
|
||||
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(1));
|
||||
|
||||
//read from buffer
|
||||
BufferReader br{buf};
|
||||
uint16_t tmp;
|
||||
EXPECT_THAT(br.readBits(tmp,4), Eq(true));
|
||||
EXPECT_THAT(br.readBits(tmp,2), Eq(true));
|
||||
EXPECT_THAT(br.readBits(tmp,2), Eq(true));
|
||||
EXPECT_THAT(br.readBits(tmp,2), Eq(false));
|
||||
|
||||
//part of next byte
|
||||
BufferReader br1{buf};
|
||||
EXPECT_THAT(br1.readBits(tmp,2), Eq(true));
|
||||
EXPECT_THAT(br1.readBits(tmp,7), Eq(false));
|
||||
|
||||
//bigger than byte
|
||||
BufferReader br2{buf};
|
||||
EXPECT_THAT(br2.readBits(tmp,9), Eq(false));
|
||||
}
|
||||
|
||||
TEST(BufferBitsOperations, ConsecutiveCallsToAlignHasNoEffect) {
|
||||
Buffer buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBits(3u, 2);
|
||||
//3 calls to align after 1st data
|
||||
bw.align();
|
||||
bw.align();
|
||||
bw.align();
|
||||
bw.writeBits(7u, 3);
|
||||
//1 call to align after 2nd data
|
||||
bw.align();
|
||||
bw.writeBits(15u, 4);
|
||||
bw.flush();
|
||||
|
||||
unsigned char tmp;
|
||||
BufferReader br{buf};
|
||||
EXPECT_THAT(br.readBits(tmp,2), Eq(true));
|
||||
EXPECT_THAT(tmp, Eq(3u));
|
||||
EXPECT_THAT(br.align(), Eq(true));
|
||||
|
||||
EXPECT_THAT(br.readBits(tmp,3), Eq(true));
|
||||
EXPECT_THAT(tmp, Eq(7u));
|
||||
EXPECT_THAT(br.align(), Eq(true));
|
||||
EXPECT_THAT(br.align(), Eq(true));
|
||||
EXPECT_THAT(br.align(), Eq(true));
|
||||
|
||||
EXPECT_THAT(br.readBits(tmp,4), Eq(true));
|
||||
EXPECT_THAT(tmp, Eq(15u));
|
||||
}
|
||||
|
||||
|
||||
|
||||
TEST(BufferBitsOperations, WhenAlignedFlushHasNoEffect) {
|
||||
//setup data
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBits(3u, 2);
|
||||
bw.align();
|
||||
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(1));
|
||||
bw.flush();
|
||||
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(1));
|
||||
}
|
||||
|
||||
|
||||
TEST(BufferBitsOperations, AlignMustWriteZerosBits) {
|
||||
//setup data
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
//write 2 bits and align
|
||||
bw.writeBits(3u, 2);
|
||||
bw.align();
|
||||
|
||||
unsigned char tmp;
|
||||
BufferReader br1{buf};
|
||||
br1.readBits(tmp,2);
|
||||
//read aligned bits
|
||||
EXPECT_THAT(br1.readBits(tmp,6), Eq(true));
|
||||
EXPECT_THAT(tmp, Eq(0));
|
||||
|
||||
BufferReader br2{buf};
|
||||
//read 2 bits
|
||||
br2.readBits(tmp,2);
|
||||
EXPECT_THAT(br2.align(), Eq(true));
|
||||
}
|
||||
@@ -1,274 +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 <bitsery/buffer_writer.h>
|
||||
#include <bitsery/buffer_reader.h>
|
||||
#include <list>
|
||||
#include <bitset>
|
||||
|
||||
using testing::Eq;
|
||||
using testing::ContainerEq;
|
||||
using bitsery::BufferWriter;
|
||||
using bitsery::BufferReader;
|
||||
using Buffer = std::vector<bitsery::DefaultConfig::BufferValueType>;
|
||||
|
||||
struct IntegralTypes {
|
||||
int64_t a;
|
||||
uint32_t b;
|
||||
int16_t c;
|
||||
uint8_t d;
|
||||
int8_t e;
|
||||
int8_t f[2];
|
||||
};
|
||||
|
||||
IntegralTypes getInitializedIntegralTypes() {
|
||||
IntegralTypes data;
|
||||
data.a = -4894541654564;
|
||||
data.b = 94545646;
|
||||
data.c = -8778;
|
||||
data.d = 200;
|
||||
data.e = -98;
|
||||
data.f[0] = 43;
|
||||
data.f[1] = -45;
|
||||
return data;
|
||||
}
|
||||
|
||||
void writeIntegralTypesToBuffer(BufferWriter& bw, const IntegralTypes& data) {
|
||||
bw.writeBytes<4>(data.b);
|
||||
bw.writeBytes<1>(data.f[0]);
|
||||
bw.writeBytes<2>(data.c);
|
||||
bw.writeBytes<1>(data.d);
|
||||
bw.writeBytes<8>(data.a);
|
||||
bw.writeBytes<1>(data.e);
|
||||
bw.writeBytes<1>(data.f[1]);
|
||||
}
|
||||
|
||||
|
||||
TEST(BufferBytesOperations, WriteAndReadBytes) {
|
||||
//setup data
|
||||
auto data =getInitializedIntegralTypes();
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
writeIntegralTypesToBuffer(bw, data);
|
||||
|
||||
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(18));
|
||||
//read from buffer
|
||||
BufferReader br{buf};
|
||||
IntegralTypes res{};
|
||||
EXPECT_THAT(br.readBytes<4>(res.b), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<1>(res.f[0]), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<2>(res.c), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<1>(res.d), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<8>(res.a), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<1>(res.e), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<1>(res.f[1]), Eq(true));
|
||||
//assert results
|
||||
|
||||
EXPECT_THAT(data.a, Eq(res.a));
|
||||
EXPECT_THAT(data.b, Eq(res.b));
|
||||
EXPECT_THAT(data.c, Eq(res.c));
|
||||
EXPECT_THAT(data.d, Eq(res.d));
|
||||
EXPECT_THAT(data.e, Eq(res.e));
|
||||
EXPECT_THAT(data.f, ContainerEq(res.f));
|
||||
|
||||
}
|
||||
|
||||
TEST(BufferBytesOperations, BufferReaderUsingDataPlusSizeCtor) {
|
||||
//setup data
|
||||
auto data =getInitializedIntegralTypes();
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
writeIntegralTypesToBuffer(bw, data);
|
||||
|
||||
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(18));
|
||||
//read from buffer
|
||||
BufferReader br{buf.data(), buf.size()};
|
||||
IntegralTypes res{};
|
||||
EXPECT_THAT(br.readBytes<4>(res.b), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<1>(res.f[0]), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<2>(res.c), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<1>(res.d), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<8>(res.a), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<1>(res.e), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<1>(res.f[1]), Eq(true));
|
||||
//assert results
|
||||
|
||||
EXPECT_THAT(data.a, Eq(res.a));
|
||||
EXPECT_THAT(data.b, Eq(res.b));
|
||||
EXPECT_THAT(data.c, Eq(res.c));
|
||||
EXPECT_THAT(data.d, Eq(res.d));
|
||||
EXPECT_THAT(data.e, Eq(res.e));
|
||||
EXPECT_THAT(data.f, ContainerEq(res.f));
|
||||
}
|
||||
|
||||
TEST(BufferBytesOperations, BufferReaderUsingCArrayCtor) {
|
||||
//setup data
|
||||
auto data =getInitializedIntegralTypes();
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
writeIntegralTypesToBuffer(bw, data);
|
||||
|
||||
ASSERT_THAT(std::distance(buf.begin(), buf.end()), Eq(18));
|
||||
uint8_t cArrBuf[18];
|
||||
std::copy(buf.begin(), buf.end(), cArrBuf);
|
||||
//read from buffer
|
||||
BufferReader br{cArrBuf};
|
||||
IntegralTypes res{};
|
||||
EXPECT_THAT(br.readBytes<4>(res.b), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<1>(res.f[0]), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<2>(res.c), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<1>(res.d), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<8>(res.a), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<1>(res.e), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<1>(res.f[1]), Eq(true));
|
||||
//assert results
|
||||
|
||||
EXPECT_THAT(data.a, Eq(res.a));
|
||||
EXPECT_THAT(data.b, Eq(res.b));
|
||||
EXPECT_THAT(data.c, Eq(res.c));
|
||||
EXPECT_THAT(data.d, Eq(res.d));
|
||||
EXPECT_THAT(data.e, Eq(res.e));
|
||||
EXPECT_THAT(data.f, ContainerEq(res.f));
|
||||
}
|
||||
|
||||
|
||||
|
||||
TEST(BufferBytesOperations, ReadReturnsFalseIfNotEnoughBufferSize) {
|
||||
//setup data
|
||||
uint8_t a = 111;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBytes<1>(a);
|
||||
bw.writeBytes<1>(a);
|
||||
bw.writeBytes<1>(a);
|
||||
//read from buffer
|
||||
BufferReader br{buf};
|
||||
int16_t b;
|
||||
int32_t c;
|
||||
EXPECT_THAT(br.readBytes<4>(c), Eq(false));
|
||||
EXPECT_THAT(br.readBytes<2>(b), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<2>(b), Eq(false));
|
||||
EXPECT_THAT(br.readBytes<1>(a), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<1>(a), Eq(false));
|
||||
EXPECT_THAT(br.readBytes<2>(b), Eq(false));
|
||||
EXPECT_THAT(br.readBytes<4>(c), Eq(false));
|
||||
|
||||
}
|
||||
|
||||
|
||||
TEST(BufferBytesOperations, ReadIsCompletedWhenAllBytesAreRead) {
|
||||
//setup data
|
||||
IntegralTypes data;
|
||||
data.b = 94545646;
|
||||
data.c = -8778;
|
||||
data.d = 200;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBytes<4>(data.b);
|
||||
bw.writeBytes<2>(data.c);
|
||||
bw.writeBytes<1>(data.d);
|
||||
//read from buffer
|
||||
BufferReader br{buf};
|
||||
IntegralTypes res;
|
||||
EXPECT_THAT(br.readBytes<4>(res.b), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<2>(res.c), Eq(true));
|
||||
EXPECT_THAT(br.isCompleted(), Eq(false));
|
||||
EXPECT_THAT(br.readBytes<1>(res.d), Eq(true));
|
||||
EXPECT_THAT(br.isCompleted(), Eq(true));
|
||||
EXPECT_THAT(br.readBytes<1>(res.d), Eq(false));
|
||||
EXPECT_THAT(br.isCompleted(), Eq(true));
|
||||
|
||||
BufferReader br1{buf};
|
||||
EXPECT_THAT(br1.readBytes<4>(res.b), Eq(true));
|
||||
EXPECT_THAT(br1.readBytes<2>(res.c), Eq(true));
|
||||
EXPECT_THAT(br1.isCompleted(), Eq(false));
|
||||
EXPECT_THAT(br1.readBytes<2>(res.c), Eq(false));
|
||||
EXPECT_THAT(br1.isCompleted(), Eq(false));
|
||||
EXPECT_THAT(br1.readBytes<1>(res.d), Eq(true));
|
||||
EXPECT_THAT(br1.isCompleted(), Eq(true));
|
||||
|
||||
}
|
||||
|
||||
TEST(BufferBytesOperations, ReadWriteBufferFncCanAcceptSignedData) {
|
||||
//setup data
|
||||
constexpr size_t DATA_SIZE = 3;
|
||||
int16_t src[DATA_SIZE] {54,-4877,30067};
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
bw.writeBuffer<2>(src, DATA_SIZE);
|
||||
bw.flush();
|
||||
//read from buffer
|
||||
BufferReader br1{buf};
|
||||
int16_t dst[DATA_SIZE]{};
|
||||
EXPECT_THAT(br1.readBuffer<2>(dst, DATA_SIZE), Eq(true));
|
||||
EXPECT_THAT(dst, ContainerEq(src));
|
||||
|
||||
//read more than available
|
||||
BufferReader br2{buf};
|
||||
int16_t dstMore[DATA_SIZE+1]{};
|
||||
EXPECT_THAT(br2.readBuffer<2>(dstMore, DATA_SIZE+1), Eq(false));
|
||||
}
|
||||
|
||||
TEST(BufferBytesOperations, ReadWriteBufferCanWorkOnUnalignedData) {
|
||||
//setup data
|
||||
constexpr size_t DATA_SIZE = 3;
|
||||
int16_t src[DATA_SIZE] {54,-4877,30067};
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
bw.writeBits(15u, 4);
|
||||
bw.writeBuffer<2>(src, DATA_SIZE);
|
||||
bw.writeBits(12u, 4);
|
||||
bw.flush();
|
||||
EXPECT_THAT(buf.size(), Eq(sizeof(src) + 1));
|
||||
|
||||
//read from buffer
|
||||
BufferReader br1{buf};
|
||||
int16_t dst[DATA_SIZE]{};
|
||||
uint8_t tmp{};
|
||||
br1.readBits(tmp, 4);
|
||||
EXPECT_THAT(tmp, Eq(15));
|
||||
EXPECT_THAT(br1.readBuffer<2>(dst, DATA_SIZE), Eq(true));
|
||||
EXPECT_THAT(dst, ContainerEq(src));
|
||||
br1.readBits(tmp, 4);
|
||||
EXPECT_THAT(tmp, Eq(12));
|
||||
|
||||
//read more than available
|
||||
BufferReader br2{buf};
|
||||
br2.readBits(tmp, 4);
|
||||
int16_t dstMore[DATA_SIZE+1]{};
|
||||
EXPECT_THAT(tmp, Eq(15));
|
||||
EXPECT_THAT(br2.readBuffer<2>(dstMore, DATA_SIZE+1), Eq(false));
|
||||
}
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
#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;
|
||||
using bitsery::EndiannessType;
|
||||
using bitsery::DefaultConfig;
|
||||
using Buffer = std::vector<bitsery::DefaultConfig::BufferValueType>;
|
||||
using Buffer = bitsery::DefaultConfig::BufferType;
|
||||
|
||||
constexpr EndiannessType getInverseEndianness(EndiannessType e) {
|
||||
return e == EndiannessType::LittleEndian
|
||||
@@ -38,10 +38,8 @@ constexpr EndiannessType getInverseEndianness(EndiannessType e) {
|
||||
: EndiannessType::LittleEndian;
|
||||
}
|
||||
|
||||
struct InverseEndiannessConfig {
|
||||
struct InverseEndiannessConfig:public DefaultConfig {
|
||||
static constexpr bitsery::EndiannessType NetworkEndianness = getInverseEndianness(DefaultConfig::NetworkEndianness);
|
||||
using BufferValueType = DefaultConfig::BufferValueType;
|
||||
using BufferScrathType = DefaultConfig::BufferScrathType;
|
||||
};
|
||||
|
||||
struct IntegralTypes {
|
||||
@@ -80,7 +78,7 @@ TEST(BufferEndianness, WhenWriteBytesThenBytesAreSwapped) {
|
||||
bw.writeBytes<1>(src.e);
|
||||
bw.flush();
|
||||
//read from buffer using inverse endianness config
|
||||
bitsery::BasicBufferReader<InverseEndiannessConfig> br{buf};
|
||||
bitsery::BasicBufferReader<InverseEndiannessConfig> br{bw.getWrittenRange()};
|
||||
IntegralTypes res{};
|
||||
br.readBytes<8>(res.a);
|
||||
br.readBytes<4>(res.b);
|
||||
@@ -106,7 +104,7 @@ TEST(BufferEndianness, WhenWriteBuffer1ByteValuesThenEndiannessIsIgnored) {
|
||||
bw.writeBuffer<1>(src, SIZE);
|
||||
bw.flush();
|
||||
//read from buffer using inverse endianness config
|
||||
bitsery::BasicBufferReader<InverseEndiannessConfig> br{buf};
|
||||
bitsery::BasicBufferReader<InverseEndiannessConfig> br{bw.getWrittenRange()};
|
||||
br.readBuffer<1>(res, SIZE);
|
||||
//result is identical, because we write separate values, of size 1byte, that requires no swapping
|
||||
//check results
|
||||
@@ -125,7 +123,7 @@ TEST(BufferEndianness, WhenWriteBufferMoreThan1ByteValuesThenValuesAreSwapped) {
|
||||
bw.writeBuffer<2>(src, SIZE);
|
||||
bw.flush();
|
||||
//read from buffer using inverse endianness config
|
||||
bitsery::BasicBufferReader<InverseEndiannessConfig> br{buf};
|
||||
bitsery::BasicBufferReader<InverseEndiannessConfig> br{bw.getWrittenRange()};
|
||||
br.readBuffer<2>(res, SIZE);
|
||||
//result is identical, because we write separate values, of size 1byte, that requires no swapping
|
||||
//check results
|
||||
@@ -147,7 +145,7 @@ struct IntegralUnsignedTypes {
|
||||
|
||||
TEST(BufferEndianness, WhenBufferValueTypeIs1ByteThenBitOperationsIsNotAffectedByEndianness) {
|
||||
//fill initial values
|
||||
static_assert(sizeof(DefaultConfig::BufferValueType) == 1, "currently only 1 byte size, value size is supported");
|
||||
static_assert(sizeof(bitsery::details::BufferContainerTraits<DefaultConfig::BufferType>::TValue) == 1, "currently only 1 byte size, value size is supported");
|
||||
//fill initial values
|
||||
constexpr IntegralUnsignedTypes src {
|
||||
0x0000334455667788,//bits 19
|
||||
@@ -169,7 +167,7 @@ TEST(BufferEndianness, WhenBufferValueTypeIs1ByteThenBitOperationsIsNotAffectedB
|
||||
bw.writeBits(src.d, dBITS);
|
||||
bw.flush();
|
||||
//read from buffer using inverse endianness config
|
||||
bitsery::BasicBufferReader<InverseEndiannessConfig> br{buf};
|
||||
bitsery::BasicBufferReader<InverseEndiannessConfig> br{bw.getWrittenRange()};
|
||||
IntegralUnsignedTypes res{};
|
||||
br.readBits(res.a, aBITS);
|
||||
br.readBits(res.b, bBITS);
|
||||
|
||||
344
tests/buffer_operations.cpp
Normal file
344
tests/buffer_operations.cpp
Normal file
@@ -0,0 +1,344 @@
|
||||
//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 <bitsery/buffer_writer.h>
|
||||
#include <bitsery/buffer_reader.h>
|
||||
#include <bitsery/ext/value_range.h>
|
||||
|
||||
using testing::Eq;
|
||||
using testing::ContainerEq;
|
||||
using bitsery::BufferWriter;
|
||||
using bitsery::BufferReader;
|
||||
using Buffer = bitsery::DefaultConfig::BufferType;
|
||||
|
||||
struct IntegralUnsignedTypes {
|
||||
uint32_t a;
|
||||
uint16_t b;
|
||||
uint8_t c;
|
||||
uint8_t d;
|
||||
uint64_t e;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
constexpr size_t getBits(T v) {
|
||||
return bitsery::details::calcRequiredBits<T>({}, v);
|
||||
};
|
||||
|
||||
// *** bits operations
|
||||
|
||||
TEST(BufferBitsAndBytesOperations, WriteAndReadBitsMaxTypeValues) {
|
||||
Buffer buf;
|
||||
BufferWriter bw{buf};
|
||||
bw.writeBits(std::numeric_limits<uint64_t>::max(), 64);
|
||||
bw.writeBits(std::numeric_limits<uint32_t>::max(), 32);
|
||||
bw.writeBits(std::numeric_limits<uint16_t>::max(), 16);
|
||||
bw.writeBits(std::numeric_limits<uint8_t>::max(), 8);
|
||||
bw.flush();
|
||||
|
||||
BufferReader br{bw.getWrittenRange()};
|
||||
uint64_t v64{};
|
||||
uint32_t v32{};
|
||||
uint16_t v16{};
|
||||
uint8_t v8{};
|
||||
br.readBits(v64, 64);
|
||||
br.readBits(v32, 32);
|
||||
br.readBits(v16, 16);
|
||||
br.readBits(v8, 8);
|
||||
|
||||
EXPECT_THAT(v64, Eq(std::numeric_limits<uint64_t>::max()));
|
||||
EXPECT_THAT(v32, Eq(std::numeric_limits<uint32_t>::max()));
|
||||
EXPECT_THAT(v16, Eq(std::numeric_limits<uint16_t>::max()));
|
||||
EXPECT_THAT(v8, Eq(std::numeric_limits<uint8_t>::max()));
|
||||
}
|
||||
|
||||
TEST(BufferBitsAndBytesOperations, WriteAndReadBits) {
|
||||
//setup data
|
||||
constexpr IntegralUnsignedTypes data{
|
||||
485454,//bits 19
|
||||
45978,//bits 16
|
||||
0,//bits 1
|
||||
36,//bits 6
|
||||
479845648946//bits 39
|
||||
};
|
||||
|
||||
constexpr size_t aBITS = getBits(data.a) + 2;
|
||||
constexpr size_t bBITS = getBits(data.b) + 0;
|
||||
constexpr size_t cBITS = getBits(data.c) + 2;
|
||||
constexpr size_t dBITS = getBits(data.d) + 1;
|
||||
constexpr size_t eBITS = getBits(data.e) + 8;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBits(data.a, aBITS);
|
||||
bw.writeBits(data.b, bBITS);
|
||||
bw.writeBits(data.c, cBITS);
|
||||
bw.writeBits(data.d, dBITS);
|
||||
bw.writeBits(data.e, eBITS);
|
||||
bw.flush();
|
||||
auto range = bw.getWrittenRange();
|
||||
auto bytesCount = ((aBITS + bBITS + cBITS + dBITS + eBITS) / 8) +1 ;
|
||||
EXPECT_THAT(std::distance(range.begin(), range.end()), Eq(bytesCount));
|
||||
//read from buffer
|
||||
BufferReader br{range};
|
||||
IntegralUnsignedTypes res;
|
||||
|
||||
br.readBits(res.a, aBITS);
|
||||
br.readBits(res.b, bBITS);
|
||||
br.readBits(res.c, cBITS);
|
||||
br.readBits(res.d, dBITS);
|
||||
br.readBits(res.e, eBITS);
|
||||
|
||||
EXPECT_THAT(res.a, Eq(data.a));
|
||||
EXPECT_THAT(res.b, Eq(data.b));
|
||||
EXPECT_THAT(res.c, Eq(data.c));
|
||||
EXPECT_THAT(res.d, Eq(data.d));
|
||||
EXPECT_THAT(res.e, Eq(data.e));
|
||||
|
||||
}
|
||||
|
||||
TEST(BufferBitsAndBytesOperations, BufferSizeIsCountedPerByteNotPerBit) {
|
||||
//setup data
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBits(7u,3);
|
||||
bw.flush();
|
||||
auto range = bw.getWrittenRange();
|
||||
EXPECT_THAT(std::distance(range.begin(), range.end()), Eq(1));
|
||||
|
||||
//read from buffer
|
||||
BufferReader br{range};
|
||||
uint16_t tmp;
|
||||
br.readBits(tmp,4);
|
||||
br.readBits(tmp,2);
|
||||
br.readBits(tmp,2);
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
br.readBits(tmp,2);
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));//false
|
||||
|
||||
//part of next byte
|
||||
BufferReader br1{range};
|
||||
br1.readBits(tmp,2);
|
||||
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
br1.readBits(tmp,7);
|
||||
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));//false
|
||||
|
||||
//bigger than byte
|
||||
BufferReader br2{range};
|
||||
br2.readBits(tmp,9);
|
||||
EXPECT_THAT(br2.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));//false
|
||||
}
|
||||
|
||||
TEST(BufferBitsAndBytesOperations, ConsecutiveCallsToAlignHasNoEffect) {
|
||||
Buffer buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBits(3u, 2);
|
||||
//3 calls to align after 1st data
|
||||
bw.align();
|
||||
bw.align();
|
||||
bw.align();
|
||||
bw.writeBits(7u, 3);
|
||||
//1 call to align after 2nd data
|
||||
bw.align();
|
||||
bw.writeBits(15u, 4);
|
||||
bw.flush();
|
||||
|
||||
unsigned char tmp;
|
||||
BufferReader br{bw.getWrittenRange()};
|
||||
br.readBits(tmp,2);
|
||||
EXPECT_THAT(tmp, Eq(3u));
|
||||
br.align();
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
br.readBits(tmp,3);
|
||||
br.align();
|
||||
br.align();
|
||||
br.align();
|
||||
EXPECT_THAT(tmp, Eq(7u));
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
|
||||
br.readBits(tmp,4);
|
||||
EXPECT_THAT(tmp, Eq(15u));
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
}
|
||||
|
||||
TEST(BufferBitsAndBytesOperations, AlignWritesZerosBits) {
|
||||
//setup data
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
//write 2 bits and align
|
||||
bw.writeBits(3u, 2);
|
||||
bw.align();
|
||||
bw.flush();
|
||||
auto range = bw.getWrittenRange();
|
||||
EXPECT_THAT(std::distance(range.begin(), range.end()), Eq(1));
|
||||
unsigned char tmp;
|
||||
BufferReader br1{range};
|
||||
br1.readBits(tmp,2);
|
||||
//read aligned bits
|
||||
br1.readBits(tmp,6);
|
||||
EXPECT_THAT(tmp, Eq(0));
|
||||
|
||||
BufferReader br2{range};
|
||||
//read 2 bits
|
||||
br2.readBits(tmp,2);
|
||||
br2.align();
|
||||
EXPECT_THAT(br2.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
}
|
||||
|
||||
|
||||
// *** bytes operations
|
||||
|
||||
struct IntegralTypes {
|
||||
int64_t a;
|
||||
uint32_t b;
|
||||
int16_t c;
|
||||
uint8_t d;
|
||||
int8_t e;
|
||||
int8_t f[2];
|
||||
};
|
||||
|
||||
TEST(BufferBitsAndBytesOperations, WriteAndReadBytes) {
|
||||
//setup data
|
||||
IntegralTypes data;
|
||||
data.a = -4894541654564;
|
||||
data.b = 94545646;
|
||||
data.c = -8778;
|
||||
data.d = 200;
|
||||
data.e = -98;
|
||||
data.f[0] = 43;
|
||||
data.f[1] = -45;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
bw.writeBytes<4>(data.b);
|
||||
bw.writeBytes<2>(data.c);
|
||||
bw.writeBytes<1>(data.d);
|
||||
bw.writeBytes<8>(data.a);
|
||||
bw.writeBytes<1>(data.e);
|
||||
bw.writeBuffer<1>(data.f, 2);
|
||||
bw.flush();
|
||||
auto range = bw.getWrittenRange();
|
||||
|
||||
EXPECT_THAT(std::distance(range.begin(), range.end()), Eq(18));
|
||||
//read from buffer
|
||||
BufferReader br{range};
|
||||
IntegralTypes res{};
|
||||
br.readBytes<4>(res.b);
|
||||
br.readBytes<2>(res.c);
|
||||
br.readBytes<1>(res.d);
|
||||
br.readBytes<8>(res.a);
|
||||
br.readBytes<1>(res.e);
|
||||
br.readBuffer<1>(res.f, 2);
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
//assert results
|
||||
|
||||
EXPECT_THAT(data.a, Eq(res.a));
|
||||
EXPECT_THAT(data.b, Eq(res.b));
|
||||
EXPECT_THAT(data.c, Eq(res.c));
|
||||
EXPECT_THAT(data.d, Eq(res.d));
|
||||
EXPECT_THAT(data.e, Eq(res.e));
|
||||
EXPECT_THAT(data.f, ContainerEq(res.f));
|
||||
|
||||
}
|
||||
|
||||
TEST(BufferBitsAndBytesOperations, ReadWriteBufferFncCanAcceptSignedData) {
|
||||
//setup data
|
||||
constexpr size_t DATA_SIZE = 3;
|
||||
int16_t src[DATA_SIZE] {54,-4877,30067};
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
bw.writeBuffer<2>(src, DATA_SIZE);
|
||||
bw.flush();
|
||||
//read from buffer
|
||||
BufferReader br1{bw.getWrittenRange()};
|
||||
int16_t dst[DATA_SIZE]{};
|
||||
br1.readBuffer<2>(dst, DATA_SIZE);
|
||||
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
EXPECT_THAT(dst, ContainerEq(src));
|
||||
}
|
||||
|
||||
TEST(BufferBitsAndBytesOperations, ReadWriteBufferCanWorkOnUnalignedData) {
|
||||
//setup data
|
||||
constexpr size_t DATA_SIZE = 3;
|
||||
int16_t src[DATA_SIZE] {54,-4877,30067};
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
bw.writeBits(15u, 4);
|
||||
bw.writeBuffer<2>(src, DATA_SIZE);
|
||||
bw.writeBits(12u, 4);
|
||||
bw.flush();
|
||||
auto range = bw.getWrittenRange();
|
||||
EXPECT_THAT(std::distance(range.begin(), range.end()), Eq(sizeof(src) + 1));
|
||||
|
||||
//read from buffer
|
||||
BufferReader br1{range};
|
||||
int16_t dst[DATA_SIZE]{};
|
||||
uint8_t tmp{};
|
||||
br1.readBits(tmp, 4);
|
||||
EXPECT_THAT(tmp, Eq(15));
|
||||
br1.readBuffer<2>(dst, DATA_SIZE);
|
||||
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
EXPECT_THAT(dst, ContainerEq(src));
|
||||
br1.readBits(tmp, 4);
|
||||
EXPECT_THAT(tmp, Eq(12));
|
||||
}
|
||||
|
||||
TEST(BufferBitsAndBytesOperations, RegressionTestReadBytesAfterReadBitsWithLotsOfZeroBits) {
|
||||
//setup data
|
||||
int16_t data[2]{0x0000, 0x7FFF};
|
||||
int16_t res[2]{};
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
bw.writeBits(2u, 2);
|
||||
bw.writeBytes<2>(data[0]);
|
||||
bw.writeBytes<2>(data[1]);
|
||||
bw.align();
|
||||
bw.flush();
|
||||
auto range = bw.getWrittenRange();
|
||||
|
||||
//read from buffer
|
||||
BufferReader br{range};
|
||||
uint8_t tmp{};
|
||||
br.readBits(tmp, 2);
|
||||
EXPECT_THAT(tmp, Eq(2));
|
||||
br.readBytes<2>(res[0]);
|
||||
br.readBytes<2>(res[1]);
|
||||
br.align();
|
||||
EXPECT_THAT(res[0], Eq(data[0]));
|
||||
EXPECT_THAT(res[1], Eq(data[1]));
|
||||
}
|
||||
|
||||
|
||||
158
tests/buffer_reading.cpp
Normal file
158
tests/buffer_reading.cpp
Normal file
@@ -0,0 +1,158 @@
|
||||
//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 <bitsery/buffer_writer.h>
|
||||
#include <bitsery/buffer_reader.h>
|
||||
#include <list>
|
||||
#include <bitset>
|
||||
|
||||
using testing::Eq;
|
||||
using bitsery::BufferWriter;
|
||||
using bitsery::BufferReader;
|
||||
using Buffer = bitsery::DefaultConfig::BufferType;
|
||||
|
||||
struct IntegralTypes {
|
||||
int64_t a;
|
||||
uint32_t b;
|
||||
int16_t c;
|
||||
uint8_t d;
|
||||
int8_t e;
|
||||
int8_t f[2];
|
||||
};
|
||||
|
||||
TEST(BufferReading, WhenReadingMoreThanAvailableThenEmptyBufferError) {
|
||||
//setup data
|
||||
uint8_t a = 111;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBytes<1>(a);
|
||||
bw.writeBytes<1>(a);
|
||||
bw.writeBytes<1>(a);
|
||||
bw.flush();
|
||||
//read from buffer
|
||||
BufferReader br{bw.getWrittenRange()};
|
||||
int32_t c;
|
||||
br.readBytes<4>(c);
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
|
||||
}
|
||||
|
||||
TEST(BufferReading, WhenErrorOccursThenAllOtherOperationsFailsForSameError) {
|
||||
//setup data
|
||||
uint8_t a = 111;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBytes<1>(a);
|
||||
bw.writeBytes<1>(a);
|
||||
bw.writeBytes<1>(a);
|
||||
bw.flush();
|
||||
//read from buffer
|
||||
BufferReader br{bw.getWrittenRange()};
|
||||
int32_t c;
|
||||
br.readBytes<4>(c);
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
|
||||
br.readBytes<1>(a);
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
|
||||
}
|
||||
|
||||
|
||||
TEST(BufferReading, ReadIsCompletedSuccessfullyWhenAllBytesAreReadWithoutErrors) {
|
||||
//setup data
|
||||
IntegralTypes data;
|
||||
data.b = 94545646;
|
||||
data.c = -8778;
|
||||
data.d = 200;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBytes<4>(data.b);
|
||||
bw.writeBytes<2>(data.c);
|
||||
bw.writeBytes<1>(data.d);
|
||||
bw.flush();
|
||||
//read from buffer
|
||||
BufferReader br{bw.getWrittenRange()};
|
||||
IntegralTypes res;
|
||||
br.readBytes<4>(res.b);
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
br.readBytes<2>(res.c);
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(false));
|
||||
br.readBytes<1>(res.d);
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(true));
|
||||
br.readBytes<1>(res.d);
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
|
||||
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(false));
|
||||
|
||||
BufferReader br1{bw.getWrittenRange()};
|
||||
br1.readBytes<4>(res.b);
|
||||
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
br1.readBytes<2>(res.c);
|
||||
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
EXPECT_THAT(br1.isCompletedSuccessfully(), Eq(false));
|
||||
br1.readBytes<2>(res.c);
|
||||
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
|
||||
EXPECT_THAT(br1.isCompletedSuccessfully(), Eq(false));
|
||||
br1.readBytes<1>(res.d);
|
||||
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
|
||||
EXPECT_THAT(br1.isCompletedSuccessfully(), Eq(false));
|
||||
}
|
||||
|
||||
TEST(BufferReading, WhenReaderHasErrorsAllOperationsReadsReturnZero) {
|
||||
//setup data
|
||||
uint8_t a = 111;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBytes<1>(a);
|
||||
bw.writeBytes<1>(a);
|
||||
bw.writeBytes<1>(a);
|
||||
bw.flush();
|
||||
//read from buffer
|
||||
BufferReader br{bw.getWrittenRange()};
|
||||
int32_t c;
|
||||
br.readBytes<4>(c);
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
|
||||
|
||||
int16_t r1= {-645};
|
||||
uint32_t r2[2] = {54898,87854};
|
||||
uint8_t r3 = 0xFF;
|
||||
|
||||
br.readBytes<2>(r1);
|
||||
br.readBuffer<4>(r2, 2);
|
||||
br.readBits(r3, 7);
|
||||
EXPECT_THAT(r1, Eq(0));
|
||||
EXPECT_THAT(r2[0], Eq(0u));
|
||||
EXPECT_THAT(r2[1], Eq(0u));
|
||||
EXPECT_THAT(r3, Eq(0u));
|
||||
}
|
||||
119
tests/buffer_reading_errors.cpp
Normal file
119
tests/buffer_reading_errors.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
//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 testing::Eq;
|
||||
using bitsery::BufferWriter;
|
||||
using bitsery::BufferReader;
|
||||
using Buffer = bitsery::DefaultConfig::BufferType;
|
||||
|
||||
TEST(BufferReadingErrors, WhenContainerOrTextSizeIsMoreThanMaxThenInvalidBufferDataError) {
|
||||
SerializationContext ctx;
|
||||
std::string tmp = "larger text then allowed";
|
||||
ctx.createSerializer().text1b(tmp,100);
|
||||
ctx.createDeserializer().text1b(tmp, 10);
|
||||
EXPECT_THAT(ctx.br->getError(), Eq(bitsery::BufferReaderError::INVALID_BUFFER_DATA));
|
||||
}
|
||||
|
||||
TEST(BufferReadingErrors, WhenReadingBoolByteReadsMoreThanOneThenInvalidBufferDataErrorAndResultIsFalse) {
|
||||
SerializationContext ctx;
|
||||
auto ser = ctx.createSerializer();
|
||||
ser.value1b(uint8_t{1});
|
||||
ser.value1b(uint8_t{2});
|
||||
bool res{};
|
||||
auto des = ctx.createDeserializer();
|
||||
des.boolByte(res);
|
||||
EXPECT_THAT(res, Eq(true));
|
||||
des.boolByte(res);
|
||||
EXPECT_THAT(res, Eq(false));
|
||||
EXPECT_THAT(ctx.br->getError(), Eq(bitsery::BufferReaderError::INVALID_BUFFER_DATA));
|
||||
}
|
||||
|
||||
TEST(BufferReadingErrors, WhenReadingAlignHasNonZerosThenInvalidBufferDataError) {
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
uint8_t tmp{0xFF};
|
||||
bw.writeBytes<1>(tmp);
|
||||
bw.flush();
|
||||
BufferReader br{bw.getWrittenRange()};
|
||||
|
||||
br.readBits(tmp,3);
|
||||
br.align();
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::INVALID_BUFFER_DATA));
|
||||
}
|
||||
|
||||
TEST(BufferReadingErrors, WhenReadingNewSessionInMiddleOfOldDataThenInvalidBufferError) {
|
||||
uint8_t tmp{0xFF};
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
for (auto i = 0; i < 2; ++i) {
|
||||
bw.beginSession();
|
||||
bw.writeBytes<1>(tmp);
|
||||
bw.writeBytes<1>(tmp);
|
||||
bw.endSession();
|
||||
}
|
||||
bw.flush();
|
||||
BufferReader br{bw.getWrittenRange()};
|
||||
for (auto i = 0; i < 2; ++i) {
|
||||
br.beginSession();
|
||||
br.readBytes<1>(tmp);
|
||||
br.beginSession();
|
||||
br.readBytes<1>(tmp);
|
||||
br.endSession();
|
||||
br.endSession();
|
||||
}
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::INVALID_BUFFER_DATA));
|
||||
}
|
||||
|
||||
|
||||
TEST(BufferReadingErrors, WhenInitializingSessionsWhenNotEnoughDataThenInvalidBufferData) {
|
||||
uint8_t tmp1{0xFF};
|
||||
Buffer buf1{};
|
||||
BufferWriter bw1{buf1};
|
||||
bw1.writeBytes<1>(tmp1);
|
||||
bw1.flush();
|
||||
BufferReader br1{bw1.getWrittenRange()};
|
||||
br1.beginSession();
|
||||
EXPECT_THAT(br1.getError(), Eq(bitsery::BufferReaderError::INVALID_BUFFER_DATA));
|
||||
|
||||
Buffer buf2{};
|
||||
BufferWriter bw2{buf2};
|
||||
uint16_t tmp2{0x8000};
|
||||
bw2.writeBytes<2>(tmp2);
|
||||
bw2.flush();
|
||||
BufferReader br2{bw2.getWrittenRange()};
|
||||
br2.beginSession();
|
||||
EXPECT_THAT(br2.getError(), Eq(bitsery::BufferReaderError::INVALID_BUFFER_DATA));
|
||||
}
|
||||
|
||||
TEST(BufferReadingErrors, WhenInitializingSessionsWhereSessionsDataOffsetIsCorruptedThenInvalidBufferData) {
|
||||
Buffer buf{};
|
||||
BufferWriter bw{buf};
|
||||
bw.writeBytes<1>(uint8_t{1});
|
||||
bw.writeBytes<1>(uint8_t{1});
|
||||
bw.writeBytes<2>(uint16_t{10});
|
||||
BufferReader br{bw.getWrittenRange()};
|
||||
br.beginSession();
|
||||
EXPECT_THAT(br.getError(), Eq(bitsery::BufferReaderError::INVALID_BUFFER_DATA));
|
||||
}
|
||||
117
tests/buffer_writing.cpp
Normal file
117
tests/buffer_writing.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
//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 <bitsery/buffer_writer.h>
|
||||
#include <bitsery/buffer_reader.h>
|
||||
#include <bitsery/details/serialization_common.h>
|
||||
|
||||
using testing::Eq;
|
||||
using testing::ContainerEq;
|
||||
using bitsery::EndiannessType;
|
||||
using bitsery::DefaultConfig;
|
||||
using Buffer = bitsery::DefaultConfig::BufferType;
|
||||
|
||||
struct FixedBufferConfig: public DefaultConfig {
|
||||
using BufferType = std::array<uint8_t, 100>;
|
||||
};
|
||||
|
||||
struct NonFixedBufferConfig: public DefaultConfig {
|
||||
using BufferType = std::vector<uint8_t>;
|
||||
};
|
||||
|
||||
template <typename Config>
|
||||
class BufferWriting:public testing::Test {
|
||||
public:
|
||||
using type = Config;
|
||||
|
||||
};
|
||||
|
||||
using BufferWritingConfigs = ::testing::Types<
|
||||
NonFixedBufferConfig,
|
||||
FixedBufferConfig>;
|
||||
|
||||
TYPED_TEST_CASE(BufferWriting, BufferWritingConfigs);
|
||||
|
||||
static constexpr size_t DATA_SIZE = 14u;
|
||||
|
||||
template <typename BW>
|
||||
void writeData(BW& bw) {
|
||||
uint16_t tmp1{45}, tmp2{6543}, tmp3{46533};
|
||||
uint32_t tmp4{8979445}, tmp5{7987564};
|
||||
bw.template writeBytes<2>(tmp1);
|
||||
bw.template writeBytes<2>(tmp2);
|
||||
bw.template writeBytes<2>(tmp3);
|
||||
bw.template writeBytes<4>(tmp4);
|
||||
bw.template writeBytes<4>(tmp5);
|
||||
}
|
||||
|
||||
TYPED_TEST(BufferWriting, GetWrittenRangeReturnsBeginEndIterators) {
|
||||
using Config = typename TestFixture::type;
|
||||
using Buffer = typename Config::BufferType;
|
||||
Buffer buf{};
|
||||
bitsery::BasicBufferWriter<Config> bw{buf};
|
||||
writeData(bw);
|
||||
bw.flush();
|
||||
auto range = bw.getWrittenRange();
|
||||
EXPECT_THAT(std::distance(range.begin(), range.end()), DATA_SIZE);
|
||||
}
|
||||
|
||||
TYPED_TEST(BufferWriting, WhenWritingBitsThenMustFlushWriter) {
|
||||
using Config = typename TestFixture::type;
|
||||
using Buffer = typename Config::BufferType;
|
||||
Buffer buf{};
|
||||
bitsery::BasicBufferWriter<Config> bw{buf};
|
||||
bw.writeBits(3u, 2);
|
||||
auto range1 = bw.getWrittenRange();
|
||||
bw.flush();
|
||||
auto range2 = bw.getWrittenRange();
|
||||
EXPECT_THAT(std::distance(range1.begin(), range1.end()), Eq(0));
|
||||
EXPECT_THAT(std::distance(range2.begin(), range2.end()), Eq(1));
|
||||
}
|
||||
|
||||
TYPED_TEST(BufferWriting, WhenDataAlignedThenFlushHasNoEffect) {
|
||||
using Config = typename TestFixture::type;
|
||||
using Buffer = typename Config::BufferType;
|
||||
Buffer buf{};
|
||||
bitsery::BasicBufferWriter<Config> bw{buf};
|
||||
|
||||
bw.writeBits(3u, 2);
|
||||
bw.align();
|
||||
auto range1 = bw.getWrittenRange();
|
||||
bw.flush();
|
||||
auto range2 = bw.getWrittenRange();
|
||||
EXPECT_THAT(std::distance(range1.begin(), range1.end()), Eq(1));
|
||||
EXPECT_THAT(std::distance(range2.begin(), range2.end()), Eq(1));
|
||||
}
|
||||
|
||||
TEST(BufferWrittingNonFixedBuffer, BufferIsAlwaysResizedToCapacity) {
|
||||
using Buffer = typename NonFixedBufferConfig::BufferType;
|
||||
Buffer buf{};
|
||||
bitsery::BasicBufferWriter<NonFixedBufferConfig> bw{buf};
|
||||
for (auto i = 0; i < 5; ++i) {
|
||||
uint32_t tmp{};
|
||||
bw.writeBytes<4>(tmp);
|
||||
bw.writeBytes<4>(tmp);
|
||||
EXPECT_TRUE(buf.size() == buf.capacity());
|
||||
}
|
||||
}
|
||||
@@ -33,8 +33,12 @@ TEST(SerializeBooleans, BoolAsBit) {
|
||||
bool t2{false};
|
||||
bool res1;
|
||||
bool res2;
|
||||
ctx.createSerializer().boolBit(t1).boolBit(t2);
|
||||
ctx.createDeserializer().boolBit(res1).boolBit(res2);
|
||||
auto ser = ctx.createSerializer();
|
||||
ser.boolBit(t1);
|
||||
ser.boolBit(t2);
|
||||
auto des = ctx.createDeserializer();
|
||||
des.boolBit(res1);
|
||||
des.boolBit(res2);
|
||||
|
||||
EXPECT_THAT(res1, Eq(t1));
|
||||
EXPECT_THAT(res2, Eq(t2));
|
||||
@@ -47,8 +51,12 @@ TEST(SerializeBooleans, BoolAsByte) {
|
||||
bool t2{false};
|
||||
bool res1;
|
||||
bool res2;
|
||||
ctx.createSerializer().boolByte(t1).boolByte(t2);
|
||||
ctx.createDeserializer().boolByte(res1).boolByte(res2);
|
||||
auto ser = ctx.createSerializer();
|
||||
ser.boolByte(t1);
|
||||
ser.boolByte(t2);
|
||||
auto des = ctx.createDeserializer();
|
||||
des.boolByte(res1);
|
||||
des.boolByte(res2);
|
||||
|
||||
EXPECT_THAT(res1, Eq(t1));
|
||||
EXPECT_THAT(res2, Eq(t2));
|
||||
|
||||
@@ -23,10 +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;
|
||||
@@ -36,29 +38,29 @@ using testing::Eq;
|
||||
* overload to get container of types
|
||||
*/
|
||||
|
||||
template <typename Container>
|
||||
template<typename Container>
|
||||
Container getFilledContainer() {
|
||||
return {1,2,3,4,5,78,456,8,54};
|
||||
return {1, 2, 3, 4, 5, 78, 456, 8, 54};
|
||||
}
|
||||
|
||||
template <>
|
||||
template<>
|
||||
std::vector<MyStruct1> getFilledContainer<std::vector<MyStruct1>>() {
|
||||
return {
|
||||
{0,1},
|
||||
{2,3},
|
||||
{4,5},
|
||||
{6,7},
|
||||
{8,9},
|
||||
{11,34},
|
||||
{5134,1532}
|
||||
{0, 1},
|
||||
{2, 3},
|
||||
{4, 5},
|
||||
{6, 7},
|
||||
{8, 9},
|
||||
{11, 34},
|
||||
{5134, 1532}
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
template<>
|
||||
std::list<MyStruct2> getFilledContainer<std::list<MyStruct2>>() {
|
||||
return {
|
||||
{MyStruct2::V1, {0,1}} ,
|
||||
{MyStruct2::V3, {-45,45}}
|
||||
{MyStruct2::V1, {0, 1}},
|
||||
{MyStruct2::V3, {-45, 45}}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -66,16 +68,16 @@ std::list<MyStruct2> getFilledContainer<std::list<MyStruct2>>() {
|
||||
* start testing session
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
class SerializeContainerArthmeticTypes:public testing::Test {
|
||||
template<typename T>
|
||||
class SerializeContainerDynamicSizeArthmeticTypes : public testing::Test {
|
||||
public:
|
||||
using TContainer = T;
|
||||
using TValue = typename T::value_type;
|
||||
|
||||
const TContainer src= getFilledContainer<TContainer>() ;
|
||||
const TContainer src = getFilledContainer<TContainer>();
|
||||
TContainer res{};
|
||||
|
||||
size_t getExpectedBufSize(const SerializationContext& ctx) const {
|
||||
size_t getExpectedBufSize(const SerializationContext &ctx) const {
|
||||
return ctx.containerSizeSerializedBytesCount(src.size()) + src.size() * sizeof(TValue);
|
||||
}
|
||||
};
|
||||
@@ -85,9 +87,9 @@ using SequenceContainersWithArthmeticTypes = ::testing::Types<
|
||||
std::list<float>,
|
||||
std::deque<unsigned short>>;
|
||||
|
||||
TYPED_TEST_CASE(SerializeContainerArthmeticTypes, SequenceContainersWithArthmeticTypes);
|
||||
TYPED_TEST_CASE(SerializeContainerDynamicSizeArthmeticTypes, SequenceContainersWithArthmeticTypes);
|
||||
|
||||
TYPED_TEST(SerializeContainerArthmeticTypes, Values) {
|
||||
TYPED_TEST(SerializeContainerDynamicSizeArthmeticTypes, Values) {
|
||||
SerializationContext ctx{};
|
||||
using TValue = typename TestFixture::TValue;
|
||||
|
||||
@@ -98,53 +100,51 @@ TYPED_TEST(SerializeContainerArthmeticTypes, Values) {
|
||||
EXPECT_THAT(this->res, ContainerEq(this->src));
|
||||
}
|
||||
|
||||
TYPED_TEST(SerializeContainerArthmeticTypes, CustomFunctionIncrements) {
|
||||
TYPED_TEST(SerializeContainerDynamicSizeArthmeticTypes, CustomFunctionIncrements) {
|
||||
SerializationContext ctx{};
|
||||
using TValue = typename TestFixture::TValue;
|
||||
|
||||
auto ser = ctx.createSerializer();
|
||||
ser.container(this->src, 1000, [](auto &s, auto v) {
|
||||
//increment by 1 before writing
|
||||
v++;
|
||||
s.template value<sizeof(v)>(v);
|
||||
ser.container(this->src, 1000, [&ser](TValue& v) {
|
||||
ser.template value<sizeof(v)>(v);
|
||||
});
|
||||
auto des = ctx.createDeserializer();
|
||||
des.container(this->res, 1000, [](auto &s, auto&v ) {
|
||||
s.template value<sizeof(v)>(v);
|
||||
des.container(this->res, 1000, [&des](TValue &v) {
|
||||
des.template value<sizeof(v)>(v);
|
||||
//increment by 1 after reading
|
||||
v++;
|
||||
});
|
||||
//decrement result by 2, before comparing for eq
|
||||
for(auto& v:this->res)
|
||||
v -= 2;
|
||||
//decrement result by 1, before comparing for eq
|
||||
for (auto &v:this->res)
|
||||
v -= 1;
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(this->getExpectedBufSize(ctx)));
|
||||
EXPECT_THAT(this->res, ContainerEq(this->src));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
class SerializeContainerCompositeTypes:public testing::Test {
|
||||
template<typename T>
|
||||
class SerializeContainerDynamicSizeCompositeTypes : public testing::Test {
|
||||
public:
|
||||
using TContainer = T;
|
||||
using TValue = typename T::value_type;
|
||||
|
||||
const TContainer src= getFilledContainer<TContainer>();
|
||||
const TContainer src = getFilledContainer<TContainer>();
|
||||
TContainer res{};
|
||||
size_t getExpectedBufSize(const SerializationContext& ctx) const {
|
||||
|
||||
size_t getExpectedBufSize(const SerializationContext &ctx) const {
|
||||
return ctx.containerSizeSerializedBytesCount(src.size()) + src.size() * TValue::SIZE;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
using SequenceContainersWithCompositeTypes = ::testing::Types<
|
||||
using SerializeContainerDynamicSizeWithCompositeTypes = ::testing::Types<
|
||||
std::vector<MyStruct1>,
|
||||
std::list<MyStruct2>>;
|
||||
|
||||
TYPED_TEST_CASE(SerializeContainerCompositeTypes, SequenceContainersWithCompositeTypes);
|
||||
TYPED_TEST_CASE(SerializeContainerDynamicSizeCompositeTypes, SerializeContainerDynamicSizeWithCompositeTypes);
|
||||
|
||||
TYPED_TEST(SerializeContainerCompositeTypes, DefaultSerializeFunction) {
|
||||
TYPED_TEST(SerializeContainerDynamicSizeCompositeTypes, DefaultSerializeFunction) {
|
||||
SerializationContext ctx{};
|
||||
|
||||
ctx.createSerializer().container(this->src, 1000);
|
||||
@@ -155,15 +155,95 @@ TYPED_TEST(SerializeContainerCompositeTypes, DefaultSerializeFunction) {
|
||||
}
|
||||
|
||||
|
||||
TYPED_TEST(SerializeContainerCompositeTypes, CustomFunctionThatDoNothing) {
|
||||
TYPED_TEST(SerializeContainerDynamicSizeCompositeTypes, CustomFunctionThatDoNothing) {
|
||||
SerializationContext ctx{};
|
||||
using TValue = typename TestFixture::TValue;
|
||||
|
||||
|
||||
auto emptyFnc = [](auto& s, auto& v) {};
|
||||
auto emptyFnc = [](TValue &v) {};
|
||||
ctx.createSerializer().container(this->src, 1000, emptyFnc);
|
||||
ctx.createDeserializer().container(this->res, 1000, emptyFnc);
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(ctx.containerSizeSerializedBytesCount(this->src.size())));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
class SerializeContainerFixedSizeArithmeticTypes : public testing::Test {
|
||||
public:
|
||||
using TContainer = T;
|
||||
|
||||
size_t getContainerSize() {
|
||||
T tmp{};
|
||||
return static_cast<size_t>(std::distance(std::begin(tmp), std::end(tmp)));
|
||||
}
|
||||
};
|
||||
|
||||
using StaticContainersWithIntegralTypes = ::testing::Types<
|
||||
std::array<int16_t, 4>,
|
||||
int16_t[4]>;
|
||||
|
||||
TYPED_TEST_CASE(SerializeContainerFixedSizeArithmeticTypes, StaticContainersWithIntegralTypes);
|
||||
|
||||
TYPED_TEST(SerializeContainerFixedSizeArithmeticTypes, ArithmeticValues) {
|
||||
using Container = typename TestFixture::TContainer;
|
||||
Container src{5, 9, 15, -459};
|
||||
Container res{};
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().container<2>(src);
|
||||
ctx.createDeserializer().container<2>(res);
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(this->getContainerSize() * 2));
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
class SerializeContainerFixedSizeCompositeTypes : public SerializeContainerFixedSizeArithmeticTypes<T> {
|
||||
|
||||
};
|
||||
|
||||
using StaticContainersWithCompositeTypes = ::testing::Types<
|
||||
std::array<MyStruct1, 4>, MyStruct1[4]>;
|
||||
|
||||
TYPED_TEST_CASE(SerializeContainerFixedSizeCompositeTypes, StaticContainersWithCompositeTypes);
|
||||
|
||||
TYPED_TEST(SerializeContainerFixedSizeCompositeTypes, DefaultSerializationFunction) {
|
||||
using Container = typename TestFixture::TContainer;
|
||||
Container src{MyStruct1{0, 1}, MyStruct1{8, 9}, MyStruct1{11, 34}, MyStruct1{5134, 1532}};
|
||||
Container res{};
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().container(src);
|
||||
ctx.createDeserializer().container(res);
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(this->getContainerSize() * MyStruct1::SIZE));
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
}
|
||||
|
||||
TYPED_TEST(SerializeContainerFixedSizeCompositeTypes, CustomFunctionThatSerializesAnEmptyByteEveryElement) {
|
||||
using Container = typename TestFixture::TContainer;
|
||||
Container src{MyStruct1{0, 1}, MyStruct1{2, 3}, MyStruct1{4, 5}, MyStruct1{5134, 1532}};
|
||||
Container res{};
|
||||
|
||||
using TValue = decltype(*std::begin(res));
|
||||
|
||||
SerializationContext ctx;
|
||||
auto ser = ctx.createSerializer();
|
||||
ser.container(src, [&ser](TValue &v) {
|
||||
char tmp{};
|
||||
ser.object(v);
|
||||
ser.value1b(tmp);
|
||||
});
|
||||
auto des = ctx.createDeserializer();
|
||||
des.container(res, [&des](TValue &v) {
|
||||
char tmp{};
|
||||
des.object(v);
|
||||
des.value1b(tmp);
|
||||
});
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(this->getContainerSize() * (MyStruct1::SIZE + sizeof(char))));
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
}
|
||||
|
||||
|
||||
|
||||
140
tests/serialization_ext_container_map.cpp
Normal file
140
tests/serialization_ext_container_map.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
//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/container_map.h>
|
||||
#include <bitsery/ext/entropy.h>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
|
||||
using ContainerMap = bitsery::ext::ContainerMap;
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
|
||||
|
||||
template<typename Container>
|
||||
Container createData() {
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
std::unordered_map<std::string, MyStruct1> createData<std::unordered_map<std::string, MyStruct1>>() {
|
||||
return {
|
||||
std::make_pair("some key", MyStruct1{874,456}),
|
||||
std::make_pair("other key", MyStruct1{-34,8645}),
|
||||
std::make_pair("secret key", MyStruct1{-4878,3468975})
|
||||
};
|
||||
}
|
||||
|
||||
template<>
|
||||
std::unordered_map<int32_t, float> createData<std::unordered_map<int32_t, float>>() {
|
||||
return {
|
||||
std::pair<int32_t , float>(545, 45.485f),
|
||||
std::pair<int32_t , float>(6748, -7891.5f),
|
||||
std::pair<int32_t , float>(845, -457.0f)
|
||||
};
|
||||
}
|
||||
|
||||
template<>
|
||||
std::map<MyEnumClass, MyStruct1> createData<std::map<MyEnumClass, MyStruct1>>() {
|
||||
return {
|
||||
std::make_pair(MyEnumClass::E3, MyStruct1{874,456}),
|
||||
std::make_pair(MyEnumClass::E6, MyStruct1{-34,8645}),
|
||||
std::make_pair(MyEnumClass::E2, MyStruct1{-4878,3468975})
|
||||
};
|
||||
}
|
||||
|
||||
template<>
|
||||
std::map<int32_t ,int64_t> createData<std::map<int32_t ,int64_t>>() {
|
||||
return {//these are optimized with range and entropy
|
||||
std::pair<int32_t, int64_t>(-45, -984196845ll),
|
||||
std::pair<int32_t, int64_t>(54, 1ll),
|
||||
std::pair<int32_t, int64_t>(98, 3ll)
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
class SerializeExtensionContainerMap : public testing::Test {
|
||||
public:
|
||||
using TContainer = T;
|
||||
|
||||
const TContainer src = createData<TContainer>();
|
||||
TContainer res{};
|
||||
};
|
||||
|
||||
using SerializeExtensionContainerMapTypes = ::testing::Types<
|
||||
std::unordered_map<std::string, MyStruct1>,
|
||||
std::unordered_map<int32_t, float>,
|
||||
std::map<MyEnumClass , MyStruct1>,
|
||||
std::map<int32_t ,int64_t>
|
||||
>;
|
||||
|
||||
TYPED_TEST_CASE(SerializeExtensionContainerMap, SerializeExtensionContainerMapTypes);
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, std::unordered_map<std::string, MyStruct1>& o) {
|
||||
s.ext(o, ContainerMap{10}, [&s](std::string& key, MyStruct1& value) {
|
||||
s.text1b(key, 100);
|
||||
s.object(value);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, std::unordered_map<int32_t, float>& o) {
|
||||
s.ext(o, ContainerMap{10}, [&s](int32_t& key, float& value) {
|
||||
s.value4b(key);
|
||||
s.value4b(value);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, std::map<MyEnumClass , MyStruct1>& o) {
|
||||
s.ext(o, ContainerMap{10}, [&s](MyEnumClass& key, MyStruct1& value) {
|
||||
s.value4b(key);
|
||||
s.object(value);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, std::map<int32_t ,int64_t>& o) {
|
||||
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});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
TYPED_TEST(SerializeExtensionContainerMap, SerializeAndDeserializeEquals) {
|
||||
SerializationContext ctx1;
|
||||
ctx1.createSerializer().object(this->src);
|
||||
ctx1.createDeserializer().object(this->res);
|
||||
EXPECT_THAT(this->res, Eq(this->src));
|
||||
}
|
||||
143
tests/serialization_ext_entropy.cpp
Normal file
143
tests/serialization_ext_entropy.cpp
Normal 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));
|
||||
}
|
||||
445
tests/serialization_ext_growable.cpp
Normal file
445
tests/serialization_ext_growable.cpp
Normal file
@@ -0,0 +1,445 @@
|
||||
//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/growable.h>
|
||||
|
||||
using namespace testing;
|
||||
|
||||
using bitsery::ext::Growable;
|
||||
|
||||
using Buffer = typename bitsery::DefaultConfig::BufferType;
|
||||
using DiffType = typename bitsery::details::BufferContainerTraits<Buffer>::TDifference;
|
||||
|
||||
struct DataV1 {
|
||||
int32_t v1;
|
||||
};
|
||||
|
||||
struct DataV2 {
|
||||
int32_t v1;
|
||||
int32_t v2;
|
||||
};
|
||||
|
||||
struct DataV3 {
|
||||
int32_t v1;
|
||||
int32_t v2;
|
||||
int32_t v3;
|
||||
};
|
||||
|
||||
|
||||
TEST(SerializeExtensionGrowable, WriteSessionsDataAtBufferEndAfterFlush) {
|
||||
SerializationContext ctx;
|
||||
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(SerializeExtensionGrowable, SessionDataConsistOfSessionsEndPosAnd2BytesSessionsDataOffset) {
|
||||
SerializationContext ctx;
|
||||
|
||||
|
||||
constexpr size_t DATA_SIZE = 4;
|
||||
int32_t data{};
|
||||
|
||||
auto ser = ctx.createSerializer();
|
||||
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));
|
||||
|
||||
//read value back
|
||||
auto& br = *(ctx.br);
|
||||
br.readBytes<DATA_SIZE>(data);
|
||||
|
||||
size_t sessionEnd{};
|
||||
//there should start session data with first size of session
|
||||
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
|
||||
br.readBytes<2>(sessionsOffset);
|
||||
EXPECT_THAT(sessionsOffset, Eq(1+2));//1byte for session info, 2 bytes for session offset variable
|
||||
auto range = ctx.bw->getWrittenRange();
|
||||
auto dSize = std::distance(range.begin(), std::next(range.end(), -sessionsOffset));
|
||||
EXPECT_THAT(dSize, Eq(DATA_SIZE));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, WhenNestedSessionsThenStoreEachDepthAndSize) {
|
||||
SerializationContext ctx;
|
||||
DataV3 data{19457,846, 498418};
|
||||
ctx.createSerializer();
|
||||
ctx.bw->beginSession();
|
||||
ctx.bw->writeBytes<4>(data.v1);
|
||||
ctx.bw->beginSession();
|
||||
ctx.bw->writeBytes<4>(data.v2);
|
||||
ctx.bw->endSession();
|
||||
ctx.bw->beginSession();
|
||||
ctx.bw->writeBytes<4>(data.v3);
|
||||
ctx.bw->endSession();
|
||||
ctx.bw->endSession();
|
||||
DataV3 res{};
|
||||
ctx.createDeserializer();//to flush data and create buffer reader
|
||||
ctx.br->readBytes<4>(res.v1);
|
||||
ctx.br->readBytes<4>(res.v2);
|
||||
ctx.br->readBytes<4>(res.v3);
|
||||
//read data correctly
|
||||
EXPECT_THAT(res.v1, Eq(data.v1));
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
EXPECT_THAT(res.v3, Eq(data.v3));
|
||||
size_t sessionEnd[3];
|
||||
//read sessions sizes
|
||||
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(SerializeExtensionGrowable, WhenSessionsDataIsMoreThan0x7FFFThenWrite4BytesForSessionsOffset) {
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer();
|
||||
//create more sessions that can fit in 2 bytes
|
||||
for (auto i = 0u; i < 0x8000; ++i) {
|
||||
ctx.bw->beginSession();
|
||||
ctx.bw->endSession();
|
||||
}
|
||||
ctx.createDeserializer();//to flush data and create buffer reader
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(0x8000+4));
|
||||
uint8_t tmp{};
|
||||
for (auto i = 0u; i < 0x8000; ++i) {
|
||||
ctx.br->beginSession();
|
||||
ctx.br->endSession();
|
||||
}
|
||||
|
||||
EXPECT_THAT(ctx.br->getError(), Eq(bitsery::BufferReaderError::NO_ERROR));
|
||||
ctx.br->readBytes<1>(tmp);
|
||||
EXPECT_THAT(ctx.br->getError(), Eq(bitsery::BufferReaderError::BUFFER_OVERFLOW));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleSessionsReadSameVersionData) {
|
||||
SerializationContext ctx;
|
||||
DataV2 data{8454,987451};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
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 < 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));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleSessionsReadNewerVersionData) {
|
||||
SerializationContext ctx;
|
||||
DataV3 data{8454,987451,54};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
bw.beginSession();
|
||||
bw.writeBytes<4>(data.v1);
|
||||
bw.writeBytes<4>(data.v2);
|
||||
bw.writeBytes<4>(data.v3);
|
||||
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 < 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(br.isCompletedSuccessfully(), Eq(true));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleSessionsReadOlderVersionData) {
|
||||
SerializationContext ctx;
|
||||
DataV2 data{8454,987451};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
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
|
||||
DataV3 res{4798,657891,985};
|
||||
auto& br = (*ctx.br);
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
br.beginSession();
|
||||
br.readBytes<4>(res.v1);
|
||||
br.readBytes<4>(res.v2);
|
||||
br.readBytes<4>(res.v3);
|
||||
br.endSession();
|
||||
EXPECT_THAT(res.v1, Eq(data.v1));
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
EXPECT_THAT(res.v3, Eq(0));
|
||||
}
|
||||
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadSameVersionData) {
|
||||
SerializationContext ctx;
|
||||
DataV2 data{8454,987451};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
for (auto i = 0; i < 2; ++i) {
|
||||
bw.beginSession();
|
||||
bw.writeBytes<4>(data.v1);
|
||||
bw.writeBytes<4>(data.v2);
|
||||
bw.beginSession();
|
||||
bw.writeBytes<4>(data.v1);
|
||||
bw.writeBytes<4>(data.v2);
|
||||
bw.endSession();
|
||||
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 < 2; ++i) {
|
||||
br.beginSession();
|
||||
br.readBytes<4>(res.v1);
|
||||
br.readBytes<4>(res.v2);
|
||||
EXPECT_THAT(res.v1, Eq(data.v1));
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
br.beginSession();
|
||||
br.readBytes<4>(res.v1);
|
||||
br.readBytes<4>(res.v2);
|
||||
EXPECT_THAT(res.v1, Eq(data.v1));
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
br.endSession();
|
||||
br.endSession();
|
||||
}
|
||||
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadOlderVersionData) {
|
||||
SerializationContext ctx;
|
||||
DataV2 data{8454,987451};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
for (auto i = 0; i < 5; ++i) {
|
||||
bw.beginSession();
|
||||
bw.writeBytes<4>(data.v1);
|
||||
bw.endSession();
|
||||
bw.writeBytes<4>(data.v2);
|
||||
}
|
||||
|
||||
//create more sessions that can fit in 2 bytes
|
||||
ctx.createDeserializer();//to flush data and create buffer reader
|
||||
DataV3 res{};
|
||||
auto& br = (*ctx.br);
|
||||
for (auto i = 0; i < 5; ++i) {
|
||||
br.beginSession();
|
||||
br.readBytes<4>(res.v1);
|
||||
EXPECT_THAT(res.v1, Eq(data.v1));
|
||||
//new flow
|
||||
br.beginSession();
|
||||
br.readBytes<4>(res.v3);
|
||||
EXPECT_THAT(res.v3, Eq(0));
|
||||
br.beginSession();
|
||||
br.readBytes<4>(res.v3);
|
||||
EXPECT_THAT(res.v3, Eq(0));
|
||||
br.endSession();
|
||||
br.endSession();
|
||||
br.beginSession();
|
||||
br.readBytes<4>(res.v3);
|
||||
EXPECT_THAT(res.v3, Eq(0));
|
||||
br.endSession();
|
||||
br.endSession();
|
||||
br.readBytes<4>(res.v2);
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
}
|
||||
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadNewerVersionData1) {
|
||||
SerializationContext ctx;
|
||||
DataV3 data{8454,987451,54};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
for (auto i = 0; i < 2; ++i) {
|
||||
bw.beginSession();
|
||||
{
|
||||
bw.writeBytes<4>(data.v1);
|
||||
bw.writeBytes<4>(data.v2);
|
||||
//new flow
|
||||
bw.beginSession();
|
||||
{
|
||||
bw.writeBytes<4>(data.v3);
|
||||
}
|
||||
bw.endSession();
|
||||
bw.beginSession();
|
||||
{
|
||||
bw.writeBytes<4>(data.v3);
|
||||
bw.beginSession();
|
||||
{
|
||||
bw.beginSession();
|
||||
{
|
||||
bw.writeBytes<4>(data.v3);
|
||||
bw.writeBytes<4>(data.v3);
|
||||
}
|
||||
bw.endSession();
|
||||
bw.writeBytes<4>(data.v3);
|
||||
}
|
||||
bw.endSession();
|
||||
}
|
||||
bw.endSession();
|
||||
bw.writeBytes<4>(data.v3);
|
||||
}
|
||||
bw.endSession();
|
||||
bw.writeBytes<4>(data.v2);
|
||||
}
|
||||
//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 < 2; ++i) {
|
||||
br.beginSession();
|
||||
{
|
||||
br.readBytes<4>(res.v1);
|
||||
br.readBytes<4>(res.v2);
|
||||
EXPECT_THAT(res.v1, Eq(data.v1));
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
}
|
||||
br.endSession();
|
||||
br.readBytes<4>(res.v2);
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
}
|
||||
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadNewerVersionData2) {
|
||||
SerializationContext ctx;
|
||||
DataV3 data{8454,987451,54};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
for (auto i = 0; i < 2; ++i) {
|
||||
bw.beginSession();
|
||||
bw.writeBytes<4>(data.v1);
|
||||
bw.writeBytes<4>(data.v2);
|
||||
bw.beginSession();
|
||||
bw.writeBytes<4>(data.v1);
|
||||
bw.writeBytes<4>(data.v2);
|
||||
|
||||
//new flow
|
||||
bw.beginSession();
|
||||
bw.writeBytes<4>(data.v3);
|
||||
bw.endSession();
|
||||
bw.beginSession();
|
||||
bw.writeBytes<4>(data.v3);
|
||||
bw.beginSession();
|
||||
bw.beginSession();
|
||||
bw.writeBytes<4>(data.v3);
|
||||
bw.writeBytes<4>(data.v3);
|
||||
bw.endSession();
|
||||
bw.writeBytes<4>(data.v3);
|
||||
bw.endSession();
|
||||
bw.endSession();
|
||||
bw.writeBytes<4>(data.v3);
|
||||
bw.endSession();
|
||||
bw.writeBytes<4>(data.v2);
|
||||
|
||||
//new flow
|
||||
bw.writeBytes<4>(data.v3);
|
||||
bw.beginSession();
|
||||
bw.writeBytes<4>(data.v3);
|
||||
bw.endSession();
|
||||
bw.writeBytes<4>(data.v3);
|
||||
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 < 2; ++i) {
|
||||
br.beginSession();
|
||||
br.readBytes<4>(res.v1);
|
||||
br.readBytes<4>(res.v2);
|
||||
EXPECT_THAT(res.v1, Eq(data.v1));
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
br.beginSession();
|
||||
br.readBytes<4>(res.v1);
|
||||
br.readBytes<4>(res.v2);
|
||||
EXPECT_THAT(res.v1, Eq(data.v1));
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
br.endSession();
|
||||
br.readBytes<4>(res.v2);
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
br.endSession();
|
||||
}
|
||||
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));
|
||||
}
|
||||
@@ -24,36 +24,30 @@
|
||||
#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>
|
||||
|
||||
|
||||
template <typename T>
|
||||
using extoptional = bitsery::ext::optional<T>;
|
||||
using Optional = bitsery::ext::Optional;
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
|
||||
template <typename T>
|
||||
void test(SerializationContext& ctx, const T& v, T& r) {
|
||||
auto fnc = [](auto ser, auto& v) {
|
||||
ser.template value<sizeof(v)>(v);
|
||||
};
|
||||
ctx.createSerializer().ext<extoptional>(v, fnc);
|
||||
ctx.createDeserializer().ext<extoptional>(r, fnc);
|
||||
ctx.createSerializer().ext4b(v, Optional{});
|
||||
ctx.createDeserializer().ext4b(r, Optional{});
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionOptional, EmptyOptional) {
|
||||
std::optional<int> t1{};
|
||||
std::optional<int> r1{};
|
||||
std::optional<int32_t> t1{};
|
||||
std::optional<int32_t> r1{};
|
||||
|
||||
SerializationContext ctx1;
|
||||
test(ctx1,t1, r1);
|
||||
@@ -69,8 +63,8 @@ TEST(SerializeExtensionOptional, EmptyOptional) {
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionOptional, OptionalHasValue) {
|
||||
std::optional<int> t1{43};
|
||||
std::optional<int> r1{52};
|
||||
std::optional<int32_t> t1{43};
|
||||
std::optional<int32_t> r1{52};
|
||||
|
||||
SerializationContext ctx1;
|
||||
test(ctx1,t1, r1);
|
||||
@@ -84,3 +78,5 @@ TEST(SerializeExtensionOptional, OptionalHasValue) {
|
||||
EXPECT_THAT(t1.value(), Eq(r1.value()));
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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,112 +47,126 @@ 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(SerializeExtensionValueRange, WhenDataIsInvalidThenReturnMinimumRangeValue) {
|
||||
SerializationContext ctx;
|
||||
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().ext(res1, r1);
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
|
||||
EXPECT_THAT(res1, Eq(4));
|
||||
}
|
||||
@@ -1,131 +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"
|
||||
#include <type_traits>
|
||||
|
||||
using testing::ContainerEq;
|
||||
using testing::Eq;
|
||||
|
||||
TEST(SerializeFSArrayStdArray, ArithmeticValues) {
|
||||
SerializationContext ctx;
|
||||
std::array<int, 4> src{5,9,15,-459};
|
||||
std::array<int, 4> res{};
|
||||
|
||||
ctx.createSerializer().array<sizeof(int)>(src);
|
||||
ctx.createDeserializer().array<sizeof(int)>(res);
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(src.size() * sizeof(int)));
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
|
||||
}
|
||||
|
||||
TEST(SerializeFSArrayStdArray, CompositeTypes) {
|
||||
SerializationContext ctx;
|
||||
std::array<MyStruct1, 7> src{
|
||||
MyStruct1{0,1}, MyStruct1{2,3}, MyStruct1{4,5}, MyStruct1{6,7},
|
||||
MyStruct1{8,9}, MyStruct1{11,34}, MyStruct1{5134,1532}};
|
||||
std::array<MyStruct1, 7> res{};
|
||||
|
||||
ctx.createSerializer().array(src);
|
||||
ctx.createDeserializer().array(res);
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(src.size() * MyStruct1::SIZE));
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
}
|
||||
//
|
||||
//
|
||||
TEST(SerializeFSArrayStdArray, CustomFunctionThatSerializesAnEmptyByteEveryElement) {
|
||||
SerializationContext ctx;
|
||||
std::array<MyStruct1, 7> src{
|
||||
MyStruct1{0,1}, MyStruct1{2,3}, MyStruct1{4,5}, MyStruct1{6,7},
|
||||
MyStruct1{8,9}, MyStruct1{11,34}, MyStruct1{5134,1532}};
|
||||
std::array<MyStruct1, 7> res{};
|
||||
|
||||
|
||||
auto ser = ctx.createSerializer();
|
||||
ser.array(src, [](auto &s, auto& v) {
|
||||
char tmp{};
|
||||
s.object(v).value1(tmp);
|
||||
});
|
||||
auto des = ctx.createDeserializer();
|
||||
des.array(res, [](auto &s, auto& v) {
|
||||
char tmp{};
|
||||
s.object(v).value1(tmp);
|
||||
});
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(src.size() * (MyStruct1::SIZE + sizeof(char))));
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
}
|
||||
|
||||
TEST(SerializeFSArrayCArray, ArithmeticValuesSettingValueSizeExplicitly) {
|
||||
SerializationContext ctx;
|
||||
int src[4]{5,9,15,-459};
|
||||
int res[4]{};
|
||||
|
||||
ctx.createSerializer().array<sizeof(int)>(src);
|
||||
ctx.createDeserializer().array<sizeof(int)>(res);
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(std::extent<decltype(src)>::value * sizeof(int)));
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
|
||||
}
|
||||
|
||||
TEST(SerializeFSArrayCArray, CompositeTypes) {
|
||||
SerializationContext ctx;
|
||||
MyStruct1 src[]{
|
||||
MyStruct1{0,1}, MyStruct1{2,3}, MyStruct1{4,5}, MyStruct1{6,7},
|
||||
MyStruct1{8,9}, MyStruct1{11,34}, MyStruct1{5134,1532}};
|
||||
MyStruct1 res[7]{};
|
||||
|
||||
ctx.createSerializer().array(src);
|
||||
ctx.createDeserializer().array(res);
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(std::extent<decltype(src)>::value * MyStruct1::SIZE));
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
}
|
||||
//
|
||||
//
|
||||
TEST(SerializeFSArrayCArray, CustomFunctionThatSerializesAnEmptyByteEveryElement) {
|
||||
SerializationContext ctx;
|
||||
MyStruct1 src[]{
|
||||
MyStruct1{0,1}, MyStruct1{2,3}, MyStruct1{4,5}, MyStruct1{6,7},
|
||||
MyStruct1{8,9}, MyStruct1{11,34}, MyStruct1{5134,1532}};
|
||||
MyStruct1 res[7]{};
|
||||
|
||||
|
||||
auto ser = ctx.createSerializer();
|
||||
ser.array(src, [](auto& s, auto& v) {
|
||||
char tmp{};
|
||||
s.object(v).value1(tmp);
|
||||
});
|
||||
auto des = ctx.createDeserializer();
|
||||
des.array(res, [](auto& s, auto& v) {
|
||||
char tmp{};
|
||||
s.object(v).value1(tmp);
|
||||
});
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(std::extent<decltype(src)>::value * (MyStruct1::SIZE + sizeof(char))));
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
}
|
||||
@@ -24,13 +24,6 @@
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
#include <bitsery/delta_serializer.h>
|
||||
#include <bitsery/delta_deserializer.h>
|
||||
|
||||
#include <list>
|
||||
|
||||
|
||||
|
||||
using testing::Eq;
|
||||
using testing::StrEq;
|
||||
using testing::ContainerEq;
|
||||
@@ -56,28 +49,29 @@ struct Y {
|
||||
struct Z { X x{}; Y y{}; };
|
||||
|
||||
|
||||
SERIALIZE(Z)
|
||||
template <typename S>
|
||||
void serialize(S& s, Z& o)
|
||||
{
|
||||
s.object(o.x);
|
||||
s.object(o.y);
|
||||
return s;
|
||||
}
|
||||
|
||||
SERIALIZE(X)
|
||||
template <typename S>
|
||||
void serialize(S& s, X& o)
|
||||
{
|
||||
return s.template value<sizeof(o.x)>(o.x)
|
||||
.template text<1>(o.s, 1000);
|
||||
s.template value<sizeof(o.x)>(o.x);
|
||||
s.template text<1>(o.s, 1000);
|
||||
}
|
||||
|
||||
SERIALIZE(Y)
|
||||
template <typename S>
|
||||
void serialize(S& s, Y& o)
|
||||
{
|
||||
auto writeInt = [](auto& s, auto& v) { s.template value<sizeof(v)>(v); };
|
||||
auto writeInt = [&s]( int& v) { s.template value<sizeof(v)>(v); };
|
||||
s.template text<1>(o.s, 10000);
|
||||
s.template value<sizeof(o.y)>(o.y);
|
||||
s.array(o.arr, writeInt);
|
||||
s.array(o.carr, writeInt);
|
||||
s.container(o.vx, 10000, [](auto& s, auto& v) { s.object(v); });
|
||||
return s;
|
||||
s.container(o.arr, writeInt);
|
||||
s.container(o.carr, writeInt);
|
||||
s.container(o.vx, 10000, [&s](X& v) { s.object(v); });
|
||||
}
|
||||
|
||||
|
||||
@@ -128,49 +122,4 @@ TEST(SerializeObject, GeneralConceptTest) {
|
||||
EXPECT_THAT(zres.x.s, StrEq(z.x.s));
|
||||
EXPECT_THAT(zres.x.x, Eq(z.x.x));
|
||||
|
||||
}
|
||||
|
||||
TEST(DeltaSerializer, GeneralConceptTest) {
|
||||
//std::string buf;
|
||||
Y y{};
|
||||
y.y = 3423;
|
||||
y.arr[0] = 111;
|
||||
y.arr[1] = 222;
|
||||
y.arr[2] = 333;
|
||||
y.carr[0] = 123;
|
||||
y.carr[1] = 456;
|
||||
y.carr[2] = 789;
|
||||
y.vx.push_back(X(234));
|
||||
y.vx.push_back(X(6245));
|
||||
y.vx.push_back(X(613461));
|
||||
y.s = "labal diena";
|
||||
y.vx[0].s = "very nice";
|
||||
y.vx[1].s = "very nice string, that is a little bit longer that previous";
|
||||
|
||||
Y yRead = y;
|
||||
Y yNew = y;
|
||||
yNew.y = 111111;
|
||||
yNew.arr[2] = 0xFFFFFFFF;
|
||||
yNew.carr[1] = 0xFFFFFFFF;
|
||||
yNew.s = "labas dienaABC";
|
||||
yNew.vx[0].s = "very opapa";
|
||||
yNew.vx[1].s = "bla";
|
||||
yNew.vx.push_back(X{ 3 });
|
||||
|
||||
std::vector<bitsery::DefaultConfig::BufferValueType> buf;
|
||||
bitsery::BufferWriter bw{ buf };
|
||||
bitsery::DeltaSerializer<bitsery::BufferWriter, Y> ser(bw, y, yNew);
|
||||
serialize(ser, yNew);
|
||||
bw.flush();
|
||||
|
||||
bitsery::BufferReader br{ buf };
|
||||
bitsery::DeltaDeserializer<bitsery::BufferReader, Y> des(br, y, yRead);
|
||||
serialize(des, yRead);
|
||||
|
||||
EXPECT_THAT(yRead.y, Eq(yNew.y));
|
||||
EXPECT_THAT(yRead.vx, ContainerEq(yNew.vx));
|
||||
EXPECT_THAT(yRead.arr, ContainerEq(yNew.arr));
|
||||
EXPECT_THAT(yRead.carr, ContainerEq(yNew.carr));
|
||||
EXPECT_THAT(yRead.s, StrEq(yNew.s));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,9 +28,9 @@ using testing::Eq;
|
||||
|
||||
bool SerializeDeserializeContainerSize(SerializationContext& ctx, const size_t size) {
|
||||
std::vector<char> t1(size);
|
||||
ctx.createSerializer().container(t1, size+1, [](auto , auto ){});
|
||||
ctx.createSerializer().container(t1, size+1, []( char& ){});
|
||||
t1.clear();
|
||||
ctx.createDeserializer().container(t1, size+1, [](auto , auto ){});
|
||||
ctx.createDeserializer().container(t1, size+1, []( char& ){});
|
||||
return t1.size() == size;
|
||||
}
|
||||
|
||||
|
||||
@@ -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(SerializeSubstitution, WhenSubstitutedThenOnlyWriteIndexUsingMinRequiredBits) {
|
||||
int32_t v = 4849;
|
||||
int32_t res;
|
||||
constexpr size_t N = 3;
|
||||
std::array<int32_t,N> subsitution{485,4849,89};
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().substitution<4>(v, subsitution);
|
||||
ctx.createDeserializer().substitution<4>(res, subsitution);
|
||||
|
||||
EXPECT_THAT(res, Eq(v));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
|
||||
|
||||
SerializationContext ctx1;
|
||||
ctx1.createSerializer().substitution<4>(v, subsitution);
|
||||
auto des = ctx1.createDeserializer();
|
||||
des.range(res, {0, N + 1});
|
||||
EXPECT_THAT(res, Eq(2));
|
||||
}
|
||||
|
||||
TEST(SerializeSubstitution, WhenNoSubstitutionThenWriteZeroBitsAndValueOrObject) {
|
||||
int16_t v = 8945;
|
||||
int16_t res;
|
||||
std::array<int16_t,3> subsitution{485,4849,89};
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().substitution<2>(v, subsitution);
|
||||
ctx.createDeserializer().substitution<2>(res, subsitution);
|
||||
|
||||
EXPECT_THAT(res, Eq(v));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(sizeof(int16_t)+1));
|
||||
}
|
||||
|
||||
TEST(SerializeSubstitution, CustomTypeSubstituted) {
|
||||
MyStruct1 v = {12,10};
|
||||
MyStruct1 res;
|
||||
constexpr size_t N = 4;
|
||||
std::array<MyStruct1, N> subsitution = {
|
||||
MyStruct1{12,10}, MyStruct1{485, 454},
|
||||
MyStruct1{4849,89}, MyStruct1{0,1}};
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().substitution(v, subsitution);
|
||||
ctx.createDeserializer().substitution(res, subsitution);
|
||||
|
||||
EXPECT_THAT(res, Eq(v));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
|
||||
}
|
||||
|
||||
TEST(SerializeSubstitution, CustomTypeNotSubstituted) {
|
||||
MyStruct1 v = {8945,4456};
|
||||
MyStruct1 res;
|
||||
constexpr size_t N = 4;
|
||||
std::array<MyStruct1, N> subsitution = {
|
||||
MyStruct1{12,10}, MyStruct1{485, 454},
|
||||
MyStruct1{4849,89}, MyStruct1{0,1}};
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().substitution(v, subsitution);
|
||||
ctx.createDeserializer().substitution(res, subsitution);
|
||||
|
||||
EXPECT_THAT(res, Eq(v));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(MyStruct1::SIZE + 1));
|
||||
}
|
||||
|
||||
TEST(SerializeSubstitution, CustomFunctionNotSubstituted) {
|
||||
MyStruct1 v = {8945,4456};
|
||||
MyStruct1 res;
|
||||
constexpr size_t N = 4;
|
||||
std::array<MyStruct1, N> subsitution = {
|
||||
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 = [rangeForValue](auto& s, const MyStruct1& v) {
|
||||
s.range(v.i1, rangeForValue);
|
||||
s.range(v.i2, rangeForValue);
|
||||
};
|
||||
ser.substitution(v, subsitution, serLambda);
|
||||
|
||||
auto des = ctx.createDeserializer();
|
||||
auto desLambda = [rangeForValue](auto& s, MyStruct1& v) {
|
||||
s.range(v.i1, rangeForValue);
|
||||
s.range(v.i2, rangeForValue);
|
||||
};
|
||||
des.substitution(res, subsitution, desLambda);
|
||||
|
||||
EXPECT_THAT(res, Eq(v));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq((rangeForIndex.bitsRequired + rangeForValue.bitsRequired * 2 - 1) / 8 + 1 ));
|
||||
}
|
||||
|
||||
TEST(SerializeSubstitution, WhenSubstitutedThenCustomFunctionNotInvoked) {
|
||||
MyStruct1 v = {4849,89};
|
||||
MyStruct1 res;
|
||||
constexpr size_t N = 4;
|
||||
std::array<MyStruct1, N> subsitution = {
|
||||
MyStruct1{12,10}, MyStruct1{485, 454},
|
||||
MyStruct1{4849,89}, MyStruct1{0,1}};
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().substitution(v, subsitution, [](bitsery::Serializer<bitsery::BufferWriter>& ,const MyStruct1& ) {});
|
||||
ctx.createDeserializer().substitution(res, subsitution, [](bitsery::Deserializer<bitsery::BufferReader>&, MyStruct1& ) {});
|
||||
|
||||
EXPECT_THAT(res, Eq(v));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
|
||||
}
|
||||
@@ -21,8 +21,8 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_SERIALIZERTESTS_H
|
||||
#define BITSERY_SERIALIZERTESTS_H
|
||||
#ifndef BITSERY_SERIALIZER_TEST_UTILS_H
|
||||
#define BITSERY_SERIALIZER_TEST_UTILS_H
|
||||
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <memory>
|
||||
@@ -33,23 +33,27 @@
|
||||
*/
|
||||
|
||||
struct MyStruct1 {
|
||||
MyStruct1(int v1, int v2):i1{v1}, i2{v2} {}
|
||||
MyStruct1():MyStruct1{0,0} {}
|
||||
MyStruct1(int32_t v1, int32_t v2) : i1{v1}, i2{v2} {}
|
||||
|
||||
MyStruct1() : MyStruct1{0, 0} {}
|
||||
|
||||
int i1;
|
||||
int i2;
|
||||
bool operator == (const MyStruct1& rhs) const {
|
||||
|
||||
bool operator==(const MyStruct1 &rhs) const {
|
||||
return i1 == rhs.i1 && i2 == rhs.i2;
|
||||
}
|
||||
|
||||
static constexpr size_t SIZE = sizeof(MyStruct1::i1) + sizeof(MyStruct1::i2);
|
||||
};
|
||||
|
||||
SERIALIZE(MyStruct1) {
|
||||
return s.
|
||||
template value<sizeof(o.i1)>(o.i1).
|
||||
template value<sizeof(o.i2)>(o.i2);
|
||||
template <typename S>
|
||||
void serialize(S& s, MyStruct1& o) {
|
||||
s.template value<sizeof(o.i1)>(o.i1);
|
||||
s.template value<sizeof(o.i2)>(o.i2);
|
||||
}
|
||||
|
||||
enum class MyEnumClass {
|
||||
enum class MyEnumClass:int32_t {
|
||||
E1, E2, E3, E4, E5, E6
|
||||
};
|
||||
|
||||
@@ -58,37 +62,44 @@ struct MyStruct2 {
|
||||
V1, V2, V3, V4, V5, V6
|
||||
};
|
||||
|
||||
MyStruct2(MyEnum e, MyStruct1 s):e1{e}, s1{s} {}
|
||||
MyStruct2():MyStruct2{V1,{0,0}} {}
|
||||
MyStruct2(MyEnum e, MyStruct1 s) : e1{e}, s1{s} {}
|
||||
|
||||
MyStruct2() : MyStruct2{V1, {0, 0}} {}
|
||||
|
||||
MyEnum e1;
|
||||
MyStruct1 s1;
|
||||
bool operator == (const MyStruct2& rhs) const {
|
||||
|
||||
bool operator==(const MyStruct2 &rhs) const {
|
||||
return e1 == rhs.e1 && s1 == rhs.s1;
|
||||
}
|
||||
|
||||
static constexpr size_t SIZE = MyStruct1::SIZE + sizeof(MyStruct2::e1);
|
||||
};
|
||||
|
||||
SERIALIZE(MyStruct2) {
|
||||
return s.
|
||||
template value<sizeof(o.e1)>(o.e1).
|
||||
object(o.s1);
|
||||
template <typename S>
|
||||
void serialize(S&s, MyStruct2& o) {
|
||||
s.template value<sizeof(o.e1)>(o.e1);
|
||||
s.object(o.s1);
|
||||
}
|
||||
|
||||
|
||||
class SerializationContext {
|
||||
std::vector<bitsery::DefaultConfig::BufferValueType> buf{};
|
||||
public:
|
||||
bitsery::DefaultConfig::BufferType buf{};
|
||||
std::unique_ptr<bitsery::BufferWriter> bw;
|
||||
std::unique_ptr<bitsery::BufferReader> br;
|
||||
public:
|
||||
bitsery::Serializer<bitsery::BufferWriter> createSerializer() {
|
||||
bw = std::make_unique<bitsery::BufferWriter>(buf);
|
||||
return {*bw};
|
||||
|
||||
bitsery::Serializer createSerializer() {
|
||||
//make_unique is not in c++11
|
||||
bw = std::unique_ptr<bitsery::BufferWriter>(new bitsery::BufferWriter(buf));
|
||||
return bitsery::Serializer{*bw};
|
||||
};
|
||||
|
||||
size_t getBufferSize() const {
|
||||
return buf.size();
|
||||
auto range = bw->getWrittenRange();
|
||||
return std::distance(range.begin(), range.end());
|
||||
}
|
||||
|
||||
//since all containers .size() method returns size_t, it cannot be directly serialized, because size_t is platform dependant
|
||||
//this function returns number of bytes writen to buffer, when reading/writing size of container
|
||||
static size_t containerSizeSerializedBytesCount(size_t elemsCount) {
|
||||
@@ -99,11 +110,12 @@ public:
|
||||
return 4;
|
||||
}
|
||||
|
||||
bitsery::Deserializer<bitsery::BufferReader> createDeserializer() {
|
||||
bitsery::Deserializer createDeserializer() {
|
||||
bw->flush();
|
||||
br = std::make_unique<bitsery::BufferReader>(buf);
|
||||
return {*br};
|
||||
//make_unique is not in c++11
|
||||
br = std::unique_ptr<bitsery::BufferReader>(new bitsery::BufferReader(bw->getWrittenRange()));
|
||||
return bitsery::Deserializer{*br};
|
||||
};
|
||||
};
|
||||
|
||||
#endif //BITSERY_SERIALIZERTESTS_H
|
||||
#endif //BITSERY_SERIALIZER_TEST_UTILS_H
|
||||
|
||||
@@ -108,17 +108,11 @@ TEST(SerializeText, CArraySerializesTextLength) {
|
||||
EXPECT_THAT(r1, ContainerEq(t1));
|
||||
}
|
||||
|
||||
TEST(SerializeText, WhenCArrayNotNullterminatedThenMakeItNullterminated) {
|
||||
TEST(SerializeText, WhenCArrayNotNullterminatedThenAssert) {
|
||||
SerializationContext ctx;
|
||||
char16_t t1[CARR_LENGTH]{u"some text"};
|
||||
//make last character not nullterminated
|
||||
t1[CARR_LENGTH-1] = 'x';
|
||||
char16_t r1[CARR_LENGTH]{};
|
||||
|
||||
ctx.createSerializer().text<2>(t1);
|
||||
ctx.createDeserializer().text<2>(r1);
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(ctx.containerSizeSerializedBytesCount(CARR_LENGTH) +
|
||||
(CARR_LENGTH - 1) * 2));
|
||||
EXPECT_THAT(r1[CARR_LENGTH-1], Eq(0));
|
||||
EXPECT_DEATH(ctx.createSerializer().text<2>(t1), "");
|
||||
}
|
||||
@@ -65,8 +65,8 @@ TEST(SerializeValues, ValueSizeOverload1Byte) {
|
||||
constexpr size_t TSIZE = sizeof(v);
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().value1(v);
|
||||
ctx.createDeserializer().value1(res);
|
||||
ctx.createSerializer().value1b(v);
|
||||
ctx.createDeserializer().value1b(res);
|
||||
|
||||
EXPECT_THAT(res, Eq(v));
|
||||
EXPECT_THAT(TSIZE, Eq(ctx.getBufferSize()));
|
||||
@@ -78,8 +78,8 @@ TEST(SerializeValues, ValueSizeOverload2Byte) {
|
||||
constexpr size_t TSIZE = sizeof(v);
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().value2(v);
|
||||
ctx.createDeserializer().value2(res);
|
||||
ctx.createSerializer().value2b(v);
|
||||
ctx.createDeserializer().value2b(res);
|
||||
|
||||
EXPECT_THAT(res, Eq(v));
|
||||
EXPECT_THAT(TSIZE, Eq(ctx.getBufferSize()));
|
||||
@@ -91,8 +91,8 @@ TEST(SerializeValues, ValueSizeOverload4Byte) {
|
||||
constexpr size_t TSIZE = sizeof(v);
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().value4(v);
|
||||
ctx.createDeserializer().value4(res);
|
||||
ctx.createSerializer().value4b(v);
|
||||
ctx.createDeserializer().value4b(res);
|
||||
|
||||
EXPECT_THAT(res, Eq(v));
|
||||
EXPECT_THAT(TSIZE, Eq(ctx.getBufferSize()));
|
||||
@@ -104,8 +104,8 @@ TEST(SerializeValues, ValueSizeOverload8Byte) {
|
||||
constexpr size_t TSIZE = sizeof(v);
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().value8(v);
|
||||
ctx.createDeserializer().value8(res);
|
||||
ctx.createSerializer().value8b(v);
|
||||
ctx.createDeserializer().value8b(res);
|
||||
|
||||
EXPECT_THAT(res, Eq(v));
|
||||
EXPECT_THAT(TSIZE, Eq(ctx.getBufferSize()));
|
||||
|
||||
Reference in New Issue
Block a user