mirror of
https://github.com/fraillt/bitsery.git
synced 2026-06-15 19:59:13 +00:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d787680819 | ||
|
|
a519d333e2 | ||
|
|
826b8d4269 | ||
|
|
16f637da0d | ||
|
|
9cade41dbb | ||
|
|
3dc5940c16 | ||
|
|
a544879b22 | ||
|
|
d47ee834e4 | ||
|
|
c556c75100 | ||
|
|
541632fa9e | ||
|
|
d24dfe14f5 | ||
|
|
ee68261124 | ||
|
|
d22b7c1527 | ||
|
|
8d6ffc9873 | ||
|
|
0e76e0608c | ||
|
|
501d60f67d | ||
|
|
04afd16fbd | ||
|
|
9621db1cd7 | ||
|
|
c555088aa3 | ||
|
|
c9619e3e3d | ||
|
|
01d56e00b8 | ||
|
|
04526ff0f4 | ||
|
|
b7d159bbfc | ||
|
|
f85dff7415 | ||
|
|
f35ae3f4dc | ||
|
|
ff40222124 | ||
|
|
a1785f3e15 | ||
|
|
105aa5f9e5 | ||
|
|
1822796f2e | ||
|
|
57dd028b7a | ||
|
|
03f2c3c8b5 | ||
|
|
aca3139600 | ||
|
|
c1ae593fb4 |
74
.travis.yml
74
.travis.yml
@@ -1,30 +1,70 @@
|
||||
dist: xenial
|
||||
language: cpp
|
||||
|
||||
compiler:
|
||||
- gcc
|
||||
# clang
|
||||
# CXX_COMPILER and CC_COMPILER is defined, because travis will override CC and CXX environment variables
|
||||
# We'll need to override them back "before_install"
|
||||
matrix:
|
||||
include:
|
||||
- addons:
|
||||
apt:
|
||||
packages:
|
||||
- g++-5
|
||||
env:
|
||||
- CXXSTD=11
|
||||
- CXX_COMPILER=g++-5
|
||||
- CC_COMPILER=gcc-5
|
||||
- addons:
|
||||
apt:
|
||||
packages:
|
||||
- clang-3.9
|
||||
env:
|
||||
- CXXSTD=11
|
||||
- CXX_COMPILER=clang++-3.9
|
||||
- CC_COMPILER=clang-3.9
|
||||
- addons:
|
||||
apt:
|
||||
packages:
|
||||
- g++-7
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
env:
|
||||
- CXXSTD=17
|
||||
- CXX_COMPILER=g++-7
|
||||
- CC_COMPILER=gcc-7
|
||||
- addons:
|
||||
apt:
|
||||
packages:
|
||||
- libstdc++-7-dev
|
||||
- clang-8
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- llvm-toolchain-xenial-8
|
||||
- sourceline: 'deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-8 main'
|
||||
key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key'
|
||||
env:
|
||||
- CXXSTD=17
|
||||
- CXX_COMPILER=clang++-8
|
||||
- CC_COMPILER=clang-8
|
||||
|
||||
before_install:
|
||||
# C++14
|
||||
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||
- sudo apt-get update -qq
|
||||
- export CXX=$CXX_COMPILER
|
||||
- export CC=$CC_COMPILER
|
||||
|
||||
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
|
||||
- wget https://github.com/google/googletest/archive/release-1.8.0.tar.gz
|
||||
- tar xf release-1.8.0.tar.gz
|
||||
- cd googletest-release-1.8.0
|
||||
- wget https://github.com/google/googletest/archive/release-1.10.0.tar.gz
|
||||
- tar xf release-1.10.0.tar.gz
|
||||
- cd googletest-release-1.10.0
|
||||
- cmake -DBUILD_SHARED_LIBS=ON .
|
||||
- make
|
||||
- sudo make install
|
||||
- cd ..
|
||||
|
||||
before_script:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -DBITSERY_BUILD_TESTS=ON ..
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -DBITSERY_BUILD_TESTS=ON -DCMAKE_CXX_STANDARD=$CXXSTD ..
|
||||
|
||||
script: make && (cd tests && ctest)
|
||||
|
||||
script:
|
||||
- make
|
||||
- cd tests
|
||||
- ctest
|
||||
113
CHANGELOG.md
113
CHANGELOG.md
@@ -1,3 +1,110 @@
|
||||
# [5.1.0](https://github.com/fraillt/bitsery/compare/v5.0.3...v5.1.0) (2020-06-08)
|
||||
|
||||
### Features
|
||||
|
||||
* new extension **StdAtomic** (thanks for [VelocityRa](https://github.com/VelocityRa)).
|
||||
|
||||
### Improvements
|
||||
* [examples](examples) no longer include bitsery in global namespace (removed `using namespace bitsery`).
|
||||
* removed multiple warning regarding integer conversions (thanks to [tower120](https://github.com/tower120)).
|
||||
* removed unnecessary `dynamic_cast` from BasicInput/OutputStreamAdapter (thanks to [tower120](https://github.com/tower120)).
|
||||
* fixed some include paths, now you can basically to copy/paste bitsery include directory to your project without cmake support.
|
||||
|
||||
### Other notes
|
||||
* added tutorial of how to write your own extension ([here])(doc/tutorial/first_extension.md).
|
||||
* now gtest 1.10 is required if you want to build tests.
|
||||
|
||||
# [5.0.3](https://github.com/fraillt/bitsery/compare/v5.0.2...v5.0.3) (2020-01-29)
|
||||
|
||||
### Improvements
|
||||
* rewritten buffer adapters (and `BasicBufferedOutputStreamAdapter`) to fix UB when incrementing past the end iterator, and added an additional read/write method that accepts a number of bytes to be read/written at compile time.
|
||||
This provides additional optimization opportunities.
|
||||
|
||||
# [5.0.2](https://github.com/fraillt/bitsery/compare/v5.0.1...v5.0.2) (2020-01-17)
|
||||
|
||||
### Bug fixes
|
||||
* fixed a bug when deserializing non-default constructible containers (thanks to [nicktrandafil](https://github.com/nicktrandafil)).
|
||||
* fixed issue with a brace initialization in extension StdMap and StdSet. It was working on major compilers, but it wasn't C++11 compatible.
|
||||
More info about it in [stackoverflow](https://stackoverflow.com/questions/25612262/why-does-auto-x3-deduce-an-initializer-list) (thanks to [BotellaA](https://github.com/BotellaA)).
|
||||
|
||||
### Other notes
|
||||
* added [patches/centos7_gcc4.8.2.diff](patches/centos7_gcc4.8.2.diff) that allows to use bitsery with gcc4.8.2 on Centos7 (thanks to [BotellaA](https://github.com/BotellaA)).
|
||||
More information on patches is [here](patches/README.md).
|
||||
* added documentation on how [extensions](doc/design/extensions.md) work.
|
||||
|
||||
# [5.0.1](https://github.com/fraillt/bitsery/compare/v5.0.0...v5.0.1) (2019-08-21)
|
||||
|
||||
### Bug fixes
|
||||
* fixed polymorphic handler deleter in `PolymorphicContext`, now it is captured by value and doesn't depend on lifetime of `PolymorphicContext` instance.
|
||||
* fixed compilation errors in `ext/utils/pointer_utils.h` on macOS.
|
||||
* reduced warnings on Visual Studio (thanks to [BotellaA](https://github.com/BotellaA))
|
||||
|
||||
# [5.0.0](https://github.com/fraillt/bitsery/compare/v4.6.1...v5.0.0) (2019-07-09)
|
||||
|
||||
This version reduces library complexity by removing redundant features and changing existing ones.
|
||||
Additionally, it provides more customization options for serialization/deserialization flow.
|
||||
|
||||
### Features
|
||||
|
||||
* added config options to enable/disable checking input adapter and data errors: **CheckAdapterErrors** and **CheckDataErrors**.
|
||||
If you can trust your data this will improve deserialization performance. Error checks are enabled by default.
|
||||
* all memory allocation in the library can be customized using memory resource (similar to c++ 17 memory resource)
|
||||
* contexts that internally requires to allocate memory are: *PointerLinkingContext, PolymorphicContext, and InheritanceContext*.
|
||||
* extensions, for pointers like types, that allocate memory are: *PointerOwner* and *StdSharedPtr*.
|
||||
More information about pointer extension can be found [here](doc/design/pointers.md).
|
||||
|
||||
### Breaking changes
|
||||
|
||||
* improved design for serializer/deserializer *context*. It was hard to understand and easy to misuse, so several changes were made.
|
||||
* removed *internal* context from config, because it doesn't actually solve any problems, only allows doing the same thing in multiple ways.
|
||||
* removed `T* context()`. This allowed to get a context that is `std::tuple<...>`, but you can do the same with other methods, by wrapping in an outer tuple, e.g. `std::tuple<std::tuple<...>>`.
|
||||
* if a context is defined, in serializer/deserializer, it is passed (and stored) by reference as a first argument (instead of pointer). Other parameters are forwarded to a input/output adapter.
|
||||
* changed signature `T* context<T>` to `T& context<T>`, this will either return context or doesn't compile, previously it could also return nullptr.
|
||||
* `context<T>` and `contextOrNull<T>` now also check if a type is convertible, so it can work with base classes
|
||||
(e.g. you can require a base class of context in extension, but provided child implementation instead). This allows to further customise extension by providing different context implementations.
|
||||
* reworked *BufferedSessions*. It was the only feature that was in a bitsery core and required allocations.
|
||||
It was removed and instead was added ability to change read/write position directly for buffered adapters.
|
||||
This is also a more flexible solution, and can be used to implement solutions that allow deserialization [in-place](https://github.com/fraillt/bitsery/issues/19) like *flatbuffers* does.
|
||||
* changed the signature to all serialization functions that accept lambdas, instead of accepting `(T& )`, now accept `(S& ,T& )`.
|
||||
This allows much easier serialization customization, because no additional state is required in a functor, and these methods can now accept function pointers or stateless lambdas.
|
||||
* removed various functions/classes that became redundant:
|
||||
* *AdapterWriter/Reader* classes - their functionality is moved to *adapters*.
|
||||
* *AdapterAccess* class - now serializer/deserializer expose adapter directly via `adapter()` method.
|
||||
Additionally adapter can be moved out if serializer/deserializer is rvalue .e.g `auto adapter = std::move(ser).adapter();`.
|
||||
* removed *Writer/Reader* parameters from extensions *serialize/deserialize* methods - because serializer/deserializer now expose *adapter* directly.
|
||||
* *archive* from serializer/deserializer - because `operator()` do the same thing, but it is more terse, and is compatible with `cereal` library.
|
||||
* *align* function from serializer/deserializer - it can now be called directly on input/output adapter.
|
||||
* *UnsafeInputBufferAdapter* - instead config option is provided to disable adapter read errors (*CheckAdapterErrors*).
|
||||
* *isValidState* from stream output adapter - it didn't provide any additional information that couldn't be queried directly on stream object.
|
||||
* *registerBasesList* from *PolymorphicContext* - it was previously deprecated.
|
||||
* renamed various classes/functions and files:
|
||||
* *flexible* to **brief_syntax** - this is a big change and might affect a lot of code, but it was necessary, because this name was contradicting to what it actually was trying to achieve.
|
||||
Migration should not be very hard, just global replace `flexible` to `brief_syntax` and it should work:).
|
||||
* *BasicSerializer/BasicDeserializer* to **Serializer/Deserializer** - previously they were aliases, but after removing adapter writer/reader,
|
||||
serializer/deserializer signature has changed to `Serializer/Deserializer<Adapter, Context=void>` and no longer need any alias.
|
||||
* *setError* to **error** for input adapters.
|
||||
* *NetworkEndianness* to **Endianness** in config.
|
||||
* *MeasureSize* adapter moved to separate file `/adapter/measure_size.h`
|
||||
|
||||
### Improvements
|
||||
|
||||
* added `quickSerialization/Deserialization` overloads that can accept context as first parameter.
|
||||
* added convenience classes (functors) **FtorExtValue, FtorExtObject**, in places where you need to provide (des)serialize function/lambda that uses extension.
|
||||
e.g. instead of writing `s.container(obj, [](S& s, MyData& data) {s.ext(data, MyExtension{});});` you can write `s.container(obj, FtorExtObject<MyExtension>{});`
|
||||
* added more tests for adapters.
|
||||
* input adapters now save first error that occured, this allows better diagnostic for what actually happened.
|
||||
|
||||
### Bug fixes
|
||||
* fixed `enabledBitPacking` in serializer/deserializer where writer/reader and internal context states were not restored properly after bit packing is complete.
|
||||
|
||||
# [4.6.1](https://github.com/fraillt/bitsery/compare/v4.6.0...v4.6.1) (2019-06-27)
|
||||
|
||||
### Features
|
||||
* flexible syntax now also supports `cereal` like serialization interface (thanks to [Luli2020](https://github.com/Luli2020))
|
||||
|
||||
### Bug fixes
|
||||
* when using `enableBitPacking`, internal context (i.e. context<T>()) in serializer/deserializer was not copied to/from new bitpacking enabled instance.
|
||||
|
||||
# [4.6.0](https://github.com/fraillt/bitsery/compare/v4.5.1...v4.6.0) (2019-03-12)
|
||||
|
||||
### Features
|
||||
@@ -259,9 +366,6 @@ Be careful when using deserializing untrusted data and make sure to enforce fund
|
||||
* 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.
|
||||
@@ -269,8 +373,9 @@ Be careful when using deserializing untrusted data and make sure to enforce fund
|
||||
* 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*)
|
||||
|
||||
### Bug fixes
|
||||
* *align* method fixed in *BufferReader*
|
||||
|
||||
<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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
project(bitsery
|
||||
LANGUAGES CXX
|
||||
VERSION 4.6.0)
|
||||
VERSION 5.1.0)
|
||||
|
||||
#======== build options ===================================
|
||||
option(BITSERY_BUILD_EXAMPLES "Build examples" OFF)
|
||||
|
||||
49
README.md
49
README.md
@@ -15,31 +15,32 @@ All cross-platform requirements are enforced at compile time, so serialized data
|
||||
* Cross-platform compatible.
|
||||
* Optimized for speed and space.
|
||||
* No code generation required: no IDL or metadata, just use your types directly.
|
||||
* Runtime error checking on deserialization.
|
||||
* Configurable runtime error checking on deserialization.
|
||||
* Can read/write from any source: stream (file, network stream. etc... ), or buffer (vector, c-array, etc...).
|
||||
* Don't pay for what you don't use! - customize your serialization via **extensions**. Some notable *extensions* allow:
|
||||
* forward/backward compatibility for your types.
|
||||
* smart and raw pointers with customizable runtime polymorphism support.
|
||||
* fine-grained bit-level serialization control.
|
||||
* forward/backward compatibility for your types.
|
||||
* smart and raw pointers with allocators support and customizable runtime polymorphism.
|
||||
* Easily extendable for any type.
|
||||
* Allows flexible or/and verbose syntax for better serialization control.
|
||||
* Allows brief (similar to [cereal](https://uscilab.github.io/cereal/)) or/and verbose syntax for better serialization control.
|
||||
* Configurable endianness support.
|
||||
* No macros.
|
||||
|
||||
## Why to use bitsery
|
||||
## Why use bitsery
|
||||
|
||||
Look at the numbers and features list, and decide yourself.
|
||||
|
||||
| | binary size | data size | serialize | deserialize |
|
||||
|------------------------------|-------------|-----------|-------------|-------------|
|
||||
| **test_bitsery** | 64704 B | **7565 B**| **1229 ms** | **1086 ms** |
|
||||
| **test_bitsery_compression** | 64880 B | **4784 B**| **1641 ms** | **2462 ms** |
|
||||
| test_yas | 63864 B | 11311 B | 1616 ms | 1712 ms |
|
||||
| test_yas_compression | 72688 B | 8523 B | 2387 ms | 2890 ms |
|
||||
| test_cereal | 74848 B | 11261 B | 6708 ms | 6799 ms |
|
||||
| test_flatbuffers | 67032 B | 16100 B | 8793 ms | 3028 ms |
|
||||
| library | data size | serialize | deserialize |
|
||||
| ---------------- | --------- | --------- | ----------- |
|
||||
| bitsery | 6913B | 959ms | 927ms |
|
||||
| bitsery_compress | 4213B | 1282ms | 1115ms |
|
||||
| boost | 11037B | 9826ms | 8313ms |
|
||||
| cereal | 10413B | 6324ms | 5698ms |
|
||||
| flatbuffers | 14924B | 5129ms | 2142ms |
|
||||
| protobuf | 10018B | 11966ms | 13919ms |
|
||||
| yas | 10463B | 1908ms | 1217ms |
|
||||
|
||||
*benchmarked on Ubuntu with GCC 7.1.0, more details can be found [here](https://github.com/fraillt/cpp_serializers_benchmark.git)*
|
||||
*benchmarked on Ubuntu with GCC 8.3.0, more details can be found [here](https://github.com/fraillt/cpp_serializers_benchmark.git)*
|
||||
|
||||
If still not convinced read more in library [motivation](doc/design/README.md) section.
|
||||
|
||||
@@ -61,13 +62,11 @@ void serialize(S& s, MyStruct& o) {
|
||||
s.value4b(o.i);
|
||||
s.value2b(o.e);
|
||||
s.container4b(o.fs, 10);
|
||||
};
|
||||
|
||||
using namespace bitsery;
|
||||
}
|
||||
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
MyStruct data{8941, MyEnum::V2, {15.0f, -8.5f, 0.045f}};
|
||||
@@ -75,11 +74,10 @@ int main() {
|
||||
|
||||
Buffer buffer;
|
||||
|
||||
auto writtenSize = quickSerialization<OutputAdapter>(buffer, data);
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
|
||||
auto state = quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data.fs == res.fs && data.i == res.i && data.e == res.e);
|
||||
}
|
||||
```
|
||||
@@ -102,9 +100,12 @@ Works with C++11 compiler, no additional dependencies, include `<bitsery/bitsery
|
||||
|
||||
This library was tested on
|
||||
* Windows: Visual Studio 2015, MinGW (GCC 5.2)
|
||||
* Linux: GCC 5.4, GCC 6.2, Clang 3.9
|
||||
* Linux: GCC 5.4, Clang 3.9
|
||||
* OS X Mavericks: AppleClang 8
|
||||
|
||||
There is a patch that allows using bitsery with non-fully compatible C++11 compilers.
|
||||
* CentOS 7 with gcc 4.8.2.
|
||||
|
||||
## License
|
||||
|
||||
**bitsery** is licensed under the [MIT license](LICENSE).
|
||||
|
||||
@@ -4,24 +4,24 @@ Once you're familiar with the library consider the following reference material.
|
||||
Library design:
|
||||
* `fundamental types`
|
||||
* `valueNb instead of value`
|
||||
* `flexible syntax`
|
||||
* `brief syntax`
|
||||
* `serializer/deserializer functions overloads`
|
||||
* `extending library functionality`
|
||||
* [extending library functionality](design/extensions.md)
|
||||
* `errors handling`
|
||||
* `forward/backward compatibility via Growable extension`
|
||||
* `pointers`
|
||||
* [pointers](design/pointers.md)
|
||||
* `inheritance`
|
||||
* `polymorphism`
|
||||
|
||||
|
||||
Core Serializer/Deserializer functions (alphabetical order):
|
||||
* `align` (1.0.0)
|
||||
* `operator()` (4.6.1) (when brief syntax is enabled)
|
||||
* `adapter` (5.0.0)
|
||||
* `boolValue` (4.0.0)
|
||||
* `container` (1.0.0)
|
||||
* `ext` (2.0.0)
|
||||
* `context` (3.0.0)
|
||||
* `context<T>` (4.1.0)
|
||||
* `contextOrNull<T>` (4.2.0)
|
||||
* `enableBitPacking` (4.0.0)
|
||||
* `ext` (2.0.0)
|
||||
* `object` (1.0.0)
|
||||
* `text` (1.0.0)
|
||||
* `value` (1.0.0)
|
||||
@@ -48,28 +48,26 @@ Serializer/Deserializer extensions via `ext` method (alphabetical order):
|
||||
* `ValueRange` (3.0.0)
|
||||
* `VirtualBaseClass` (4.2.0)
|
||||
|
||||
AdapterWriter/Reader functions:
|
||||
* `writeBits/readBits`
|
||||
* `writeBytes/readBytes`
|
||||
* `writeBuffer/readBuffer`
|
||||
* `align`
|
||||
* `beginSession/endSession`
|
||||
* `flush (writer only)`
|
||||
* `writtenBytesCount (writer only)`
|
||||
* `setError (reader only)`
|
||||
* `getError (reader only)`
|
||||
* `isCompletedSuccessfully (reader only)`
|
||||
|
||||
Input adapters (buffer and stream) functions:
|
||||
* `read`
|
||||
* `error`
|
||||
* `setError`
|
||||
* `align`
|
||||
* `readBits`
|
||||
* `readBytes`
|
||||
* `readBuffer`
|
||||
* `currentReadPos (get/set)` (buffer adapter only)
|
||||
* `currentReadEndPos (get/set)` (buffer adapter only)
|
||||
* `error (get/set)`
|
||||
* `isCompletedSuccessfully`
|
||||
|
||||
Output adapters (buffer and stream) functions:
|
||||
* `write`
|
||||
* `align`
|
||||
* `writeBits`
|
||||
* `writeBytes`
|
||||
* `writeBuffer`
|
||||
* `flush`
|
||||
* `writtenBytesCount` (buffer adapter only)
|
||||
* `currentyWritePos (get/set)` (buffer adapter only) gets/sets write position in buffer, it can jump past the buffer end, in this case buffer will be resized.
|
||||
This function doesn't write any bytes.
|
||||
* `writtenBytesCount` (buffer adapter only) this doesn't necessary mean how many bytes are written, but rather how many bytes in the buffer was "affected" during serialization.
|
||||
E.g. if `currentyWritePos` (set) jumps from 0 to 100, and then 4 bytes are written, `writtenBytesCount` return 104, it also returns 104 if you jump in somewhere in the middle.
|
||||
|
||||
|
||||
Tips and tricks:
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
## Motivation
|
||||
|
||||
Inspiration to create **bitsery** came mainly because there aren't any good alternatives for C++.
|
||||
Inspiration to create **bitsery** came mainly because there aren't any good alternatives for C++ that meets my requirements.
|
||||
|
||||
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.
|
||||
I wanted serializer that is easy to use as [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 safe to use with untrusted (malicious) data, and most importantly is fast and has a 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*.
|
||||
Furthermore, I wanted full serialization control and the ability to work on a bit level, so I can further reduce data size. For example, serializing container of [quaternions](https://en.wikipedia.org/wiki/Quaternion) I can reduce the size by a 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.
|
||||
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 add 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.
|
||||
Often 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.
|
||||
|
||||
@@ -34,17 +34,17 @@ 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 endianness transformation if necessary.
|
||||
* **Flexible syntax.** If you don't like like writing code with explicitly specifying underlying type size, like *container2b* or *value8b*, you can use flexible syntax.
|
||||
Just include <bitsery/flexible.h> and can write like in [cereal](http://uscilab.github.io/cereal/).
|
||||
* **Brief syntax.** If you don't like like writing code with explicitly specifying underlying type size, like *container2b* or *value8b*, you can use brief syntax.
|
||||
Just include <bitsery/brief_syntax.h> and can write like in [cereal](http://uscilab.github.io/cereal/).
|
||||
But do it on your own risk, and static assert using *assertFundamentalTypeSizes* function if you're planing to use it across multiple platforms.
|
||||
* **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.
|
||||
* **Optimized for speed and space.** library itself doesn't do any allocations so data writing/reading is fast as memcpy to/from your buffer.
|
||||
It also doesn't serialize any type information, all information needed is written 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 crash you.
|
||||
* **Supports forward/backward compatibility for your types** library has optional forward/backward compatibility for types implemented in *AdapterReader/Writer* by allowing to have inner data sessions inside buffer.
|
||||
This is the only functionality that requires dynamic memory allocation.
|
||||
*Growable* 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.
|
||||
* **Configurable 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 crash you. BUT, if you trust your data then you can simply disable error checks for better performance.
|
||||
* **Supports forward/backward compatibility for your types** library provides access for buffer input/output adapters to directly change read/write position on the buffer.
|
||||
*Growable* extension use this capability to allow forward/backward compatibility.
|
||||
You can implement your own extensions to do all sorts of other things, like in-place deserialization..
|
||||
* **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* and *save* functions, but Bitsery explicitly doesn't support it, to avoid any serialization deserialization divergence, 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.
|
||||
@@ -67,3 +67,5 @@ To use same container for buffer writing/reading add specialization to *BufferAd
|
||||
You want to customize serialization flow - use extensions, only two methods to define, and *ExtensionTraits* to further customize usage.
|
||||
* **Configurable endianness 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 :)
|
||||
|
||||
It should also be noted, that extensions that allocate memory (e.g. pointer serialization/deserialization) allow to provide memory resource (similar to C++17 `std::pmr::memory_resource`).
|
||||
@@ -0,0 +1,51 @@
|
||||
Extensions are at the heart of bitsery. They allow implementing all sorts of things, that requires customizing serialization and deserialization flows separately.
|
||||
Bitsery already provides a lot of useful extensions, which can be found [here](../../include/bitsery/ext).
|
||||
|
||||
Let's see what are the core components of an extension:
|
||||
|
||||
1. Extension class itself, which implements templated `serialize` and `deserialize` methods. These functions provide similar capabilities to `save` and `load` functions in other frameworks e.g. [cereal](https://uscilab.github.io/cereal/) or [boost](https://www.boost.org/doc/libs/1_71_0/libs/serialization/doc/index.html), but are more powerful because extension itself can store extension related data as well, that can be used for additional functionality.
|
||||
```cpp
|
||||
class MyExtension {
|
||||
public:
|
||||
template<typename Ser, typename T, typename Fnc>
|
||||
void serialize(Ser& ser, const T& obj, Fnc&& fnc) const {
|
||||
...
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserialize(Des& des, T& obj, Fnc&& fnc) const {
|
||||
...
|
||||
}
|
||||
};
|
||||
```
|
||||
2. `ExtensionTraits` specialization for an extension, which specifies how it should be used:
|
||||
```cpp
|
||||
namespace bitsery {
|
||||
namespace traits {
|
||||
template<typename T>
|
||||
struct ExtensionTraits<ext::MyExtension, T> {
|
||||
using TValue = ...;
|
||||
static constexpr bool SupportValueOverload = ...;
|
||||
static constexpr bool SupportObjectOverload = ...;
|
||||
static constexpr bool SupportLambdaOverload = ...;
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now, that we know the core components of an extension, let's see how everything fits together.
|
||||
|
||||
An Extension can be called in 3 different ways, and `Support...Overload` methods basically define, what call syntax can be used with a particular extension.
|
||||
* `SupportValueOverload` - allows to call extension by providing the size of the value type, the same as `valueNb` function. e.g. `s.ext4b(value, MyExtension{})`.
|
||||
* `SupportObjectOverload` - allows to call extension the same as simple `object` function. e.g. `s.ext(value, MyExtension{})`.
|
||||
* `SupportLambdaOverload` - allows to call extension by providing a custom lambda. e.g. `s.ext(value, MyExtension{}, [](...) { ... })`.
|
||||
|
||||
You might wonder, how there are 3 ways to call an extension, but only one signature for `serialize` and `deserialize` functions?
|
||||
|
||||
This is where a `TValue` from `ExtensionTraits` and the third parameter `Fnc` in `serialize` and `deserialize` comes in.
|
||||
|
||||
In case of lambda overload is called, the lambda is passed straight to the serialize/deserialize function as the third parameter. In theory `SupportLambdaOverload` can be any object, not necessary a callable object.
|
||||
When value overload is used, then lambda is constructed by bitsery like this `[](Serializer& s, VType &v) { s.value<VSIZE>(v); }`, where `VType` is equal to `TValue` from `ExtensionTraits`.
|
||||
Similarly, when object overload is used `[](Serializer& s, VType &v) { s.object(v); }` lambda is constructed.
|
||||
|
||||
When there is no direct mapping from object type to its underlying value type, you can disable lambda generation for value and object overload, by setting `TValue=void`, in this case, a "dummy" lambda will be provided.
|
||||
39
doc/design/pointers.md
Normal file
39
doc/design/pointers.md
Normal file
@@ -0,0 +1,39 @@
|
||||
*document in progress*
|
||||
|
||||
## Extensions
|
||||
Raw pointers are managed by three extensions:
|
||||
* **PointerOwner** - manages a lifetime of the pointer, creates or destroys if required.
|
||||
* **PointerObserver** - doesn't own pointer so it doesn't create or destroy anything.
|
||||
* **ReferencedByPointer** - when a non-owning pointer (*PointerObserver*) points to reference type, this extension marks this object as a valid target for PointerObserver.
|
||||
|
||||
"Smart" pointers, from c++ standard lib (std), are managed by:
|
||||
* **StdSmartPtr** - can accept unique_ptr, shared_ptr and weak_ptr
|
||||
|
||||
## Implementation details
|
||||
|
||||
All aforementioned extensions derive from single base class `PointerObjectExtensionBase`.
|
||||
This base class accepts three template parameters that customise its behaviour.
|
||||
* `TPtrManager\<T\>` - describes how particular pointer type should be handled:
|
||||
pointer creation/destruction describes a type of pointer (e.g. owning, shared, observer), and how to actually get value for pointer object.
|
||||
This is the place to start if you want to implement pointer support for your custom type. \<T\> is type of pointer object, e.g. `std::unique_ptr<MyType>`, `MyType*`
|
||||
* `TPolymorphicContext\<RTTI\>` - provides the functionality to register class hierarchies for your types with serializer, and deserializer, in order to polymorphically serialize/deserialize objects.
|
||||
\<RTTI\> template parameter provides runtime information about a type that is used to construct class hierarchies and save them to read/write them to buffer.
|
||||
* `RTTI` - this template parameter provides information if a type is polymorphic, and if it is, then it is used in `TPolymorphicContext\<RTTI\>`.
|
||||
Some pointer managers, like `PointerObserver` and `ReferencedByPointer` never requires polymorphic context. In these cases, you need to provide RTTI that will return `isPolymorphic`=false for all types.
|
||||
By default all pointers extensions use `StandardRTTI` from `/ext/utils/rtti_utils.h` that internally uses `typeid` and `dynamic_cast`.
|
||||
If your environment doesn't allow RTTI, you can provide your own RTTI for your types.
|
||||
|
||||
## Allocation and memory resources
|
||||
|
||||
Allocation is implemented using memory resources (similar to `std::pmr::memory_resource` from c++17),
|
||||
and it is called `MemResourceBase` from "ext/utils/memory_allocator.h". The core difference between standard one
|
||||
is that it has additional `size_t typeId` field for `allocate` and `deallocate` methods, and this typeId is returned from `RTTI`.
|
||||
There are few options to customise pointer allocation for your pointers by using `MemResourceBase`:
|
||||
* invoke `setMemResource` in `PointerLinkingContext`.
|
||||
* pass memory resource to pointer manager constructor, along with boolean parameter that specifies if this memory resource should propagate when deserializing child objects.
|
||||
If no memory resource is provided, then `MemResourceNewDelete` is used, which calls `::operator new(bytes)` and `::operator delete(ptr)`.
|
||||
|
||||
**IMPORTANT**: there are few things that you should know to correctly use custom allocations with `StdSmartPtr`:
|
||||
* Memory resource must live as long as the last object, that was allocated with it (this is required by std::shared_ptr, custom deleter is provided, that will be able to deallocate correctly when a shared pointer is destroyed).
|
||||
* std::unique_ptr is allocated and deallocated using provided memory resource.
|
||||
If you create unique pointers your self, make sure that it uses the same memory resource, because bitsery will not call `.reset` method on pointer, and instead `.release()` it and deallocate manually.
|
||||
@@ -1,8 +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.
|
||||
* [Getting started](hello_world.md) with bitsery, and serialize/deserialize your first object.
|
||||
* [Extend to your needs](first_extension.md) by enabling serialization/deserialization depending on version number.
|
||||
* `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.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
*document in progress*
|
||||
* explain why *value* and *object* is fundamental functions.
|
||||
* write about *Growable* extension
|
||||
67
doc/tutorial/first_extension.md
Normal file
67
doc/tutorial/first_extension.md
Normal file
@@ -0,0 +1,67 @@
|
||||
... TODO explain step-by-step what we need and how to get there
|
||||
|
||||
Instead I immediately provide implementation for an extension.
|
||||
|
||||
```cpp
|
||||
#include "../details/adapter_common.h"
|
||||
#include "../traits/core/traits.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
namespace ext {
|
||||
|
||||
template<size_t VERSION>
|
||||
class Version {
|
||||
public:
|
||||
|
||||
template<typename Ser, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, const T &v, Fnc &&fnc) const {
|
||||
details::writeSize(ser.adapter(), VERSION);
|
||||
fnc(ser, const_cast<T&>(v), VERSION);
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserialize(Des &des, T &v, Fnc &&fnc) const {
|
||||
size_t version{};
|
||||
details::readSize(des.adapter(), version, 0u, std::false_type{});
|
||||
fnc(des, v, version);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
namespace traits {
|
||||
template<typename T, size_t V>
|
||||
struct ExtensionTraits<ext::Version<V>, T> {
|
||||
using TValue = T;
|
||||
static constexpr bool SupportValueOverload = false;
|
||||
static constexpr bool SupportObjectOverload = false;
|
||||
static constexpr bool SupportLambdaOverload = true;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
Adding such extension to the bitsery itself is impractical because it is very easy to implement, but at the same time it has a lot of customization options that actual user might require e.g.:
|
||||
* how do you want to handle reading/writing version number? (in this case use compact representation as size, but do not check for errors if version number is too large)
|
||||
* maybe you want to be able to set ReaderError, when version is larger than deserialization implementation handles. (we simply ignore this case and later we'll probably get reading error later anyway)
|
||||
* or maybe you want to wrap object in `Growable` extension? so that you could ignore unknown fields in newer version of object.
|
||||
|
||||
Example of how to use this provided implementation:
|
||||
```cpp
|
||||
|
||||
struct TypeV2 {
|
||||
uint16_t x{};
|
||||
uint16_t y{};
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& ser, TypeV2& obj) {
|
||||
ser.ext(obj, bitsery::ext::Version<2u>{}, [](S& s, TypeV2&o, size_t version) {
|
||||
s.value2b(o.x);
|
||||
if (version == 2u) {
|
||||
s.value2b(o.y);
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -18,11 +18,9 @@ There's nothing to build or make - **bitsery** is header only.
|
||||
#include <bitsery/traits/vector.h>
|
||||
#include <bitsery/traits/string.h>
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
```
|
||||
|
||||
@@ -58,11 +56,11 @@ void serialize(S& s, MyStruct& o) {
|
||||
|
||||
**bitsery** also allows to define serialize function in side your class, and can also serialize private class members, just make *friend bitsery::Access;*
|
||||
|
||||
**bitsery** supports two ways how to describe your serialization flow: *verbose syntax* (as in example) or *flexible syntax*, similar to *cereal* library, just include `<bitsery/flexible.h>` to use it.
|
||||
**bitsery** supports two ways how to describe your serialization flow: *verbose syntax* (as in example) or *brief syntax*, similar to *cereal* library, just include `<bitsery/brief_syntax.h>` to use it.
|
||||
|
||||
This example we choosed probably unfamiliar verbose syntax, so lets explain core functionality that you'll use all the time:
|
||||
* **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.text1b(o.str);** serialize text (null-terminated) of char type, if you use *wchar* then you would write *text2b* or *text4b* depending on the OS platform.
|
||||
* **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.
|
||||
@@ -76,9 +74,8 @@ Create buffer and use helper functions for serialization and deserialization.
|
||||
```cpp
|
||||
Buffer buffer;
|
||||
|
||||
auto writtenSize = quickSerialization<OutputAdapter>(buffer, data);
|
||||
|
||||
auto state = quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
auto writtenSize = bitsery::quickSerialization(OutputAdapter{buffer}, data);
|
||||
auto state = bitsery::quickDeserialization(InputAdapter{buffer.begin(), writtenSize}, res);
|
||||
```
|
||||
|
||||
These helper functions use default configuration *bitsery::DefaultConfig*
|
||||
@@ -94,36 +91,33 @@ deserialization state has two properties, error code and bool that indicates if
|
||||
#include <bitsery/traits/vector.h>
|
||||
#include <bitsery/traits/string.h>
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
struct MyStruct {
|
||||
uint32_t i;
|
||||
char str[6];
|
||||
std::vector<float> fs;
|
||||
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);
|
||||
};
|
||||
s.value4b(o.i);
|
||||
s.text1b(o.str);
|
||||
s.container4b(o.fs, 100);
|
||||
}
|
||||
|
||||
int main() {
|
||||
MyStruct data{8941, "hello", {15.0f, -8.5f, 0.045f}};
|
||||
MyStruct res{};
|
||||
MyStruct data{8941, "hello", {15.0f, -8.5f, 0.045f}};
|
||||
MyStruct res{};
|
||||
|
||||
Buffer buffer;
|
||||
auto writtenSize = quickSerialization<OutputAdapter>(buffer, data);
|
||||
Buffer buffer;
|
||||
auto writtenSize = bitsery::quickSerialization(OutputAdapter{buffer}, data);
|
||||
auto state = bitsery::quickDeserialization(InputAdapter{buffer.begin(), writtenSize}, res);
|
||||
|
||||
auto state = quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
assert(data.fs == res.fs && data.i == res.i && std::strcmp(data.str, res.str) == 0);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data.fs == res.fs && data.i == res.i && std::strcmp(data.str, res.str) == 0);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -29,11 +29,6 @@ endif()
|
||||
|
||||
file(GLOB ExampleFiles ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
|
||||
|
||||
if (WIN32)
|
||||
message(WARNING "Removing example `flexible_assert_linux_x64` for Windows")
|
||||
list(REMOVE_ITEM ExampleFiles ${CMAKE_CURRENT_SOURCE_DIR}/flexible_assert_linux_x64.cpp)
|
||||
endif()
|
||||
|
||||
foreach(ExampleFile ${ExampleFiles})
|
||||
get_filename_component(ExampleName ${ExampleFile} NAME_WE)
|
||||
add_executable(bitsery.example.${ExampleName} ${ExampleFile})
|
||||
|
||||
@@ -22,12 +22,10 @@ void serialize(S& s, MyStruct& o) {
|
||||
s.container4b(o.fs, 10);//resizable containers also requires maxSize, to make it safe from buffer-overflow attacks
|
||||
}
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
@@ -39,12 +37,12 @@ int main() {
|
||||
//use quick serialization function,
|
||||
//it will use default configuration to setup all the nesessary steps
|
||||
//and serialize data to container
|
||||
auto writtenSize = quickSerialization<OutputAdapter>(buffer, data);
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
|
||||
//same as serialization, but returns deserialization state as a pair
|
||||
//first = error code, second = is buffer was successfully read from begin to the end.
|
||||
auto state = quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data.fs == res.fs && data.i == res.i && data.e == res.e);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace MyTypes {
|
||||
//compress path in a range of -1.0 .. 1.0 with 0.01 precision
|
||||
//enableBitPacking creates separate serializer/deserializer object, that contains bit packing operations
|
||||
s.enableBitPacking([&o](typename S::BPEnabledType& sbp) {
|
||||
sbp.container(o.path, 1000, [&sbp](Vec3& vec3) {
|
||||
sbp.container(o.path, 1000, [](typename S::BPEnabledType& sbp, Vec3& vec3) {
|
||||
constexpr bitsery::ext::ValueRange<float> range{-1.0f,1.0f, 0.01f};
|
||||
sbp.ext(vec3.x, range);
|
||||
sbp.ext(vec3.y, range);
|
||||
@@ -41,12 +41,10 @@ namespace MyTypes {
|
||||
}
|
||||
}
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
//use fixed-size buffer
|
||||
using Buffer = std::array<uint8_t, 10000>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
@@ -55,10 +53,10 @@ int main() {
|
||||
|
||||
//create buffer to store data to
|
||||
Buffer buffer{};
|
||||
auto writtenSize = quickSerialization<OutputAdapter>(buffer, data);
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
|
||||
MyTypes::Monster res{};
|
||||
auto state = quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
//include flexible header, to use flexible syntax
|
||||
#include <bitsery/flexible.h>
|
||||
//to use brief syntax always include this header
|
||||
#include <bitsery/brief_syntax.h>
|
||||
//we also need additional traits to work with container types,
|
||||
//instead of including <bitsery/traits/vector.h> for vector traits, now we also need traits to work with flexible types.
|
||||
//so include everything from <bitsery/flexible/...> instead of <bitsery/traits/...>
|
||||
//instead of including <bitsery/traits/vector.h> for vector traits, now we also need traits to work with brief_syntax types.
|
||||
//so include everything from <bitsery/brief_syntax/...> instead of <bitsery/traits/...>
|
||||
//otherwise we'll get static assert error, saying to define serialize function.
|
||||
#include <bitsery/flexible/vector.h>
|
||||
#include <bitsery/brief_syntax/vector.h>
|
||||
|
||||
enum class MyEnum:uint16_t { V1,V2,V3 };
|
||||
struct MyStruct {
|
||||
@@ -17,19 +17,16 @@ struct MyStruct {
|
||||
//define serialize function as usual
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
//now we can use flexible syntax with
|
||||
s.archive(i, e, fs);
|
||||
//now we can use brief syntax with
|
||||
s(i, e, fs);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
@@ -38,10 +35,10 @@ int main() {
|
||||
|
||||
//serialization, deserialization flow is unchanged as in basic usage
|
||||
Buffer buffer;
|
||||
auto writtenSize = quickSerialization<OutputAdapter>(buffer, data);
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
|
||||
auto state = quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data.fs == res.fs && data.i == res.i && data.e == res.e);
|
||||
}
|
||||
@@ -29,27 +29,24 @@ void serialize(S& s, MyStruct& o) {
|
||||
using MyTuple = std::tuple<float, MyStruct>;
|
||||
using MyVariant = std::variant<int64_t, MyTuple, MyStruct>;
|
||||
|
||||
// for convenience
|
||||
using namespace bitsery;
|
||||
|
||||
// define default serialize function for MyVariant, so that we could use quickSerialization/Deserialization functions
|
||||
template<typename S>
|
||||
void serialize(S& s, MyVariant& o) {
|
||||
// in order to serialize a variant, it needs to know how to do it for all types
|
||||
// we can do this simply by providing any callable object, that accepts serializer and type as arguments
|
||||
s.ext(o, ext::StdVariant{
|
||||
s.ext(o, bitsery::ext::StdVariant{
|
||||
// specify how to serialize tuple by creating a lambda
|
||||
[](S& s, MyTuple& o) {
|
||||
// StdTuple is used exactly the same as StdVariant
|
||||
s.ext(o, ext::StdTuple{
|
||||
s.ext(o, bitsery::ext::StdTuple{
|
||||
// this is convenient callable object to specify integral value size
|
||||
// it is different equivalent to lambda [](auto& s, float&o) { s.value4b(o);}
|
||||
ext::OverloadValue<float, 4>{},
|
||||
bitsery::ext::OverloadValue<float, 4>{},
|
||||
// it is not required to provide MyStruct overload, because it we have defined 'serialize' function for it
|
||||
});
|
||||
},
|
||||
// this might also be useful if you want to overload using extension
|
||||
ext::OverloadExtValue<int64_t, 8, ext::CompactValue>{},
|
||||
bitsery::ext::OverloadExtValue<int64_t, 8, bitsery::ext::CompactValue>{},
|
||||
// you can even go further and instead of writing lambda for MyTuple you can as well compose the same functionality
|
||||
// with OverloadExtObject, like this:
|
||||
// (comment out MyTuple lambda, and uncomment this)
|
||||
@@ -58,8 +55,8 @@ void serialize(S& s, MyVariant& o) {
|
||||
// we can also override default 'serialize' function by creating an overloading for that type
|
||||
[](S& s, MyStruct& o) {
|
||||
s.value4b(o.f);
|
||||
s.container(o.v, 1000, [&s](int32_t& v) {
|
||||
s.ext4b(v, ext::CompactValue{});
|
||||
s.container(o.v, 1000, [](S& s, int32_t& v) {
|
||||
s.ext4b(v, bitsery::ext::CompactValue{});
|
||||
});
|
||||
},
|
||||
// NOTE.
|
||||
@@ -78,8 +75,8 @@ void serialize(S& s, MyVariant& o) {
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
|
||||
@@ -93,13 +90,13 @@ int main() {
|
||||
//use quick serialization function,
|
||||
//it will use default configuration to setup all the nesessary steps
|
||||
//and serialize data to container
|
||||
auto writtenSize = quickSerialization<OutputAdapter>(buffer, data);
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
|
||||
//same as serialization, but returns deserialization state as a pair
|
||||
//first = error code, second = is buffer was successfully read from begin to the end.
|
||||
auto state = quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data == res);
|
||||
}
|
||||
#else
|
||||
|
||||
@@ -34,15 +34,15 @@ namespace MyTypes {
|
||||
template<typename S>
|
||||
void serialize(S& s, GameState &o) {
|
||||
//we can have multiple types in context with std::tuple
|
||||
//this cast also works if our context is the same as cast
|
||||
auto maxMonsters = s.template context<int>();
|
||||
//all data from context is always pointer
|
||||
//if data type doesn't match then it will be compile time error
|
||||
auto dmgRange = s.template context<std::pair<uint32_t, uint32_t>>();
|
||||
s.container(o.monsters, *maxMonsters, [&s, dmgRange] (Monster& m) {
|
||||
//NOTE: if context is optional then you can call contextOrNull<T>, and it will return null if T doesn't exists
|
||||
auto maxMonsters = s.template context<int>();
|
||||
auto& dmgRange = s.template context<std::pair<uint32_t, uint32_t>>();
|
||||
|
||||
s.container(o.monsters, maxMonsters, [&dmgRange] (S& s, Monster& m) {
|
||||
s.text1b(m.name, 20);
|
||||
//we know min/max damage range for monsters, so we can use this range instead of full value
|
||||
bitsery::ext::ValueRange<uint32_t> range{dmgRange->first, dmgRange->second};
|
||||
bitsery::ext::ValueRange<uint32_t> range{dmgRange.first, dmgRange.second};
|
||||
//enable bit packing
|
||||
s.enableBitPacking([&m, &range](typename S::BPEnabledType& sbp) {
|
||||
sbp.ext(m.minDamage, range);
|
||||
@@ -53,26 +53,23 @@ namespace MyTypes {
|
||||
|
||||
}
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
//use fixed-size buffer
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
//context can contain multiple types
|
||||
//it would make more sense to define separate structure for context, but for sake of this example make it more complex
|
||||
//in serialization function we can cast it like this:
|
||||
//context can contain multiple types by wrapping these types in std::tuple
|
||||
//in serialization function we can get type that we need like this:
|
||||
// s.template context<int>();
|
||||
//if we want to get whole tuple, just call s.context() without template paramter.
|
||||
//this templated version also works if our context is the same as cast:
|
||||
// struct MyContext {...};
|
||||
// ...
|
||||
// s.template context<MyContext>();
|
||||
using Context = std::tuple<int, std::pair<uint32_t, uint32_t>>;
|
||||
|
||||
//NOTE:
|
||||
// if your context has no additional usage outside of serialization flow,
|
||||
// then you can create it internally via configuration (see inheritance.cpp)
|
||||
using Context = std::tuple<int, std::pair<uint32_t, uint32_t>>;
|
||||
|
||||
//use fixed-size buffer
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
// define adapter types,
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
|
||||
@@ -92,18 +89,10 @@ int main() {
|
||||
|
||||
//create buffer to store data to
|
||||
Buffer buffer{};
|
||||
//pass game mode object to serializer as context
|
||||
BasicSerializer<AdapterWriter<OutputAdapter, bitsery::DefaultConfig>, Context> ser{buffer, &ctx};
|
||||
ser.object(data);
|
||||
|
||||
auto& w = AdapterAccess::getWriter(ser);
|
||||
w.flush();
|
||||
auto writtenSize = w.writtenBytesCount();
|
||||
auto writtenSize = bitsery::quickSerialization(ctx, OutputAdapter{buffer}, data);
|
||||
|
||||
MyTypes::GameState res{};
|
||||
BasicDeserializer <AdapterReader<InputAdapter, bitsery::DefaultConfig>, Context> des { InputAdapter{buffer.begin(), writtenSize}, &ctx};
|
||||
des.object(res);
|
||||
auto& r = AdapterAccess::getReader(des);
|
||||
auto state = bitsery::quickDeserialization(ctx, InputAdapter{buffer.begin(), writtenSize}, res);
|
||||
|
||||
assert(r.error() == ReaderError::NoError && r.isCompletedSuccessfully());
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
}
|
||||
|
||||
@@ -19,11 +19,6 @@ void serialize(S& s, MyStruct& o) {
|
||||
s.value8b(o.f);
|
||||
}
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
//some helper types
|
||||
using Stream = std::fstream;
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
MyStruct data{8941, MyEnum::V2, 0.045};
|
||||
@@ -31,17 +26,17 @@ int main() {
|
||||
|
||||
//open file stream for writing and reading
|
||||
auto fileName = "test_file.bin";
|
||||
Stream s{fileName, s.binary | s.trunc | s.out};
|
||||
std::fstream s{fileName, s.binary | s.trunc | s.out};
|
||||
if (!s.is_open()) {
|
||||
std::cout << "cannot open " << fileName << " for writing\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
//we cannot use quick serialization function, because streams cannot use writtenBytesCount method
|
||||
//for serialization we can use buffered stream adapter, it can greatly improve performance for some streams
|
||||
Serializer<OutputBufferedStreamAdapter> ser{s};
|
||||
bitsery::Serializer<bitsery::OutputBufferedStreamAdapter> ser{s};
|
||||
ser.object(data);
|
||||
//flush to writer
|
||||
AdapterAccess::getWriter(ser).flush();
|
||||
ser.adapter().flush();
|
||||
s.close();
|
||||
//reopen for reading
|
||||
|
||||
@@ -53,8 +48,8 @@ int main() {
|
||||
|
||||
//same as serialization, but returns deserialization state as a pair
|
||||
//first = error code, second = is buffer was successfully read from begin to the end.
|
||||
auto state = quickDeserialization<InputStreamAdapter>(s, res);
|
||||
auto state = bitsery::quickDeserialization<bitsery::InputStreamAdapter>(s, res);
|
||||
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data.f == res.f && data.i == res.i && data.e == res.e);
|
||||
}
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/flexible.h>
|
||||
#include <bitsery/flexible/vector.h>
|
||||
|
||||
|
||||
struct MyStruct {
|
||||
int i;
|
||||
unsigned short s;
|
||||
std::vector<long> vl;
|
||||
long long ll;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
//now we can use flexible syntax with
|
||||
//member function has same name as parameter
|
||||
s.archive(this->s, i, vl, ll);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
//this will only work on linux or mac x64
|
||||
bitsery::assertFundamentalTypeSizes<2,4,8,8>();
|
||||
//set some random data
|
||||
MyStruct data{8941, 3, {15l, -8l, 045l}, 8459845ll};
|
||||
MyStruct res{};
|
||||
|
||||
//serialization, deserialization flow is unchanged as in basic usage
|
||||
Buffer buffer;
|
||||
auto writtenSize = quickSerialization<OutputAdapter>(buffer, data);
|
||||
|
||||
auto state = quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
assert(data.vl == res.vl && data.s == res.s && data.i == res.i && data.ll == res.ll);
|
||||
}
|
||||
@@ -25,7 +25,7 @@ namespace MyTypes {
|
||||
template <typename S>
|
||||
void serialize (S& s) {
|
||||
//forward/backward compatibility for monsters
|
||||
s.ext(*this, bitsery::ext::Growable{}, [&s](Weapon& o1) {
|
||||
s.ext(*this, bitsery::ext::Growable{}, [](S& s, Weapon& o1) {
|
||||
s.text1b(o1.name, 20);
|
||||
s.value2b(o1.damage);
|
||||
});
|
||||
@@ -54,7 +54,7 @@ namespace MyTypes {
|
||||
template <typename S>
|
||||
void serialize (S& s, Monster& o) {
|
||||
//forward/backward compatibility for monsters
|
||||
s.ext(o, bitsery::ext::Growable{}, [&s](Monster& o1) {
|
||||
s.ext(o, bitsery::ext::Growable{}, [](S& s, Monster& o1) {
|
||||
s.value1b(o1.color);
|
||||
s.value2b(o1.mana);
|
||||
s.value2b(o1.hp);
|
||||
@@ -68,17 +68,10 @@ namespace MyTypes {
|
||||
}
|
||||
}
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
//use fixed-size buffer
|
||||
using Buffer = std::array<uint8_t, 10000>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
|
||||
//create configuration that enables session support, to work with "growable" extension
|
||||
struct SessionsEnabled:public DefaultConfig {
|
||||
static constexpr bool BufferSessionsEnabled = true;
|
||||
};
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
@@ -89,17 +82,11 @@ int main() {
|
||||
//create buffer to store data to
|
||||
Buffer buffer{};
|
||||
//since we're using different configuration, we cannot use quickSerialization function.
|
||||
BasicSerializer<AdapterWriter<OutputAdapter, SessionsEnabled>> ser{OutputAdapter{buffer}};
|
||||
ser.object(data);
|
||||
auto& w = AdapterAccess::getWriter(ser);
|
||||
w.flush();
|
||||
auto writtenSize = w.writtenBytesCount();
|
||||
|
||||
auto writtenSize = bitsery::quickSerialization<OutputAdapter>(buffer, data);
|
||||
|
||||
MyTypes::Monster res{};
|
||||
//deserialize
|
||||
BasicDeserializer<AdapterReader<InputAdapter, SessionsEnabled>> des{InputAdapter{buffer.begin(), writtenSize}};
|
||||
des.object(res);
|
||||
auto& r = AdapterAccess::getReader(des);
|
||||
assert(r.error() == ReaderError::NoError && r.isCompletedSuccessfully());
|
||||
auto state = bitsery::quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
// BaseClass - normal inheritance
|
||||
// VirtualBaseClass - when virtual inheritance is used
|
||||
//in order for virtual inheritance to work, InheritanceContext is required. for normal inheritance it is not required
|
||||
//it can be created either internally (via configuration) or externally (pointer to context).
|
||||
#include <bitsery/ext/inheritance.h>
|
||||
|
||||
using bitsery::ext::BaseClass;
|
||||
@@ -32,7 +31,7 @@ struct Derive1:virtual Base {// virtually inherits from base
|
||||
};
|
||||
template <typename S>
|
||||
void serialize(S& s, Derive1& o) {
|
||||
//define virtual inheritance, it will not compile if InheritanceContext is not defined in serializer/deserialzer
|
||||
//define virtual inheritance, it will not compile if InheritanceContext is not defined in serializer/deserializer
|
||||
s.ext(o, VirtualBaseClass<Base>{});
|
||||
s.value1b(o.y1);
|
||||
}
|
||||
@@ -79,20 +78,10 @@ namespace bitsery {
|
||||
struct SelectSerializeFnc<MultipleInheritance>:UseNonMemberFnc {};
|
||||
}
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
// since in this example we're using virtual inheritance we need InheritanceContext as well.
|
||||
// InheritanceContext is default constructable and has no useful information outside of serializer/deserializer
|
||||
// lets create it internally, via configuration
|
||||
struct ConfigWithContext:DefaultConfig {
|
||||
//always add internal contexts to tuple
|
||||
using InternalContext = std::tuple<ext::InheritanceContext>;
|
||||
};
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using Writer = AdapterWriter<OutputBufferAdapter<Buffer>, ConfigWithContext>;
|
||||
using Reader = AdapterReader<InputBufferAdapter<Buffer>, ConfigWithContext>;
|
||||
using Writer = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using Reader = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
|
||||
@@ -103,15 +92,13 @@ int main() {
|
||||
|
||||
Buffer buf{};
|
||||
|
||||
BasicSerializer<Writer> ser{OutputBufferAdapter<Buffer>{buf}, nullptr};
|
||||
ser.object(data);
|
||||
auto writtenSize = AdapterAccess::getWriter(ser).writtenBytesCount();
|
||||
bitsery::ext::InheritanceContext ctx1;
|
||||
auto writtenSize = bitsery::quickSerialization(ctx1, Writer{buf}, data);
|
||||
assert(writtenSize == 4);//base is serialized once, because it is inherited virtually
|
||||
|
||||
MultipleInheritance res{0};
|
||||
BasicDeserializer<Reader> des{InputBufferAdapter<Buffer>{buf.begin(), writtenSize}};
|
||||
des.object(res);
|
||||
assert(AdapterAccess::getReader(des).error() == ReaderError::NoError && AdapterAccess::getReader(des).isCompletedSuccessfully());
|
||||
|
||||
bitsery::ext::InheritanceContext ctx2;
|
||||
auto state = bitsery::quickDeserialization(ctx2, Reader{buf.begin(), writtenSize}, res);
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
assert(data.x == res.x && data.y1 == res.y1 && data.getY2() == res.getY2() && data.z == res.z);
|
||||
assert(writtenSize == 4);//base is serialized once, because it is inherited virtually
|
||||
}
|
||||
|
||||
@@ -30,12 +30,10 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
using Writer = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using Reader = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
|
||||
@@ -44,22 +42,21 @@ int main() {
|
||||
data.emplace_back(145.4f, 84.48f);
|
||||
std::vector<MyData> res{};
|
||||
|
||||
//we cant use quick (de)serialization helper methods, because we ant to serialize container directly
|
||||
//create buffer
|
||||
Buffer buffer{};
|
||||
|
||||
//create and serialize container, and get written bytes count
|
||||
Serializer<OutputAdapter> ser{OutputAdapter{buffer}};
|
||||
//we cant use quick (de)serialization helper methods, because we ant to serialize container directly
|
||||
//create writer and serialize container
|
||||
bitsery::Serializer<Writer> ser{buffer};
|
||||
ser.container(data, 10);
|
||||
auto writtenBytes = AdapterAccess::getWriter(ser).writtenBytesCount();
|
||||
ser.adapter().flush();
|
||||
|
||||
//create and deserialize container
|
||||
Deserializer<InputAdapter> des{InputAdapter{buffer.begin(), writtenBytes}};
|
||||
//create reader and deserialize container
|
||||
bitsery::Deserializer<Reader> des{buffer.begin(), ser.adapter().writtenBytesCount()};
|
||||
des.container(res, 10);
|
||||
|
||||
//check if everything went ok
|
||||
auto& reader = AdapterAccess::getReader(des);
|
||||
assert(reader.error() == ReaderError::NoError && reader.isCompletedSuccessfully());
|
||||
assert(des.adapter().error() == bitsery::ReaderError::NoError && des.adapter().isCompletedSuccessfully());
|
||||
assert(res == data);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ private:
|
||||
s.value4b(i1);
|
||||
|
||||
//set container elements to be candidates for non-owning pointers
|
||||
s.container(vdata, 100, [&s](MyStruct& d){
|
||||
s.container(vdata, 100, [](S& s, MyStruct& d){
|
||||
s.ext(d, ReferencedByPointer{});
|
||||
});
|
||||
//contains non owning pointers
|
||||
@@ -67,7 +67,7 @@ private:
|
||||
//
|
||||
//you can also serialize non owning pointers first, pointer linking context will keep track on them
|
||||
//and as soon as pointer owner data is deserialized, all non-owning pointers will be updated
|
||||
s.container(vptr, 100, [&s](MyStruct* (&d)){
|
||||
s.container(vptr, 100, [](S& s, MyStruct* (&d)){
|
||||
s.ext(d, PointerObserver{});
|
||||
});
|
||||
//observer
|
||||
@@ -78,21 +78,16 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
using Writer = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using Reader = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
//we will need PointerLinkingContext to work with pointers
|
||||
//so lets define our serializer/deserializer
|
||||
//if we need context for our own custom flow, we can define it as tuple like this:
|
||||
//if we would require additional context for our own custom flow, we can define it as tuple like this:
|
||||
// std::tuple<MyContext,ext::PointerLinkingContext>
|
||||
//and other code will work as expected as long as it cast to proper type.
|
||||
//see context_usage.cpp for usage example
|
||||
using MySerializer = BasicSerializer<AdapterWriter<OutputAdapter, DefaultConfig>, ext::PointerLinkingContext>;
|
||||
using MyDeserializer = BasicDeserializer<AdapterReader<InputAdapter, DefaultConfig>, ext::PointerLinkingContext>;
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
@@ -115,16 +110,10 @@ int main() {
|
||||
//create buffer to store data
|
||||
Buffer buffer{};
|
||||
size_t writtenSize{};
|
||||
//in order to use pointers, we need to pass pointer linking context to serializer/deserializer
|
||||
//in order to use pointers, we need to pass pointer linking context serializer/deserializer
|
||||
{
|
||||
ext::PointerLinkingContext ctx{};
|
||||
//pass lining context to serializer, by pointer
|
||||
MySerializer ser{OutputAdapter{buffer}, &ctx};
|
||||
//serialize our data
|
||||
ser.object(data);
|
||||
auto& w = AdapterAccess::getWriter(ser);
|
||||
w.flush();
|
||||
writtenSize = w.writtenBytesCount();
|
||||
bitsery::ext::PointerLinkingContext ctx{};
|
||||
writtenSize = quickSerialization(ctx, Writer{buffer}, data);
|
||||
|
||||
//make sure that pointer linking context is valid
|
||||
//this ensures that all non-owning pointers points to data that has been serialized,
|
||||
@@ -134,14 +123,10 @@ int main() {
|
||||
|
||||
Test1Data res{};
|
||||
{
|
||||
ext::PointerLinkingContext ctx{};
|
||||
//pass lining context to deserializer, by pointer
|
||||
MyDeserializer des{InputAdapter{buffer.begin(), writtenSize}, &ctx};
|
||||
//deserialize our data
|
||||
des.object(res);
|
||||
auto& r = AdapterAccess::getReader(des);
|
||||
bitsery::ext::PointerLinkingContext ctx{};
|
||||
auto state = quickDeserialization(ctx, Reader{buffer.begin(), writtenSize}, res);
|
||||
//check if everything went find
|
||||
assert(r.error() == ReaderError::NoError && r.isCompletedSuccessfully());
|
||||
assert(state.first == bitsery::ReaderError::NoError && state.second);
|
||||
//also check for dangling pointers, after deserialization
|
||||
assert(ctx.isValid());
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ void serialize(S &s, SomeShapes &o) {
|
||||
// bitsery will work regardless
|
||||
s.ext(o.weakPtr, StdSmartPtr{});
|
||||
s.ext(o.refPtr, PointerObserver{});
|
||||
s.container(o.sharedList, 100, [&s](std::shared_ptr<Shape> &item) {
|
||||
s.container(o.sharedList, 100, [](S& s, std::shared_ptr<Shape> &item) {
|
||||
s.ext(item, StdSmartPtr{});
|
||||
});
|
||||
}
|
||||
@@ -181,25 +181,21 @@ namespace bitsery {
|
||||
// also it automatically ensures, that classes is registered in the same order for serialization and deserialization
|
||||
using MyPolymorphicClassesForRegistering = bitsery::ext::PolymorphicClassesList<Shape>;
|
||||
|
||||
|
||||
//use bitsery namespace for convenience
|
||||
using namespace bitsery;
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
using Writer = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using Reader = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
//we need to define few things in order to work with polymorphism
|
||||
//1) we need pointer linking context to work with pointers
|
||||
//2) we need polymorphic context to be able to work with polymorphic types
|
||||
using TContext = std::tuple<ext::PointerLinkingContext, ext::PolymorphicContext<ext::StandardRTTI>>;
|
||||
using TContext = std::tuple<
|
||||
bitsery::ext::PointerLinkingContext,
|
||||
bitsery::ext::PolymorphicContext<bitsery::ext::StandardRTTI>>;
|
||||
//NOTE:
|
||||
// RTTI can be customizable, if you can't use dynamic_cast and typeid, and have 'custom' solution
|
||||
|
||||
using MySerializer = BasicSerializer<AdapterWriter<OutputAdapter, DefaultConfig>, TContext>;
|
||||
using MyDeserializer = BasicDeserializer<AdapterReader<InputAdapter, DefaultConfig>, TContext>;
|
||||
|
||||
using MySerializer = bitsery::Serializer<Writer, TContext>;
|
||||
using MyDeserializer = bitsery::Deserializer<Reader, TContext>;
|
||||
|
||||
//checks if deserialized data is equal
|
||||
void assertSameShapes(const SomeShapes &data, const SomeShapes &res) {
|
||||
@@ -232,18 +228,20 @@ int main() {
|
||||
//create buffer to store data
|
||||
Buffer buffer{};
|
||||
size_t writtenSize{};
|
||||
// we will not use quickSerialization/Deserialization functions to show, that we need to register polymorphic classes, explicitly
|
||||
{
|
||||
|
||||
//STEP 2
|
||||
//bind serializer with base polymorphic types, it will go through all reachable classes that is defined in first step.
|
||||
//so you dont need to add Rectangle to reach for RoundedRectangle
|
||||
// before start serialization/deserialization,
|
||||
// bind it with base polymorphic types, it will go through all reachable classes that is defined in first step.
|
||||
// NOTE: you dont need to add Rectangle to reach for RoundedRectangle
|
||||
TContext ctx{};
|
||||
std::get<1>(ctx).registerBasesList<MySerializer>(MyPolymorphicClassesForRegistering{});
|
||||
//serialize our data
|
||||
MySerializer ser{OutputAdapter{buffer}, &ctx};
|
||||
//create writer and serialize
|
||||
MySerializer ser{ctx, buffer};
|
||||
ser.object(data);
|
||||
auto &w = AdapterAccess::getWriter(ser);
|
||||
w.flush();
|
||||
writtenSize = w.writtenBytesCount();
|
||||
ser.adapter().flush();
|
||||
writtenSize = ser.adapter().writtenBytesCount();
|
||||
|
||||
//make sure that pointer linking context is valid
|
||||
//this ensures that all non-owning pointers points to data that has been serialized,
|
||||
@@ -254,12 +252,10 @@ int main() {
|
||||
{
|
||||
TContext ctx{};
|
||||
std::get<1>(ctx).registerBasesList<MyDeserializer>(MyPolymorphicClassesForRegistering{});
|
||||
//serialize our data
|
||||
MyDeserializer des{InputAdapter{buffer.begin(), writtenSize}, &ctx};
|
||||
//deserialize our data
|
||||
MyDeserializer des{ctx, buffer.begin(), writtenSize};
|
||||
des.object(res);
|
||||
auto &r = AdapterAccess::getReader(des);
|
||||
//check if everything went find
|
||||
assert(r.error() == ReaderError::NoError && r.isCompletedSuccessfully());
|
||||
assert(des.adapter().error() == bitsery::ReaderError::NoError && des.adapter().isCompletedSuccessfully());
|
||||
//also check for dangling pointers, after deserialization
|
||||
assert(std::get<0>(ctx).isValid());
|
||||
// clear shared state from pointer linking context,
|
||||
|
||||
@@ -28,129 +28,167 @@
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
//base class that stores container iterators, and is required for session support (for reading sessions data)
|
||||
template<typename Buffer>
|
||||
class BufferIterators {
|
||||
static constexpr bool isConstBuffer = std::is_const<Buffer>::value;
|
||||
using BuffNonConst = typename std::remove_const<Buffer>::type;
|
||||
|
||||
protected:
|
||||
|
||||
using TIterator = typename std::conditional<isConstBuffer,
|
||||
typename traits::BufferAdapterTraits<BuffNonConst>::TConstIterator,
|
||||
typename traits::BufferAdapterTraits<BuffNonConst>::TIterator>::type;
|
||||
static_assert(details::IsDefined<TIterator>::value,
|
||||
"Please define BufferAdapterTraits or include from <bitsery/traits/...>");
|
||||
BufferIterators(TIterator begin, TIterator end)
|
||||
: posIt{begin},
|
||||
endIt{end} {
|
||||
}
|
||||
|
||||
friend details::SessionAccess;
|
||||
|
||||
TIterator posIt;
|
||||
TIterator endIt;
|
||||
};
|
||||
|
||||
template<typename Buffer>
|
||||
class InputBufferAdapter : public BufferIterators<Buffer> {
|
||||
template<typename Buffer, typename Config = DefaultConfig>
|
||||
class InputBufferAdapter: public details::InputAdapterBaseCRTP<InputBufferAdapter<Buffer,Config>> {
|
||||
public:
|
||||
using TIterator = typename BufferIterators<Buffer>::TIterator;
|
||||
friend details::InputAdapterBaseCRTP<InputBufferAdapter<Buffer,Config>>;
|
||||
using TConfig = Config;
|
||||
using TIterator = typename traits::BufferAdapterTraits<typename std::remove_const<Buffer>::type>::TConstIterator;
|
||||
using TValue = typename traits::BufferAdapterTraits<typename std::remove_const<Buffer>::type>::TValue;
|
||||
static_assert(details::IsDefined<TValue>::value,
|
||||
"Please define BufferAdapterTraits or include from <bitsery/traits/...>");
|
||||
static_assert(traits::ContainerTraits<typename std::remove_const<Buffer>::type>::isContiguous,
|
||||
"BufferAdapter only works with contiguous containers");
|
||||
|
||||
InputBufferAdapter(TIterator begin, TIterator endIt)
|
||||
: BufferIterators<Buffer>(begin, endIt) {
|
||||
InputBufferAdapter(TIterator beginIt, size_t size)
|
||||
: _beginIt{beginIt},
|
||||
_currOffset{0},
|
||||
_endReadOffset{size},
|
||||
_bufferSize{size} {
|
||||
};
|
||||
|
||||
InputBufferAdapter(TIterator beginIt, TIterator endIt)
|
||||
:InputBufferAdapter(beginIt, static_cast<size_t>(std::distance(beginIt, endIt))) {
|
||||
}
|
||||
|
||||
InputBufferAdapter(TIterator begin, size_t size)
|
||||
: BufferIterators<Buffer>(begin, std::next(begin, size)) {
|
||||
InputBufferAdapter(const InputBufferAdapter&) = delete;
|
||||
InputBufferAdapter& operator=(const InputBufferAdapter&) = delete;
|
||||
|
||||
InputBufferAdapter(InputBufferAdapter&&) = default;
|
||||
InputBufferAdapter& operator = (InputBufferAdapter&&) = default;
|
||||
|
||||
void currentReadPos(size_t pos) {
|
||||
currentReadPosChecked(pos, std::integral_constant<bool, Config::CheckAdapterErrors>{});
|
||||
}
|
||||
|
||||
void read(TValue *data, size_t size) {
|
||||
//for optimization
|
||||
auto tmp = this->posIt;
|
||||
this->posIt += size;
|
||||
if (std::distance(this->posIt, this->endIt) >= 0) {
|
||||
std::memcpy(data, std::addressof(*tmp), size);
|
||||
size_t currentReadPos() const {
|
||||
return currentReadPosChecked(std::integral_constant<bool, Config::CheckAdapterErrors>{});
|
||||
}
|
||||
|
||||
void currentReadEndPos(size_t pos) {
|
||||
// assert that CheckAdapterErrors is enabled, otherwise it will simply will not work even if data and buffer is not corrupted
|
||||
static_assert(Config::CheckAdapterErrors, "Please enable CheckAdapterErrors to use this functionality.");
|
||||
if (_bufferSize >= pos && error() == ReaderError::NoError) {
|
||||
_overflowOnReadEndPos = pos == 0;
|
||||
if (pos == 0)
|
||||
pos = _bufferSize;
|
||||
_endReadOffset = pos;
|
||||
} else {
|
||||
this->posIt -= size;
|
||||
//set everything to zeros
|
||||
std::memset(data, 0, size);
|
||||
if (error() == ReaderError::NoError)
|
||||
setError(ReaderError::DataOverflow);
|
||||
error(ReaderError::DataOverflow);
|
||||
}
|
||||
}
|
||||
|
||||
size_t currentReadEndPos() const {
|
||||
if (_overflowOnReadEndPos)
|
||||
return 0;
|
||||
return _endReadOffset;
|
||||
}
|
||||
|
||||
ReaderError error() const {
|
||||
auto res = std::distance(this->endIt, this->posIt);
|
||||
if (res > 0) {
|
||||
auto err = static_cast<ReaderError>(res);
|
||||
return err;
|
||||
return _currOffset <= _endReadOffset
|
||||
? ReaderError::NoError
|
||||
: static_cast<ReaderError>(_currOffset - _endReadOffset);
|
||||
}
|
||||
|
||||
void error(ReaderError error) {
|
||||
if (_currOffset <= _endReadOffset) {
|
||||
_endReadOffset = 0;
|
||||
_bufferSize = 0;
|
||||
_currOffset = static_cast<size_t>(error);
|
||||
}
|
||||
return ReaderError::NoError;
|
||||
}
|
||||
|
||||
void setError(ReaderError error) {
|
||||
this->endIt = this->posIt;
|
||||
//to avoid creating temporary for error state, mark an error by passing posIt after the endIt
|
||||
std::advance(this->posIt, static_cast<size_t>(error));
|
||||
}
|
||||
|
||||
bool isCompletedSuccessfully() const {
|
||||
return this->posIt == this->endIt;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Buffer>
|
||||
class UnsafeInputBufferAdapter : public BufferIterators<Buffer> {
|
||||
public:
|
||||
|
||||
using TIterator = typename BufferIterators<Buffer>::TIterator;
|
||||
using TValue = typename traits::BufferAdapterTraits<typename std::remove_const<Buffer>::type>::TValue;
|
||||
static_assert(details::IsDefined<TValue>::value,
|
||||
"Please define BufferAdapterTraits or include from <bitsery/traits/...>");
|
||||
static_assert(traits::ContainerTraits<typename std::remove_const<Buffer>::type>::isContiguous,
|
||||
"BufferAdapter only works with contiguous containers");
|
||||
|
||||
UnsafeInputBufferAdapter(TIterator beginIt, TIterator endIt) : BufferIterators<Buffer>(beginIt, endIt) {
|
||||
}
|
||||
|
||||
UnsafeInputBufferAdapter(TIterator begin, size_t size)
|
||||
: BufferIterators<Buffer>(begin, std::next(begin, size)) {
|
||||
}
|
||||
|
||||
void read(TValue *data, size_t size) {
|
||||
//for optimization
|
||||
auto tmp = this->posIt;
|
||||
this->posIt += size;
|
||||
assert(std::distance(this->posIt, this->endIt) >= 0);
|
||||
std::memcpy(data, std::addressof(*tmp), size);
|
||||
}
|
||||
|
||||
ReaderError error() const {
|
||||
return err;
|
||||
}
|
||||
|
||||
void setError(ReaderError error) {
|
||||
err = error;
|
||||
}
|
||||
|
||||
bool isCompletedSuccessfully() const {
|
||||
return this->posIt == this->endIt;
|
||||
return _currOffset == _bufferSize;
|
||||
}
|
||||
|
||||
private:
|
||||
ReaderError err = ReaderError::NoError;
|
||||
using diff_t = typename std::iterator_traits<TIterator>::difference_type;
|
||||
|
||||
template <size_t SIZE>
|
||||
void readInternalValue(TValue *data) {
|
||||
readInternalValueChecked<SIZE>(data, std::integral_constant<bool, Config::CheckAdapterErrors>{});
|
||||
}
|
||||
|
||||
void readInternalBuffer(TValue *data, size_t size) {
|
||||
readInternalBufferChecked(data, size, std::integral_constant<bool, Config::CheckAdapterErrors>{});
|
||||
}
|
||||
|
||||
template <size_t SIZE>
|
||||
void readInternalValueChecked(TValue *data, std::false_type) {
|
||||
const size_t newOffset = _currOffset + SIZE;
|
||||
assert(newOffset <= _endReadOffset);
|
||||
std::copy_n(_beginIt + static_cast<diff_t>(_currOffset), SIZE, data);
|
||||
_currOffset = newOffset;
|
||||
}
|
||||
|
||||
template <size_t SIZE>
|
||||
void readInternalValueChecked(TValue *data, std::true_type) {
|
||||
const size_t newOffset = _currOffset + SIZE;
|
||||
if (newOffset <= _endReadOffset) {
|
||||
std::copy_n(_beginIt + static_cast<diff_t>(_currOffset), SIZE, data);
|
||||
_currOffset = newOffset;
|
||||
} else {
|
||||
//set everything to zeros
|
||||
std::memset(data, 0, SIZE);
|
||||
if (_overflowOnReadEndPos)
|
||||
error(ReaderError::DataOverflow);
|
||||
}
|
||||
}
|
||||
|
||||
void readInternalBufferChecked(TValue *data, size_t size, std::false_type) {
|
||||
const size_t newOffset = _currOffset + size;
|
||||
assert(newOffset <= _endReadOffset);
|
||||
std::copy_n(_beginIt + static_cast<diff_t>(_currOffset), size, data);
|
||||
_currOffset = newOffset;
|
||||
}
|
||||
|
||||
void readInternalBufferChecked(TValue *data, size_t size, std::true_type) {
|
||||
const size_t newOffset = _currOffset + size;
|
||||
if (newOffset <= _endReadOffset) {
|
||||
std::copy_n(_beginIt + static_cast<diff_t>(_currOffset), size, data);
|
||||
_currOffset = newOffset;
|
||||
} else {
|
||||
//set everything to zeros
|
||||
std::memset(data, 0, size);
|
||||
if (_overflowOnReadEndPos)
|
||||
error(ReaderError::DataOverflow);
|
||||
}
|
||||
}
|
||||
|
||||
void currentReadPosChecked(size_t pos, std::true_type) {
|
||||
if (_bufferSize >= pos && error() == ReaderError::NoError) {
|
||||
_currOffset = pos;
|
||||
} else {
|
||||
error(ReaderError::DataOverflow);
|
||||
}
|
||||
}
|
||||
|
||||
void currentReadPosChecked(size_t pos, std::false_type) {
|
||||
_currOffset = pos;
|
||||
}
|
||||
|
||||
size_t currentReadPosChecked(std::true_type) const {
|
||||
return error() == ReaderError::NoError ? _currOffset : 0;
|
||||
}
|
||||
|
||||
size_t currentReadPosChecked(std::false_type) const {
|
||||
return _currOffset;
|
||||
}
|
||||
|
||||
|
||||
TIterator _beginIt;
|
||||
size_t _currOffset;
|
||||
size_t _endReadOffset;
|
||||
size_t _bufferSize;
|
||||
bool _overflowOnReadEndPos = true;
|
||||
};
|
||||
|
||||
template<typename Buffer>
|
||||
class OutputBufferAdapter {
|
||||
template<typename Buffer, typename Config = DefaultConfig>
|
||||
class OutputBufferAdapter: public details::OutputAdapterBaseCRTP<OutputBufferAdapter<Buffer,Config>> {
|
||||
public:
|
||||
|
||||
friend details::OutputAdapterBaseCRTP<OutputBufferAdapter<Buffer,Config>>;
|
||||
using TConfig = Config;
|
||||
using TIterator = typename traits::BufferAdapterTraits<Buffer>::TIterator;
|
||||
using TValue = typename traits::BufferAdapterTraits<Buffer>::TValue;
|
||||
|
||||
@@ -160,13 +198,26 @@ namespace bitsery {
|
||||
"BufferAdapter only works with contiguous containers");
|
||||
|
||||
OutputBufferAdapter(Buffer &buffer)
|
||||
: _buffer{std::addressof(buffer)} {
|
||||
|
||||
: _buffer{std::addressof(buffer)},
|
||||
_beginIt{std::begin(buffer)} {
|
||||
init(TResizable{});
|
||||
}
|
||||
|
||||
void write(const TValue *data, size_t size) {
|
||||
writeInternal(data, size, TResizable{});
|
||||
OutputBufferAdapter(const OutputBufferAdapter&) = delete;
|
||||
OutputBufferAdapter& operator=(const OutputBufferAdapter&) = delete;
|
||||
OutputBufferAdapter(OutputBufferAdapter&&) = default;
|
||||
OutputBufferAdapter& operator = (OutputBufferAdapter&&) = default;
|
||||
|
||||
void currentWritePos(size_t pos) {
|
||||
const auto maxPos = _currOffset > pos ? _currOffset : pos;
|
||||
if (maxPos > _biggestCurrentPos) {
|
||||
_biggestCurrentPos = maxPos;
|
||||
}
|
||||
setCurrentWritePos(pos, TResizable{});
|
||||
}
|
||||
|
||||
size_t currentWritePos() const {
|
||||
return _currOffset;
|
||||
}
|
||||
|
||||
void flush() {
|
||||
@@ -174,15 +225,27 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
size_t writtenBytesCount() const {
|
||||
return static_cast<size_t>(std::distance(std::begin(*_buffer), _outIt));
|
||||
return _currOffset > _biggestCurrentPos ? _currOffset : _biggestCurrentPos;
|
||||
}
|
||||
|
||||
private:
|
||||
using TResizable = std::integral_constant<bool, traits::ContainerTraits<Buffer>::isResizable>;
|
||||
using diff_t = typename std::iterator_traits<TIterator>::difference_type;
|
||||
|
||||
Buffer *_buffer;
|
||||
TIterator _outIt{};
|
||||
TIterator _end{};
|
||||
template <size_t SIZE>
|
||||
void writeInternalValue(const TValue *data) {
|
||||
writeInternalValueImpl<SIZE>(data, TResizable{});
|
||||
}
|
||||
|
||||
void writeInternalBuffer(const TValue *data, size_t size) {
|
||||
writeInternalBufferImpl(data, size, TResizable{});
|
||||
}
|
||||
|
||||
Buffer* _buffer;
|
||||
TIterator _beginIt;
|
||||
size_t _currOffset{0};
|
||||
size_t _bufferSize{0};
|
||||
size_t _biggestCurrentPos{0};
|
||||
|
||||
/*
|
||||
* resizable buffer
|
||||
@@ -193,38 +256,41 @@ namespace bitsery {
|
||||
if (traits::ContainerTraits<Buffer>::size(*_buffer) == 0u) {
|
||||
traits::BufferAdapterTraits<Buffer>::increaseBufferSize(*_buffer);
|
||||
}
|
||||
_end = std::end(*_buffer);
|
||||
_outIt = std::begin(*_buffer);
|
||||
updateIteratorAndSize();
|
||||
}
|
||||
|
||||
void writeInternal(const TValue *data, const size_t size, std::true_type) {
|
||||
//optimization
|
||||
#if defined(_MSC_VER) && (_ITERATOR_DEBUG_LEVEL > 0)
|
||||
using TDistance = typename std::iterator_traits<TIterator>::difference_type;
|
||||
if (std::distance(_outIt , _end) >= static_cast<TDistance>(size)) {
|
||||
std::memcpy(std::addressof(*_outIt), data, size);
|
||||
_outIt += size;
|
||||
#else
|
||||
auto tmp = _outIt;
|
||||
_outIt += size;
|
||||
if (std::distance(_outIt, _end) >= 0) {
|
||||
std::memcpy(std::addressof(*tmp), data, size);
|
||||
#endif
|
||||
template <size_t SIZE>
|
||||
void writeInternalValueImpl(const TValue *data, std::true_type) {
|
||||
const size_t newOffset = _currOffset + SIZE;
|
||||
if (newOffset <= _bufferSize) {
|
||||
std::copy_n(data, SIZE, _beginIt + static_cast<diff_t>(_currOffset));
|
||||
_currOffset = newOffset;
|
||||
} else {
|
||||
#if defined(_MSC_VER) && (_ITERATOR_DEBUG_LEVEL > 0)
|
||||
|
||||
#else
|
||||
_outIt -= size;
|
||||
#endif
|
||||
//get current position before invalidating iterators
|
||||
const auto pos = std::distance(std::begin(*_buffer), _outIt);
|
||||
//increase container size
|
||||
traits::BufferAdapterTraits<Buffer>::increaseBufferSize(*_buffer);
|
||||
//restore iterators
|
||||
_end = std::end(*_buffer);
|
||||
_outIt = std::next(std::begin(*_buffer), pos);
|
||||
updateIteratorAndSize();
|
||||
writeInternalValueImpl<SIZE>(data, std::true_type{});
|
||||
}
|
||||
}
|
||||
|
||||
writeInternal(data, size, std::true_type{});
|
||||
void writeInternalBufferImpl(const TValue *data, const size_t size, std::true_type) {
|
||||
const size_t newOffset = _currOffset + size;
|
||||
if (newOffset <= _bufferSize) {
|
||||
std::copy_n(data, size, _beginIt + static_cast<diff_t>(_currOffset));
|
||||
_currOffset = newOffset;
|
||||
} else {
|
||||
traits::BufferAdapterTraits<Buffer>::increaseBufferSize(*_buffer);
|
||||
updateIteratorAndSize();
|
||||
writeInternalBufferImpl(data, size, std::true_type{});
|
||||
}
|
||||
}
|
||||
|
||||
void setCurrentWritePos(size_t pos, std::true_type) {
|
||||
if (pos <= _bufferSize) {
|
||||
_currOffset = pos;
|
||||
} else {
|
||||
traits::BufferAdapterTraits<Buffer>::increaseBufferSize(*_buffer);
|
||||
updateIteratorAndSize();
|
||||
setCurrentWritePos(pos, std::true_type{});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,16 +298,32 @@ namespace bitsery {
|
||||
* non resizable buffer
|
||||
*/
|
||||
void init(std::false_type) {
|
||||
_outIt = std::begin(*_buffer);
|
||||
_end = std::end(*_buffer);
|
||||
updateIteratorAndSize();
|
||||
}
|
||||
|
||||
void writeInternal(const TValue *data, size_t size, std::false_type) {
|
||||
//optimization
|
||||
auto tmp = _outIt;
|
||||
_outIt += size;
|
||||
assert(std::distance(_outIt, _end) >= 0);
|
||||
memcpy(std::addressof(*tmp), data, size);
|
||||
template <size_t SIZE>
|
||||
void writeInternalValueImpl(const TValue *data, std::false_type) {
|
||||
const size_t newOffset = _currOffset + SIZE;
|
||||
assert(newOffset <= _bufferSize);
|
||||
std::copy_n(data, SIZE, _beginIt + static_cast<diff_t>(_currOffset));
|
||||
_currOffset = newOffset;
|
||||
}
|
||||
|
||||
void writeInternalBufferImpl(const TValue *data, size_t size, std::false_type) {
|
||||
const size_t newOffset = _currOffset + size;
|
||||
assert(newOffset <= _bufferSize);
|
||||
std::copy_n(data, size, _beginIt + static_cast<diff_t>(_currOffset));
|
||||
_currOffset = newOffset;
|
||||
}
|
||||
|
||||
void setCurrentWritePos(size_t pos, std::false_type) {
|
||||
assert(pos <= _bufferSize);
|
||||
_currOffset = pos;
|
||||
}
|
||||
|
||||
void updateIteratorAndSize() {
|
||||
_beginIt = std::begin(*_buffer);
|
||||
_bufferSize = traits::ContainerTraits<Buffer>::size(*_buffer);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
95
include/bitsery/adapter/measure_size.h
Normal file
95
include/bitsery/adapter/measure_size.h
Normal file
@@ -0,0 +1,95 @@
|
||||
//MIT License
|
||||
//
|
||||
//Copyright (c) 2019 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_ADAPTER_MEASURE_SIZE_H
|
||||
#define BITSERY_ADAPTER_MEASURE_SIZE_H
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
|
||||
template<typename Config>
|
||||
class BasicMeasureSize {
|
||||
public:
|
||||
|
||||
static constexpr bool BitPackingEnabled = true;
|
||||
using TConfig = Config;
|
||||
using TValue = void;
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBytes(const T&) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
_currPosBits += details::BitsSize<T>::value;
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBuffer(const T*, size_t count) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
_currPosBits += details::BitsSize<T>::value * count;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void writeBits(const T&, size_t bitsCount) {
|
||||
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
|
||||
assert(bitsCount <= details::BitsSize<T>::value);
|
||||
_currPosBits += bitsCount;
|
||||
}
|
||||
|
||||
void currentWritePos(size_t pos) {
|
||||
align();
|
||||
const auto newPos = pos * 8;
|
||||
if (_currPosBits > newPos)
|
||||
_prevLargestPos = _currPosBits;
|
||||
_currPosBits = newPos;
|
||||
}
|
||||
|
||||
size_t currentWritePos() const {
|
||||
return _currPosBits / 8;
|
||||
}
|
||||
|
||||
void align() {
|
||||
auto _scratch = (_currPosBits % 8);
|
||||
_currPosBits += (8 - _scratch) % 8;
|
||||
}
|
||||
|
||||
void flush() {
|
||||
align();
|
||||
}
|
||||
|
||||
//get size in bytes
|
||||
size_t writtenBytesCount() const {
|
||||
const auto max = _currPosBits > _prevLargestPos ? _currPosBits : _prevLargestPos;
|
||||
return max / 8;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _prevLargestPos{};
|
||||
size_t _currPosBits{};
|
||||
};
|
||||
|
||||
//helper type for default config
|
||||
using MeasureSize = BasicMeasureSize<DefaultConfig>;
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_ADAPTER_MEASURE_SIZE_H
|
||||
@@ -29,63 +29,110 @@
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
template <typename TChar, typename CharTraits>
|
||||
class BasicInputStreamAdapter {
|
||||
template <typename TChar, typename Config, typename CharTraits>
|
||||
class BasicInputStreamAdapter: public details::InputAdapterBaseCRTP<BasicInputStreamAdapter<TChar, Config, CharTraits>> {
|
||||
public:
|
||||
friend details::InputAdapterBaseCRTP<BasicInputStreamAdapter<TChar, Config, CharTraits>>;
|
||||
using TConfig = Config;
|
||||
using TValue = TChar;
|
||||
using TIterator = void;//TIterator is used with sessions, but streams cannot be used with sessions
|
||||
|
||||
BasicInputStreamAdapter(std::basic_ios<TChar, CharTraits>& istream)
|
||||
:_ios{std::addressof(istream)} {}
|
||||
|
||||
void read(TValue* data, size_t size) {
|
||||
if (static_cast<size_t>(_ios->rdbuf()->sgetn( data , size )) != size) {
|
||||
*data = {};
|
||||
//check state, if not set by stream, set it manually
|
||||
if (_ios->good())
|
||||
_ios->setstate(std::ios_base::eofbit);
|
||||
}
|
||||
BasicInputStreamAdapter(const BasicInputStreamAdapter&) = delete;
|
||||
BasicInputStreamAdapter& operator = (const BasicInputStreamAdapter&) = delete;
|
||||
|
||||
BasicInputStreamAdapter(BasicInputStreamAdapter&&) = default;
|
||||
BasicInputStreamAdapter& operator = (BasicInputStreamAdapter&&) = default;
|
||||
|
||||
void currentReadPos(size_t ) {
|
||||
static_assert(std::is_void<TChar>::value, "setting read position is not supported with StreamAdapter");
|
||||
}
|
||||
|
||||
size_t currentReadPos() const {
|
||||
static_assert(std::is_void<TChar>::value, "setting read position is not supported with StreamAdapter");
|
||||
return {};
|
||||
}
|
||||
|
||||
void currentReadEndPos(size_t ) {
|
||||
static_assert(std::is_void<TChar>::value, "setting read position is not supported with StreamAdapter");
|
||||
}
|
||||
|
||||
size_t currentReadEndPos() const {
|
||||
static_assert(std::is_void<TChar>::value, "setting read position is not supported with StreamAdapter");
|
||||
return {};
|
||||
}
|
||||
|
||||
ReaderError error() const {
|
||||
if (_ios->good())
|
||||
return ReaderError::NoError;
|
||||
return _ios->eof()
|
||||
? ReaderError::DataOverflow
|
||||
: ReaderError::ReadingError;
|
||||
return _err;
|
||||
}
|
||||
|
||||
bool isCompletedSuccessfully() const {
|
||||
if (error() == ReaderError::NoError) {
|
||||
return _ios->rdbuf()->sgetc() == CharTraits::eof();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void setError(ReaderError ) {
|
||||
//has no effect when using
|
||||
|
||||
void error(ReaderError error) {
|
||||
if (_err == ReaderError::NoError) {
|
||||
_err = error;
|
||||
_zeroIfNoErrors = std::numeric_limits<size_t>::max();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template <size_t SIZE>
|
||||
void readInternalValue(TValue* data) {
|
||||
readChecked(data, SIZE, std::integral_constant<bool, Config::CheckAdapterErrors>{});
|
||||
}
|
||||
|
||||
void readInternalBuffer(TValue* data, size_t size) {
|
||||
readChecked(data, size, std::integral_constant<bool, Config::CheckAdapterErrors>{});
|
||||
}
|
||||
|
||||
void readChecked(TValue* data, size_t size, std::true_type) {
|
||||
if (size - static_cast<size_t>(_ios->rdbuf()->sgetn(data, static_cast<std::streamsize>(size))) != _zeroIfNoErrors) {
|
||||
*data = {};
|
||||
if (_zeroIfNoErrors == 0) {
|
||||
error(_ios->rdstate() == std::ios_base::badbit
|
||||
? ReaderError::ReadingError
|
||||
: ReaderError::DataOverflow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void readChecked(TValue* data, size_t size, std::false_type) {
|
||||
_ios->rdbuf()->sgetn(data , static_cast<std::streamsize>(size));
|
||||
}
|
||||
|
||||
std::basic_ios<TChar, CharTraits>* _ios;
|
||||
size_t _zeroIfNoErrors{};
|
||||
ReaderError _err = ReaderError::NoError;
|
||||
};
|
||||
|
||||
template <typename TChar, typename CharTraits>
|
||||
class BasicOutputStreamAdapter {
|
||||
template <typename TChar, typename Config, typename CharTraits>
|
||||
class BasicOutputStreamAdapter: public details::OutputAdapterBaseCRTP<BasicOutputStreamAdapter<TChar, Config, CharTraits>> {
|
||||
public:
|
||||
friend details::OutputAdapterBaseCRTP<BasicOutputStreamAdapter<TChar, Config, CharTraits>>;
|
||||
using TConfig = Config;
|
||||
using TValue = TChar;
|
||||
using TIterator = void;//TIterator is used with sessions, but streams cannot be used with sessions
|
||||
|
||||
BasicOutputStreamAdapter(std::basic_ios<TChar, CharTraits>& ostream)
|
||||
:_ios{std::addressof(ostream)} {}
|
||||
BasicOutputStreamAdapter(std::basic_ostream<TChar, CharTraits>& ostream)
|
||||
:_ostream{std::addressof(ostream)} {}
|
||||
|
||||
void write(const TValue* data, size_t size) {
|
||||
//for optimization
|
||||
_ios->rdbuf()->sputn( data , size );
|
||||
void currentWritePos(size_t ) {
|
||||
static_assert(std::is_void<TChar>::value, "setting write position is not supported with StreamAdapter");
|
||||
}
|
||||
|
||||
size_t currentWritePos() const {
|
||||
static_assert(std::is_void<TChar>::value, "setting write position is not supported with StreamAdapter");
|
||||
return {};
|
||||
}
|
||||
|
||||
void flush() {
|
||||
if (auto ostream = dynamic_cast<std::basic_ostream<TChar, CharTraits>*>(_ios))
|
||||
ostream->flush();
|
||||
_ostream->flush();
|
||||
}
|
||||
|
||||
size_t writtenBytesCount() const {
|
||||
@@ -94,132 +141,157 @@ namespace bitsery {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
//this method is only for stream writing
|
||||
bool isValidState() const {
|
||||
return !_ios->bad();
|
||||
private:
|
||||
|
||||
template <size_t SIZE>
|
||||
void writeInternalValue(const TValue* data) {
|
||||
_ostream->rdbuf()->sputn( data , SIZE );
|
||||
}
|
||||
|
||||
private:
|
||||
std::basic_ios<TChar, CharTraits>* _ios;
|
||||
void writeInternalBuffer(const TValue* data, size_t size) {
|
||||
_ostream->rdbuf()->sputn( data , size );
|
||||
}
|
||||
|
||||
std::basic_ostream<TChar, CharTraits>* _ostream;
|
||||
};
|
||||
|
||||
template <typename TChar, typename CharTraits, typename TBuffer = std::array<TChar, 256>>
|
||||
class BasicBufferedOutputStreamAdapter {
|
||||
template <typename TChar, typename Config, typename CharTraits, typename TBuffer = std::array<TChar, 256>>
|
||||
class BasicBufferedOutputStreamAdapter:
|
||||
public details::OutputAdapterBaseCRTP<BasicBufferedOutputStreamAdapter<TChar, Config, CharTraits, TBuffer>> {
|
||||
public:
|
||||
friend details::OutputAdapterBaseCRTP<BasicBufferedOutputStreamAdapter<TChar, Config, CharTraits, TBuffer>>;
|
||||
using TConfig = Config;
|
||||
using Buffer = TBuffer;
|
||||
using BufferIt = typename traits::BufferAdapterTraits<TBuffer>::TIterator;
|
||||
static_assert(details::IsDefined<BufferIt>::value, "Please define BufferAdapterTraits or include from <bitsery/traits/...> to use as buffer for BasicBufferedOutputStreamAdapter");
|
||||
static_assert(traits::ContainerTraits<Buffer>::isContiguous, "BasicBufferedOutputStreamAdapter only works with contiguous containers");
|
||||
using TValue = TChar;
|
||||
using TIterator = void;//TIterator is used with sessions, but streams cannot be used with sessions
|
||||
|
||||
//bufferSize is used when buffer is dynamically allocated
|
||||
BasicBufferedOutputStreamAdapter(std::basic_ios<TChar, CharTraits>& ostream, size_t bufferSize = 256)
|
||||
:_adapter(ostream),
|
||||
BasicBufferedOutputStreamAdapter(std::basic_ostream<TChar, CharTraits>& ostream, size_t bufferSize = 256)
|
||||
:_ostream(std::addressof(ostream)),
|
||||
_buf{},
|
||||
_outIt{}
|
||||
_beginIt{std::begin(_buf)},
|
||||
_currOffset{0}
|
||||
{
|
||||
init(bufferSize, TResizable{});
|
||||
// buffer size must be atleast 16, because writeIntervalValue expect that at least one value fits to buffer.
|
||||
assert(_bufferSize >= 16);
|
||||
}
|
||||
|
||||
//we need to explicitly declare move logic, in case buffer is static, because after move it will be invalidated
|
||||
//we need to explicitly declare move logic, because after move buffer might be invalidated
|
||||
BasicBufferedOutputStreamAdapter(const BasicBufferedOutputStreamAdapter&) = delete;
|
||||
BasicBufferedOutputStreamAdapter& operator = (const BasicBufferedOutputStreamAdapter&) = delete;
|
||||
|
||||
BasicBufferedOutputStreamAdapter(BasicBufferedOutputStreamAdapter&& rhs)
|
||||
: _adapter{std::move(rhs._adapter)},
|
||||
_buf{},
|
||||
_outIt{}
|
||||
: _ostream{rhs._ostream},
|
||||
_buf{std::move(rhs._buf)},
|
||||
_beginIt{std::begin(_buf)},
|
||||
_currOffset{rhs._currOffset},
|
||||
_bufferSize{rhs._bufferSize}
|
||||
{
|
||||
auto size = std::distance(std::begin(rhs._buf), rhs._outIt);
|
||||
_buf = std::move(rhs._buf);
|
||||
_outIt = std::next(std::begin(_buf), size);
|
||||
};
|
||||
|
||||
BasicBufferedOutputStreamAdapter& operator = (BasicBufferedOutputStreamAdapter&& rhs) {
|
||||
_adapter = std::move(rhs._adapter);
|
||||
//get current written size, before move
|
||||
auto size = std::distance(std::begin(rhs._buf), rhs._outIt);
|
||||
_ostream = rhs._ostream;
|
||||
_buf = std::move(rhs._buf);
|
||||
_outIt = std::next(std::begin(_buf), size);
|
||||
_beginIt = std::begin(_buf);
|
||||
_currOffset = rhs._currOffset;
|
||||
_bufferSize = rhs._bufferSize;
|
||||
return *this;
|
||||
};
|
||||
|
||||
~BasicBufferedOutputStreamAdapter() = default;
|
||||
void currentWritePos(size_t ) {
|
||||
static_assert(std::is_void<TChar>::value, "setting write position is not supported with StreamAdapter");
|
||||
}
|
||||
|
||||
void write(const TValue* data, size_t size) {
|
||||
auto tmp = _outIt;
|
||||
|
||||
#if defined(_MSC_VER) && (_ITERATOR_DEBUG_LEVEL > 0)
|
||||
using TDistance = typename std::iterator_traits<BufferIt>::difference_type;
|
||||
if (std::distance(_outIt , std::end(_buf)) >= static_cast<TDistance>(size)) {
|
||||
std::memcpy(std::addressof(*_outIt), data, size);
|
||||
_outIt += size;
|
||||
#else
|
||||
_outIt += size;
|
||||
if (std::distance(_outIt , std::end(_buf)) >= 0) {
|
||||
std::memcpy(std::addressof(*tmp), data, size);
|
||||
#endif
|
||||
} else {
|
||||
//when buffer is full write out to stream
|
||||
_outIt = std::begin(_buf);
|
||||
_adapter.write(std::addressof(*_outIt), static_cast<size_t>(std::distance(_outIt, tmp)));
|
||||
_adapter.write(data, size);
|
||||
}
|
||||
size_t currentWritePos() const {
|
||||
static_assert(std::is_void<TChar>::value, "setting write position is not supported with StreamAdapter");
|
||||
return {};
|
||||
}
|
||||
|
||||
void flush() {
|
||||
auto begin = std::begin(_buf);
|
||||
_adapter.write(std::addressof(*begin), static_cast<size_t>(std::distance(begin, _outIt)));
|
||||
_outIt = begin;
|
||||
_adapter.flush();
|
||||
writeBufferToStream();
|
||||
_ostream->flush();
|
||||
}
|
||||
|
||||
size_t writtenBytesCount() const {
|
||||
return _adapter.writtenBytesCount();
|
||||
}
|
||||
|
||||
//this method is only for stream writing
|
||||
bool isValidState() const {
|
||||
return _adapter.isValidState();
|
||||
static_assert(std::is_void<TChar>::value, "`writtenBytesCount` cannot be used with stream adapter");
|
||||
//streaming doesn't return written bytes
|
||||
return 0u;
|
||||
}
|
||||
|
||||
private:
|
||||
using TResizable = std::integral_constant<bool, traits::ContainerTraits<TBuffer>::isResizable>;
|
||||
using diff_t = typename std::iterator_traits<BufferIt>::difference_type;
|
||||
|
||||
void init (size_t bufferSize, std::true_type) {
|
||||
_buf.resize(bufferSize);
|
||||
_outIt = std::begin(_buf);
|
||||
}
|
||||
void init (size_t, std::false_type) {
|
||||
_outIt = std::begin(_buf);
|
||||
template <size_t SIZE>
|
||||
void writeInternalValue(const TValue* data) {
|
||||
auto newOffset = _currOffset + SIZE;
|
||||
if (newOffset > _bufferSize) {
|
||||
writeBufferToStream();
|
||||
newOffset = SIZE;
|
||||
}
|
||||
std::copy_n(data, SIZE, _beginIt + static_cast<diff_t>(_currOffset));
|
||||
_currOffset = newOffset;
|
||||
}
|
||||
|
||||
BasicOutputStreamAdapter<TChar, CharTraits> _adapter;
|
||||
void writeInternalBuffer(const TValue* data, size_t size) {
|
||||
const auto newOffset = _currOffset + size;
|
||||
if (newOffset <= _bufferSize) {
|
||||
std::copy_n(data, size, _beginIt + static_cast<diff_t>(_currOffset));
|
||||
_currOffset = newOffset;
|
||||
} else {
|
||||
writeBufferToStream();
|
||||
// write buffer directly to stream
|
||||
_ostream->rdbuf()->sputn(data, size);
|
||||
}
|
||||
}
|
||||
|
||||
void writeBufferToStream() {
|
||||
_ostream->rdbuf()->sputn(std::addressof(*_beginIt), static_cast<std::streamsize>(_currOffset));
|
||||
_currOffset = 0;
|
||||
}
|
||||
|
||||
void init (size_t buffSize, std::true_type) {
|
||||
// resize buffer
|
||||
_bufferSize = buffSize;
|
||||
_buf.resize(_bufferSize);
|
||||
_beginIt = std::begin(_buf);
|
||||
}
|
||||
void init (size_t , std::false_type) {
|
||||
// ignore buffer size parameter, and instead take actual buffer size
|
||||
_bufferSize = traits::ContainerTraits<Buffer>::size(_buf);
|
||||
}
|
||||
|
||||
std::basic_ostream<TChar, CharTraits>* _ostream;
|
||||
TBuffer _buf;
|
||||
BufferIt _outIt;
|
||||
BufferIt _beginIt;
|
||||
size_t _currOffset;
|
||||
size_t _bufferSize{0};
|
||||
};
|
||||
|
||||
template <typename TChar, typename CharTraits>
|
||||
class BasicIOStreamAdapter:public BasicInputStreamAdapter<TChar, CharTraits>, public BasicOutputStreamAdapter<TChar, CharTraits> {
|
||||
template <typename TChar, typename Config, typename CharTraits>
|
||||
class BasicIOStreamAdapter
|
||||
: public BasicInputStreamAdapter<TChar, Config, CharTraits>
|
||||
, public BasicOutputStreamAdapter<TChar, Config, CharTraits>
|
||||
{
|
||||
public:
|
||||
using TValue = TChar;
|
||||
using TIterator = void;//TIterator is used with sessions, but streams cannot be used with sessions
|
||||
|
||||
//both bases contain reference to same iostream, so no need to do anything
|
||||
BasicIOStreamAdapter(std::basic_ios<TChar, CharTraits>& iostream)
|
||||
:BasicInputStreamAdapter<TChar, CharTraits>{iostream},
|
||||
BasicOutputStreamAdapter<TChar, CharTraits>{iostream} {
|
||||
|
||||
}
|
||||
BasicIOStreamAdapter(std::basic_iostream<TChar, CharTraits>& iostream)
|
||||
:BasicInputStreamAdapter<TChar, Config, CharTraits>{iostream}
|
||||
,BasicOutputStreamAdapter<TChar, Config, CharTraits>{iostream}
|
||||
{}
|
||||
};
|
||||
|
||||
//helper types for most common implementations for std streams
|
||||
using OutputStreamAdapter = BasicOutputStreamAdapter<char, std::char_traits<char>>;
|
||||
using InputStreamAdapter = BasicInputStreamAdapter<char, std::char_traits<char>>;
|
||||
using IOStreamAdapter = BasicIOStreamAdapter<char, std::char_traits<char>>;
|
||||
using OutputStreamAdapter = BasicOutputStreamAdapter<char, DefaultConfig, std::char_traits<char>>;
|
||||
using InputStreamAdapter = BasicInputStreamAdapter<char, DefaultConfig, std::char_traits<char>>;
|
||||
using IOStreamAdapter = BasicIOStreamAdapter<char, DefaultConfig, std::char_traits<char>>;
|
||||
|
||||
using OutputBufferedStreamAdapter = BasicBufferedOutputStreamAdapter<char, std::char_traits<char>>;
|
||||
using OutputBufferedStreamAdapter = BasicBufferedOutputStreamAdapter<char, DefaultConfig, std::char_traits<char>>;
|
||||
}
|
||||
|
||||
#endif //BITSERY_ADAPTER_STREAM_H
|
||||
|
||||
@@ -1,263 +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_ADAPTER_READER_H
|
||||
#define BITSERY_ADAPTER_READER_H
|
||||
|
||||
#include "details/sessions.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
template <typename TReader>
|
||||
class AdapterReaderBitPackingWrapper;
|
||||
|
||||
template<typename InputAdapter, typename Config>
|
||||
struct AdapterReader {
|
||||
//this is required by deserializer
|
||||
static constexpr bool BitPackingEnabled = false;
|
||||
using TConfig = Config;
|
||||
using TValue = typename InputAdapter::TValue;
|
||||
|
||||
static_assert(details::IsDefined<TValue>::value, "Please define adapter traits or include from <bitsery/traits/...>");
|
||||
|
||||
using TIterator = typename InputAdapter::TIterator;// used by session reader
|
||||
|
||||
explicit AdapterReader(InputAdapter&& adapter)
|
||||
: _inputAdapter{std::move(adapter)},
|
||||
_session{*this, _inputAdapter}
|
||||
{
|
||||
}
|
||||
|
||||
AdapterReader(const AdapterReader &) = delete;
|
||||
|
||||
AdapterReader &operator=(const AdapterReader &) = delete;
|
||||
|
||||
//todo add conditional noexcept
|
||||
AdapterReader(AdapterReader &&) = default;
|
||||
|
||||
AdapterReader &operator=(AdapterReader &&) = default;
|
||||
|
||||
~AdapterReader() noexcept = default;
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void readBytes(T &v) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
directRead(&v, 1);
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void readBuffer(T *buf, size_t count) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
directRead(buf, count);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void readBits(T &, size_t ) {
|
||||
static_assert(std::is_void<T>::value,
|
||||
"Bit-packing is not enabled.\nEnable by call to `enableBitPacking`) or create Deserializer with bit packing enabled.");
|
||||
}
|
||||
|
||||
void align() {
|
||||
}
|
||||
|
||||
bool isCompletedSuccessfully() const {
|
||||
return _inputAdapter.isCompletedSuccessfully() && !_session.hasActiveSessions();
|
||||
}
|
||||
|
||||
ReaderError error() const {
|
||||
auto err = _inputAdapter.error();
|
||||
if (err == ReaderError::DataOverflow && _session.hasActiveSessions())
|
||||
return ReaderError::NoError;
|
||||
return err;
|
||||
}
|
||||
|
||||
void setError(ReaderError error) {
|
||||
if (this->error() == ReaderError::NoError)
|
||||
_inputAdapter.setError(error);
|
||||
}
|
||||
|
||||
void beginSession() {
|
||||
if (error() == ReaderError::NoError) {
|
||||
_session.begin();
|
||||
}
|
||||
}
|
||||
|
||||
void endSession() {
|
||||
if (error() == ReaderError::NoError) {
|
||||
_session.end();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
friend class AdapterReaderBitPackingWrapper<AdapterReader<InputAdapter, Config>>;
|
||||
|
||||
InputAdapter _inputAdapter;
|
||||
typename std::conditional<Config::BufferSessionsEnabled,
|
||||
session::SessionsReader<AdapterReader<InputAdapter, Config>>,
|
||||
session::DisabledSessionsReader<AdapterReader<InputAdapter, Config>>>::type
|
||||
_session;
|
||||
|
||||
template<typename T>
|
||||
void directRead(T *v, size_t count) {
|
||||
static_assert(!std::is_const<T>::value, "");
|
||||
_inputAdapter.read(reinterpret_cast<TValue *>(v), sizeof(T) * count);
|
||||
//swap each byte if necessary
|
||||
_swapDataBits(v, count, std::integral_constant<bool,
|
||||
Config::NetworkEndianness != details::getSystemEndianness()>{});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void _swapDataBits(T *v, size_t count, std::true_type) {
|
||||
std::for_each(v, std::next(v, count), [this](T &x) { x = details::swap(x); });
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void _swapDataBits(T *, size_t , std::false_type) {
|
||||
//empty function because no swap is required
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename TReader>
|
||||
class AdapterReaderBitPackingWrapper {
|
||||
public:
|
||||
//this is required by deserializer
|
||||
static constexpr bool BitPackingEnabled = true;
|
||||
using TConfig = typename TReader::TConfig;
|
||||
//make TValue unsigned for bitpacking
|
||||
using UnsignedValue = typename std::make_unsigned<typename TReader::TValue>::type;
|
||||
using ScratchType = typename details::ScratchType<UnsignedValue>::type;
|
||||
static_assert(details::IsDefined<ScratchType>::value, "Underlying adapter value type is not supported");
|
||||
|
||||
explicit AdapterReaderBitPackingWrapper(TReader& reader):_reader{reader}
|
||||
{
|
||||
}
|
||||
|
||||
AdapterReaderBitPackingWrapper(const AdapterReaderBitPackingWrapper&) = delete;
|
||||
AdapterReaderBitPackingWrapper& operator = (const AdapterReaderBitPackingWrapper&) = delete;
|
||||
|
||||
AdapterReaderBitPackingWrapper(AdapterReaderBitPackingWrapper&& ) noexcept = default;
|
||||
AdapterReaderBitPackingWrapper& operator = (AdapterReaderBitPackingWrapper&& ) noexcept = default;
|
||||
|
||||
~AdapterReaderBitPackingWrapper() {
|
||||
align();
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void readBytes(T &v) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
using UT = typename std::make_unsigned<T>::type;
|
||||
if (!m_scratchBits)
|
||||
_reader.template readBytes<SIZE,T>(v);
|
||||
else
|
||||
readBits(reinterpret_cast<UT &>(v), details::BitsSize<T>::value);
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void readBuffer(T *buf, size_t count) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
|
||||
if (!m_scratchBits) {
|
||||
_reader.template readBuffer<SIZE,T>(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::BitsSize<T>::value);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void readBits(T &v, size_t bitsCount) {
|
||||
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
|
||||
readBitsInternal(v, bitsCount);
|
||||
}
|
||||
|
||||
void align() {
|
||||
if (m_scratchBits) {
|
||||
ScratchType tmp{};
|
||||
readBitsInternal(tmp, m_scratchBits);
|
||||
if (tmp)
|
||||
setError(ReaderError::InvalidData);
|
||||
}
|
||||
}
|
||||
|
||||
bool isCompletedSuccessfully() const {
|
||||
return _reader.isCompletedSuccessfully();
|
||||
}
|
||||
|
||||
ReaderError error() const {
|
||||
return _reader.error();
|
||||
}
|
||||
|
||||
void setError(ReaderError error) {
|
||||
_reader.setError(error);
|
||||
}
|
||||
|
||||
void beginSession() {
|
||||
align();
|
||||
_reader.beginSession();
|
||||
}
|
||||
|
||||
void endSession() {
|
||||
align();
|
||||
_reader.endSession();
|
||||
}
|
||||
|
||||
private:
|
||||
TReader& _reader;
|
||||
ScratchType m_scratch{};
|
||||
size_t m_scratchBits{};
|
||||
|
||||
template<typename T>
|
||||
void readBitsInternal(T &v, size_t size) {
|
||||
auto bitsLeft = size;
|
||||
T res{};
|
||||
while (bitsLeft > 0) {
|
||||
auto bits = (std::min)(bitsLeft, details::BitsSize<UnsignedValue>::value);
|
||||
if (m_scratchBits < bits) {
|
||||
UnsignedValue tmp;
|
||||
_reader.template readBytes<sizeof(UnsignedValue), UnsignedValue>(tmp);
|
||||
m_scratch |= static_cast<ScratchType>(tmp) << m_scratchBits;
|
||||
m_scratchBits += details::BitsSize<UnsignedValue>::value;
|
||||
}
|
||||
auto shiftedRes =
|
||||
static_cast<T>(m_scratch & ((static_cast<ScratchType>(1) << bits) - 1)) << (size - bitsLeft);
|
||||
res |= shiftedRes;
|
||||
m_scratch >>= bits;
|
||||
m_scratchBits -= bits;
|
||||
bitsLeft -= bits;
|
||||
}
|
||||
v = res;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif //BITSERY_ADAPTER_READER_H
|
||||
@@ -1,335 +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_ADAPTER_WRITER_H
|
||||
#define BITSERY_ADAPTER_WRITER_H
|
||||
|
||||
#include "details/sessions.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
template <typename Config>
|
||||
struct BasicMeasureSize {
|
||||
//measure class is bit-packing enabled, no need to create wrapper for it
|
||||
static constexpr bool BitPackingEnabled = true;
|
||||
|
||||
using TConfig = Config;
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBytes(const T &) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
_bitsCount += details::BitsSize<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::BitsSize<T>::value);
|
||||
_bitsCount += bitsCount;
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBuffer(const T *, size_t count) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
_bitsCount += details::BitsSize<T>::value * count;
|
||||
}
|
||||
|
||||
void align() {
|
||||
auto _scratch = (_bitsCount % 8);
|
||||
_bitsCount += (8 - _scratch) % 8;
|
||||
}
|
||||
|
||||
void flush() {
|
||||
align();
|
||||
//flush sessions count
|
||||
if (_sessionsBytesCount > 0) {
|
||||
_bitsCount += (_sessionsBytesCount + 4) * 8;
|
||||
_sessionsBytesCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void beginSession() {
|
||||
|
||||
}
|
||||
|
||||
void endSession() {
|
||||
auto endPos = writtenBytesCount();
|
||||
details::writeSize(*this, endPos);
|
||||
auto sessionEndBytesCount = writtenBytesCount() - endPos;
|
||||
//remove written bytes, because we'll write them at the end
|
||||
_bitsCount -= sessionEndBytesCount * 8;
|
||||
_sessionsBytesCount += sessionEndBytesCount;
|
||||
}
|
||||
|
||||
//get size in bytes
|
||||
size_t writtenBytesCount() const {
|
||||
return _bitsCount / 8;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _bitsCount{};
|
||||
size_t _sessionsBytesCount{};
|
||||
};
|
||||
|
||||
//helper type for default config
|
||||
using MeasureSize = BasicMeasureSize<DefaultConfig>;
|
||||
|
||||
template <typename TWriter>
|
||||
class AdapterWriterBitPackingWrapper;
|
||||
|
||||
template<typename OutputAdapter, typename Config>
|
||||
struct AdapterWriter {
|
||||
//this is required by serializer
|
||||
static constexpr bool BitPackingEnabled = false;
|
||||
using TConfig = Config;
|
||||
using TValue = typename OutputAdapter::TValue;
|
||||
|
||||
static_assert(details::IsDefined<TValue>::value, "Please define adapter traits or include from <bitsery/traits/...>");
|
||||
|
||||
explicit AdapterWriter(OutputAdapter&& adapter)
|
||||
: _outputAdapter{std::move(adapter)}
|
||||
{
|
||||
}
|
||||
|
||||
AdapterWriter(const AdapterWriter &) = delete;
|
||||
|
||||
AdapterWriter &operator=(const AdapterWriter &) = delete;
|
||||
|
||||
//todo add conditional noexcept
|
||||
AdapterWriter(AdapterWriter &&) = default;
|
||||
|
||||
AdapterWriter &operator=(AdapterWriter &&) = default;
|
||||
|
||||
~AdapterWriter() {
|
||||
flush();
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBytes(const T &v) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
directWrite(&v, 1);
|
||||
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBuffer(const T *buf, size_t count) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
directWrite(buf, count);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void writeBits(const T &, size_t ) {
|
||||
static_assert(std::is_void<T>::value,
|
||||
"Bit-packing is not enabled.\nEnable by call to `enableBitPacking`) or create Serializer with bit packing enabled.");
|
||||
}
|
||||
|
||||
//to have the same interface as bitpackingwriter
|
||||
void align() {
|
||||
|
||||
}
|
||||
|
||||
void flush() {
|
||||
_session.flushSessions(*this);
|
||||
_outputAdapter.flush();
|
||||
}
|
||||
|
||||
size_t writtenBytesCount() const {
|
||||
return _outputAdapter.writtenBytesCount();
|
||||
}
|
||||
|
||||
void beginSession() {
|
||||
_session.begin(*this);
|
||||
}
|
||||
|
||||
void endSession() {
|
||||
_session.end(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class AdapterWriterBitPackingWrapper<AdapterWriter<OutputAdapter, Config>>;
|
||||
template<typename T>
|
||||
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) {
|
||||
const auto res = details::swap(v);
|
||||
_outputAdapter.write(reinterpret_cast<const TValue *>(&res), sizeof(T));
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void _directWriteSwapTag(const T *v, size_t count, std::false_type) {
|
||||
_outputAdapter.write(reinterpret_cast<const TValue *>(v), count * sizeof(T));
|
||||
}
|
||||
|
||||
OutputAdapter _outputAdapter;
|
||||
typename std::conditional<Config::BufferSessionsEnabled,
|
||||
session::SessionsWriter<AdapterWriter<OutputAdapter, Config >>,
|
||||
session::DisabledSessionsWriter<AdapterWriter<OutputAdapter, Config>>>::type
|
||||
_session{};
|
||||
};
|
||||
|
||||
//this class is used as wrapper for real AdapterWriter, it doesn't store writer itself just a reference
|
||||
template<typename TWriter>
|
||||
class AdapterWriterBitPackingWrapper {
|
||||
public:
|
||||
//this is required by serializer
|
||||
static constexpr bool BitPackingEnabled = true;
|
||||
using TConfig = typename TWriter::TConfig;
|
||||
|
||||
//make TValue unsigned for bit packing
|
||||
using UnsignedType = typename std::make_unsigned<typename TWriter::TValue>::type;
|
||||
using ScratchType = typename details::ScratchType<UnsignedType>::type;
|
||||
static_assert(details::IsDefined<ScratchType>::value, "Underlying adapter value type is not supported");
|
||||
|
||||
explicit AdapterWriterBitPackingWrapper(TWriter &writer)
|
||||
: _writer{writer}
|
||||
{
|
||||
}
|
||||
|
||||
AdapterWriterBitPackingWrapper(const AdapterWriterBitPackingWrapper&) = delete;
|
||||
AdapterWriterBitPackingWrapper& operator = (const AdapterWriterBitPackingWrapper&) = delete;
|
||||
|
||||
AdapterWriterBitPackingWrapper(AdapterWriterBitPackingWrapper&& ) noexcept = default;
|
||||
AdapterWriterBitPackingWrapper& operator = (AdapterWriterBitPackingWrapper&& ) noexcept = default;
|
||||
|
||||
~AdapterWriterBitPackingWrapper() {
|
||||
align();
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBytes(const T &v) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
|
||||
if (!_scratchBits) {
|
||||
_writer.template writeBytes<SIZE,T>(v);
|
||||
} else {
|
||||
using UT = typename std::make_unsigned<T>::type;
|
||||
writeBitsInternal(reinterpret_cast<const UT &>(v), details::BitsSize<T>::value);
|
||||
}
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBuffer(const T *buf, size_t count) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
if (!_scratchBits) {
|
||||
_writer.template writeBuffer<SIZE,T>(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)
|
||||
writeBitsInternal(reinterpret_cast<const UT &>(*it), details::BitsSize<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::BitsSize<T>::value);
|
||||
assert(v <= (bitsCount < 64
|
||||
? (1ULL << bitsCount) - 1
|
||||
: (1ULL << (bitsCount-1)) + ((1ULL << (bitsCount-1)) -1)));
|
||||
writeBitsInternal(v, bitsCount);
|
||||
}
|
||||
|
||||
void align() {
|
||||
writeBitsInternal(UnsignedType{}, (details::BitsSize<UnsignedType>::value - _scratchBits) % 8);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
align();
|
||||
_writer._session.flushSessions(_writer);
|
||||
}
|
||||
|
||||
size_t writtenBytesCount() const {
|
||||
return _writer.writtenBytesCount();
|
||||
}
|
||||
|
||||
void beginSession() {
|
||||
align();
|
||||
_writer._session.begin(_writer);
|
||||
}
|
||||
|
||||
void endSession() {
|
||||
align();
|
||||
_writer._session.end(_writer);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<typename T>
|
||||
void writeBitsInternal(const T &v, size_t size) {
|
||||
constexpr size_t valueSize = details::BitsSize<UnsignedType>::value;
|
||||
auto value = v;
|
||||
auto bitsLeft = size;
|
||||
while (bitsLeft > 0) {
|
||||
auto bits = (std::min)(bitsLeft, valueSize);
|
||||
_scratch |= static_cast<ScratchType>( value ) << _scratchBits;
|
||||
_scratchBits += bits;
|
||||
if (_scratchBits >= valueSize) {
|
||||
auto tmp = static_cast<UnsignedType>(_scratch & _MASK);
|
||||
_writer.template writeBytes<sizeof(UnsignedType), UnsignedType >(tmp);
|
||||
_scratch >>= valueSize;
|
||||
_scratchBits -= valueSize;
|
||||
|
||||
value >>= valueSize;
|
||||
}
|
||||
bitsLeft -= bits;
|
||||
}
|
||||
}
|
||||
|
||||
//overload for TValue, for better performance
|
||||
void writeBitsInternal(const UnsignedType &v, size_t size) {
|
||||
if (size > 0) {
|
||||
_scratch |= static_cast<ScratchType>( v ) << _scratchBits;
|
||||
_scratchBits += size;
|
||||
if (_scratchBits >= details::BitsSize<UnsignedType>::value) {
|
||||
auto tmp = static_cast<UnsignedType>(_scratch & _MASK);
|
||||
_writer.template writeBytes<sizeof(UnsignedType), UnsignedType>(tmp);
|
||||
_scratch >>= details::BitsSize<UnsignedType>::value;
|
||||
_scratchBits -= details::BitsSize<UnsignedType>::value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const UnsignedType _MASK = (std::numeric_limits<UnsignedType>::max)();
|
||||
ScratchType _scratch{};
|
||||
size_t _scratchBits{};
|
||||
TWriter& _writer;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif //BITSERY_ADAPTER_WRITER_H
|
||||
@@ -24,8 +24,8 @@
|
||||
#ifndef BITSERY_BITSERY_H
|
||||
#define BITSERY_BITSERY_H
|
||||
|
||||
#define BITSERY_MAJOR_VERSION 4
|
||||
#define BITSERY_MINOR_VERSION 6
|
||||
#define BITSERY_MAJOR_VERSION 5
|
||||
#define BITSERY_MINOR_VERSION 1
|
||||
#define BITSERY_PATCH_VERSION 0
|
||||
|
||||
#define BITSERY_QUOTE_MACRO(name) #name
|
||||
|
||||
@@ -21,62 +21,46 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_H
|
||||
#define BITSERY_FLEXIBLE_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_H
|
||||
#define BITSERY_BRIEF_SYNTAX_H
|
||||
|
||||
#include "details/serialization_common.h"
|
||||
#include "details/flexible_common.h"
|
||||
#include "details/brief_syntax_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
namespace flexible {
|
||||
|
||||
//overload when T is reference type
|
||||
template<typename S, typename T>
|
||||
void archiveProcessImpl(S &s, T &&head, std::true_type) {
|
||||
s.object(std::forward<T>(head));
|
||||
}
|
||||
|
||||
//overload when T is rvalue type, only allowable for behaviour modifying functions for deserializer
|
||||
template<typename S, typename T>
|
||||
void archiveProcessImpl(S &s, T &&head, std::false_type) {
|
||||
static_assert(std::is_base_of<ArchiveWrapperFnc, T>::value,
|
||||
"\nOnly archive behaviour modifying functions can be passed by rvalue to deserializer\n");
|
||||
serialize(s, head);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//define function that enables s.archive(....) usage
|
||||
//define function that enables s(....) usage
|
||||
template<typename S, typename T>
|
||||
void archiveProcess(S &s, T &&head) {
|
||||
flexible::archiveProcessImpl(s, std::forward<T>(head), std::is_reference<T>{});
|
||||
void processBriefSyntax(S& s, T&& head) {
|
||||
static_assert(std::is_lvalue_reference<T>::value || std::is_base_of<brief_syntax::ModFnc, T>::value,
|
||||
"Argument must be either lvalue or subclass of brief_syntax::ModFnc");
|
||||
s.object(head);
|
||||
}
|
||||
|
||||
//wrapper functions that enables to serialize as container or string
|
||||
template<typename T, size_t N>
|
||||
flexible::CArray<T, N, true> asText(T (&str)[N]) {
|
||||
brief_syntax::CArray<T, N, true> asText(T (& str)[N]) {
|
||||
return {str};
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
flexible::CArray<T, N, false> asContainer(T (&obj)[N]) {
|
||||
brief_syntax::CArray<T, N, false> asContainer(T (& obj)[N]) {
|
||||
return {obj};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
flexible::MaxSize<T> maxSize(T& obj, size_t max) {
|
||||
template<typename T>
|
||||
brief_syntax::MaxSize<T> maxSize(T& obj, size_t max) {
|
||||
return {obj, max};
|
||||
}
|
||||
|
||||
//define serialize function for fundamental types
|
||||
template<typename S>
|
||||
void serialize(S &s, bool &v) {
|
||||
void serialize(S& s, bool& v) {
|
||||
s.boolValue(v);
|
||||
}
|
||||
|
||||
template<typename S, typename T, typename std::enable_if<details::IsFundamentalType<T>::value>::type * = nullptr>
|
||||
void serialize(S &s, T &v) {
|
||||
void serialize(S& s, T& v) {
|
||||
s.template value<sizeof(T)>(v);
|
||||
}
|
||||
|
||||
@@ -84,18 +68,18 @@ namespace bitsery {
|
||||
|
||||
//if array is integral type, specify explicitly how to process: as text or container
|
||||
template<typename S, typename T, size_t N, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
|
||||
void serialize(S &, T (&)[N]) {
|
||||
void serialize(S&, T (&)[N]) {
|
||||
static_assert(N == 0,
|
||||
"\nPlease use 'asText(obj)' or 'asContainer(obj)' when using c-style array with integral types\n");
|
||||
}
|
||||
|
||||
template<typename S, typename T, size_t N, typename std::enable_if<!std::is_integral<T>::value>::type * = nullptr>
|
||||
void serialize(S &s, T (&obj)[N]) {
|
||||
flexible::processContainer(s, obj);
|
||||
void serialize(S& s, T (& obj)[N]) {
|
||||
brief_syntax::processContainer(s, obj);
|
||||
}
|
||||
|
||||
//this is a helper class that enforce fundamental type sizes, when used on multiple platforms
|
||||
template <size_t TShort, size_t TInt, size_t TLong, size_t TLongLong>
|
||||
template<size_t TShort, size_t TInt, size_t TLong, size_t TLongLong>
|
||||
void assertFundamentalTypeSizes() {
|
||||
//http://en.cppreference.com/w/cpp/language/types
|
||||
static_assert(sizeof(short) == TShort, "");
|
||||
@@ -107,4 +91,4 @@ namespace bitsery {
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_H
|
||||
@@ -21,17 +21,17 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_ARRAY_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_ARRAY_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_ARRAY_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_ARRAY_H
|
||||
|
||||
#include "../traits/array.h"
|
||||
#include "../details/flexible_common.h"
|
||||
#include "../details/brief_syntax_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename T, size_t N>
|
||||
void serialize(S &s, std::array<T, N> &obj) {
|
||||
flexible::processContainer(s, obj);
|
||||
brief_syntax::processContainer(s, obj);
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_ARRAY_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_ARRAY_H
|
||||
35
include/bitsery/brief_syntax/atomic.h
Normal file
35
include/bitsery/brief_syntax/atomic.h
Normal file
@@ -0,0 +1,35 @@
|
||||
//MIT License
|
||||
//
|
||||
//Copyright (c) 2020 Nick Renieris
|
||||
//
|
||||
//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_BRIEF_SYNTAX_TYPE_STD_ATOMIC_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_ATOMIC_H
|
||||
|
||||
#include "../ext/std_atomic.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename T>
|
||||
void serialize(S &s, std::atomic<T> &obj) {
|
||||
s.template ext<sizeof(T)>(obj, ext::StdAtomic{});
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_ATOMIC_H
|
||||
@@ -20,8 +20,8 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_CHRONO_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_CHRONO_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_CHRONO_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_CHRONO_H
|
||||
|
||||
#include "../ext/std_chrono.h"
|
||||
|
||||
@@ -37,4 +37,4 @@ namespace bitsery {
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_CHRONO_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_CHRONO_H
|
||||
@@ -21,17 +21,17 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_DEQUE_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_DEQUE_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_DEQUE_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_DEQUE_H
|
||||
|
||||
#include "../traits/deque.h"
|
||||
#include "../details/flexible_common.h"
|
||||
#include "../details/brief_syntax_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename T, typename Allocator>
|
||||
void serialize(S &s, std::deque<T, Allocator> &obj) {
|
||||
flexible::processContainer(s, obj);
|
||||
brief_syntax::processContainer(s, obj);
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_DEQUE_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_DEQUE_H
|
||||
@@ -21,17 +21,17 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_FORWARD_LIST_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_FORWARD_LIST_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_FORWARD_LIST_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_FORWARD_LIST_H
|
||||
|
||||
#include "../traits/forward_list.h"
|
||||
#include "../details/flexible_common.h"
|
||||
#include "../details/brief_syntax_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename T, typename Allocator>
|
||||
void serialize(S &s, std::forward_list<T, Allocator> &obj) {
|
||||
flexible::processContainer(s, obj);
|
||||
brief_syntax::processContainer(s, obj);
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_FORWARD_LIST_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_FORWARD_LIST_H
|
||||
@@ -21,17 +21,17 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_LIST_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_LIST_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_LIST_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_LIST_H
|
||||
|
||||
#include "../traits/list.h"
|
||||
#include "../details/flexible_common.h"
|
||||
#include "../details/brief_syntax_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename T, typename Allocator>
|
||||
void serialize(S &s, std::list<T, Allocator> &obj) {
|
||||
flexible::processContainer(s, obj);
|
||||
brief_syntax::processContainer(s, obj);
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_LIST_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_LIST_H
|
||||
@@ -21,8 +21,8 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_MAP_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_MAP_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_MAP_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_MAP_H
|
||||
|
||||
#include <map>
|
||||
#include "../ext/std_map.h"
|
||||
@@ -31,7 +31,7 @@ namespace bitsery {
|
||||
template<typename S, typename Key, typename T, typename Compare, typename Allocator>
|
||||
void serialize(S &s, std::map<Key, T, Compare, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
s.ext(obj, ext::StdMap{maxSize},
|
||||
[&s](Key& key, T& value) {
|
||||
[](S& s, Key& key, T& value) {
|
||||
s.object(key);
|
||||
s.object(value);
|
||||
});
|
||||
@@ -40,11 +40,11 @@ namespace bitsery {
|
||||
template<typename S, typename Key, typename T, typename Compare, typename Allocator>
|
||||
void serialize(S &s, std::multimap<Key, T, Compare, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
s.ext(obj, ext::StdMap{maxSize},
|
||||
[&s](Key& key, T& value) {
|
||||
[](S& s, Key& key, T& value) {
|
||||
s.object(key);
|
||||
s.object(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_MAP_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_MAP_H
|
||||
@@ -20,8 +20,8 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_MEMORY_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_MEMORY_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_MEMORY_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_MEMORY_H
|
||||
|
||||
#include "../ext/std_smart_ptr.h"
|
||||
|
||||
@@ -42,4 +42,4 @@ namespace bitsery {
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_MEMORY_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_MEMORY_H
|
||||
@@ -21,8 +21,8 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_QUEUE_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_QUEUE_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_QUEUE_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_QUEUE_H
|
||||
|
||||
#include "../ext/std_queue.h"
|
||||
|
||||
@@ -39,4 +39,4 @@ namespace bitsery {
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_QUEUE_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_QUEUE_H
|
||||
@@ -21,8 +21,8 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_SET_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_SET_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_SET_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_SET_H
|
||||
|
||||
#include <set>
|
||||
#include "../ext/std_set.h"
|
||||
@@ -40,4 +40,4 @@ namespace bitsery {
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_SET_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_SET_H
|
||||
@@ -21,8 +21,8 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_STACK_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_STACK_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_STACK_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_STACK_H
|
||||
|
||||
#include "../ext/std_stack.h"
|
||||
|
||||
@@ -33,4 +33,4 @@ namespace bitsery {
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_STACK_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_STACK_H
|
||||
@@ -21,17 +21,17 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_STRING_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_STRING_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_STRING_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_STRING_H
|
||||
|
||||
#include "../traits/string.h"
|
||||
#include "../details/flexible_common.h"
|
||||
#include "../details/brief_syntax_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename CharT, typename Traits, typename Allocator>
|
||||
void serialize(S &s, std::basic_string<CharT, Traits, Allocator> &str) {
|
||||
flexible::processContainer(s, str);
|
||||
brief_syntax::processContainer(s, str);
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_STRING_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_STRING_H
|
||||
@@ -20,8 +20,8 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_TUPLE_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_TUPLE_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_TUPLE_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_TUPLE_H
|
||||
|
||||
#include "../ext/std_tuple.h"
|
||||
|
||||
@@ -32,4 +32,4 @@ namespace bitsery {
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_TUPLE_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_TUPLE_H
|
||||
@@ -21,8 +21,8 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_UNORDERED_MAP_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_UNORDERED_MAP_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_UNORDERED_MAP_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_UNORDERED_MAP_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include "../ext/std_map.h"
|
||||
@@ -31,7 +31,7 @@ namespace bitsery {
|
||||
template<typename S, typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
|
||||
void serialize(S &s, std::unordered_map<Key, T, Hash, KeyEqual, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
s.ext(obj, ext::StdMap{maxSize},
|
||||
[&s](Key& key, T& value) {
|
||||
[](S& s, Key& key, T& value) {
|
||||
s.object(key);
|
||||
s.object(value);
|
||||
});
|
||||
@@ -40,7 +40,7 @@ namespace bitsery {
|
||||
template<typename S, typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
|
||||
void serialize(S &s, std::unordered_multimap<Key, T, Hash, KeyEqual, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
s.ext(obj, ext::StdMap{maxSize},
|
||||
[&s](Key& key, T& value) {
|
||||
[](S& s, Key& key, T& value) {
|
||||
s.object(key);
|
||||
s.object(value);
|
||||
});
|
||||
@@ -48,4 +48,4 @@ namespace bitsery {
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_UNORDERED_MAP_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_UNORDERED_MAP_H
|
||||
@@ -21,8 +21,8 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_UNORDERED_SET_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_UNORDERED_SET_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_UNORDERED_SET_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_UNORDERED_SET_H
|
||||
|
||||
#include <unordered_set>
|
||||
#include "../ext/std_set.h"
|
||||
@@ -40,4 +40,4 @@ namespace bitsery {
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_UNORDERED_SET_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_UNORDERED_SET_H
|
||||
@@ -20,8 +20,8 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_VARIANT_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_VARIANT_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_VARIANT_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_VARIANT_H
|
||||
|
||||
#include "../ext/std_variant.h"
|
||||
|
||||
@@ -32,4 +32,4 @@ namespace bitsery {
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_VARIANT_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_VARIANT_H
|
||||
@@ -21,17 +21,17 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_VECTOR_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_VECTOR_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_VECTOR_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_VECTOR_H
|
||||
|
||||
#include "../traits/vector.h"
|
||||
#include "../details/flexible_common.h"
|
||||
#include "../details/brief_syntax_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename T, typename Allocator>
|
||||
void serialize(S &s, std::vector<T, Allocator> &obj) {
|
||||
flexible::processContainer(s, obj);
|
||||
brief_syntax::processContainer(s, obj);
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_VECTOR_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_VECTOR_H
|
||||
@@ -36,17 +36,15 @@ namespace bitsery {
|
||||
BigEndian
|
||||
};
|
||||
|
||||
//default configuration for buffer writing/reading operations
|
||||
// default configuration for serialization and deserialization
|
||||
struct DefaultConfig {
|
||||
//data will be stored in little endian, independant of host.
|
||||
static constexpr EndiannessType NetworkEndianness = EndiannessType::LittleEndian;
|
||||
//this functionality allows to support backward/forward compatibility
|
||||
//however reading from streams is not supported, because this functionality requires random access to buffer.
|
||||
static constexpr bool BufferSessionsEnabled = false;
|
||||
//list of contexts that will be instanciated internally within serializer/deserializer.
|
||||
//contexts must be default constructable.
|
||||
//internal context has priority, if external context with the same type exists.
|
||||
using InternalContext = std::tuple<>;
|
||||
// defines endianness of data that is read from input adapter and written to output adapter.
|
||||
static constexpr EndiannessType Endianness = EndiannessType::LittleEndian;
|
||||
// these flags allow to improve deserialization performance if data is trusted
|
||||
// enables/disables checks for buffer end or stream read errors in input adapter
|
||||
static constexpr bool CheckAdapterErrors = true;
|
||||
// enables/disables checks for other errors that can significantly affect performance
|
||||
static constexpr bool CheckDataErrors = true;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -25,57 +25,153 @@
|
||||
#define BITSERY_DESERIALIZER_H
|
||||
|
||||
#include "details/serialization_common.h"
|
||||
#include "adapter_reader.h"
|
||||
#include "details/adapter_common.h"
|
||||
#include <utility>
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
namespace details {
|
||||
template<typename TAdapter>
|
||||
class InputAdapterBitPackingWrapper {
|
||||
public:
|
||||
|
||||
template<typename TAdapterReader, typename TContext = void>
|
||||
class BasicDeserializer {
|
||||
static constexpr bool BitPackingEnabled = true;
|
||||
using TConfig = typename TAdapter::TConfig;
|
||||
using TValue = typename TAdapter::TValue;
|
||||
|
||||
InputAdapterBitPackingWrapper(TAdapter& adapter)
|
||||
: _wrapped{adapter}
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
~InputAdapterBitPackingWrapper() {
|
||||
align();
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void readBytes(T &v) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
using UT = typename std::make_unsigned<T>::type;
|
||||
if (!m_scratchBits)
|
||||
this->_wrapped.template readBytes<SIZE,T>(v);
|
||||
else
|
||||
readBits(reinterpret_cast<UT &>(v), details::BitsSize<T>::value);
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void readBuffer(T *buf, size_t count) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
|
||||
if (!m_scratchBits) {
|
||||
this->_wrapped.template readBuffer<SIZE,T>(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::BitsSize<T>::value);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void readBits(T &v, size_t bitsCount) {
|
||||
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
|
||||
readBitsInternal(v, bitsCount);
|
||||
}
|
||||
|
||||
void align() {
|
||||
if (m_scratchBits) {
|
||||
ScratchType tmp{};
|
||||
readBitsInternal(tmp, m_scratchBits);
|
||||
handleAlignErrors(tmp, std::integral_constant<bool, TConfig::CheckDataErrors>{});
|
||||
}
|
||||
}
|
||||
|
||||
void currentReadPos(size_t pos) {
|
||||
align();
|
||||
this->_wrapped.currentReadPos(pos);
|
||||
}
|
||||
|
||||
size_t currentReadPos() const {
|
||||
return this->_wrapped.currentReadPos();
|
||||
}
|
||||
|
||||
void currentReadEndPos(size_t pos) {
|
||||
this->_wrapped.currentReadEndPos(pos);
|
||||
}
|
||||
|
||||
size_t currentReadEndPos() const {
|
||||
return this->_wrapped.currentReadEndPos();
|
||||
}
|
||||
|
||||
bool isCompletedSuccessfully() const {
|
||||
return this->_wrapped.isCompletedSuccessfully();
|
||||
}
|
||||
|
||||
ReaderError error() const {
|
||||
return this->_wrapped.error();
|
||||
}
|
||||
|
||||
void error(ReaderError error) {
|
||||
this->_wrapped.error(error);
|
||||
}
|
||||
|
||||
private:
|
||||
TAdapter& _wrapped;
|
||||
using UnsignedValue = typename std::make_unsigned<typename TAdapter::TValue>::type;
|
||||
using ScratchType = typename details::ScratchType<UnsignedValue>::type;
|
||||
|
||||
ScratchType m_scratch{};
|
||||
size_t m_scratchBits{};
|
||||
|
||||
template<typename T>
|
||||
void readBitsInternal(T &v, size_t size) {
|
||||
auto bitsLeft = size;
|
||||
using TFast = typename FastType<T>::type;
|
||||
TFast res{};
|
||||
while (bitsLeft > 0) {
|
||||
auto bits = (std::min)(bitsLeft, details::BitsSize<UnsignedValue>::value);
|
||||
if (m_scratchBits < bits) {
|
||||
UnsignedValue tmp;
|
||||
this->_wrapped.template readBytes<sizeof(UnsignedValue), UnsignedValue>(tmp);
|
||||
m_scratch |= static_cast<ScratchType>(tmp) << m_scratchBits;
|
||||
m_scratchBits += details::BitsSize<UnsignedValue>::value;
|
||||
}
|
||||
auto shiftedRes =
|
||||
static_cast<T>(m_scratch & ((static_cast<ScratchType>(1) << bits) - 1)) << (size - bitsLeft);
|
||||
res = static_cast<TFast>(res | static_cast<TFast>(shiftedRes));
|
||||
m_scratch >>= bits;
|
||||
m_scratchBits -= bits;
|
||||
bitsLeft -= bits;
|
||||
}
|
||||
v = static_cast<T>(res);
|
||||
}
|
||||
|
||||
void handleAlignErrors(ScratchType value, std::true_type) {
|
||||
if (value)
|
||||
error(ReaderError::InvalidData);
|
||||
}
|
||||
|
||||
void handleAlignErrors(ScratchType, std::false_type) {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<typename TInputAdapter, typename TContext = void>
|
||||
class Deserializer: public details::AdapterAndContextRef<TInputAdapter, TContext> {
|
||||
public:
|
||||
//this is used by AdapterAccess class
|
||||
using TReader = TAdapterReader;
|
||||
//helper type, that always returns bit-packing enabled type, useful inside serialize function when enabling bitpacking
|
||||
using BPEnabledType = BasicDeserializer<typename std::conditional<TAdapterReader::BitPackingEnabled,
|
||||
TAdapterReader, AdapterReaderBitPackingWrapper<TAdapterReader>>::type, TContext>;
|
||||
//helper type, that always returns bit-packing enabled type, useful inside deserialize function when enabling bitpacking
|
||||
using BPEnabledType = Deserializer<typename std::conditional<TInputAdapter::BitPackingEnabled,
|
||||
TInputAdapter,
|
||||
details::InputAdapterBitPackingWrapper<TInputAdapter>>::type, TContext>;
|
||||
using TConfig = typename TInputAdapter::TConfig;
|
||||
|
||||
static_assert(details::IsSpecializationOf<typename TReader::TConfig::InternalContext, std::tuple>::value,
|
||||
"Config::InternalContext must be std::tuple");
|
||||
|
||||
template <typename ReaderParam>
|
||||
explicit BasicDeserializer(ReaderParam&& r, TContext* context = nullptr)
|
||||
: _reader{std::forward<ReaderParam>(r)},
|
||||
_context{context},
|
||||
_internalContext{}
|
||||
{
|
||||
}
|
||||
|
||||
//copying disabled
|
||||
BasicDeserializer(const BasicDeserializer&) = delete;
|
||||
BasicDeserializer& operator = (const BasicDeserializer&) = delete;
|
||||
|
||||
//move enabled
|
||||
BasicDeserializer(BasicDeserializer&& ) = default;
|
||||
BasicDeserializer& operator = (BasicDeserializer&& ) = default;
|
||||
|
||||
/*
|
||||
* get serialization context.
|
||||
* this is optional, but might be required for some specific deserialization flows.
|
||||
*/
|
||||
TContext* context() {
|
||||
return _context;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* context(){
|
||||
return details::getContext<T>(_context, _internalContext);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* contextOrNull(){
|
||||
return details::getContextIfTypeExists<T>(_context, _internalContext);
|
||||
}
|
||||
using details::AdapterAndContextRef<TInputAdapter, TContext>::AdapterAndContextRef;
|
||||
|
||||
/*
|
||||
* object function
|
||||
@@ -83,33 +179,33 @@ namespace bitsery {
|
||||
|
||||
template<typename T>
|
||||
void object(T &&obj) {
|
||||
details::SerializeFunction<BasicDeserializer, T>::invoke(*this, std::forward<T>(obj));
|
||||
details::SerializeFunction<Deserializer, T>::invoke(*this, std::forward<T>(obj));
|
||||
}
|
||||
|
||||
template<typename T, typename Fnc>
|
||||
void object(T &&obj, Fnc &&fnc) {
|
||||
fnc(std::forward<T>(obj));
|
||||
fnc(*this, std::forward<T>(obj));
|
||||
}
|
||||
|
||||
/*
|
||||
* functionality, that enables simpler serialization syntax, by including additional header
|
||||
*/
|
||||
template<typename T, typename ... TArgs>
|
||||
void archive(T &&head, TArgs &&... tail) {
|
||||
//serialize object
|
||||
details::ArchiveFunction<BasicDeserializer, T>::invoke(*this, std::forward<T>(head));
|
||||
//expand other elements
|
||||
archive(std::forward<TArgs>(tail)...);
|
||||
|
||||
template <typename... TArgs>
|
||||
Deserializer &operator()(TArgs &&... args) {
|
||||
archive(std::forward<TArgs>(args)...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* value
|
||||
*/
|
||||
|
||||
template<size_t VSIZE, typename T, typename std::enable_if<details::IsFundamentalType<T>::value>::type * = nullptr>
|
||||
template<size_t VSIZE, typename T>
|
||||
void value(T &v) {
|
||||
static_assert(details::IsFundamentalType<T>::value, "Value must be integral, float or enum type.");
|
||||
using TValue = typename details::IntegralFromFundamental<T>::TValue;
|
||||
_reader.template readBytes<VSIZE>(reinterpret_cast<TValue &>(v));
|
||||
this->_adapter.template readBytes<VSIZE>(reinterpret_cast<TValue &>(v));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -117,7 +213,7 @@ namespace bitsery {
|
||||
*/
|
||||
template <typename Fnc>
|
||||
void enableBitPacking(Fnc&& fnc) {
|
||||
procEnableBitPacking(std::forward<Fnc>(fnc), std::integral_constant<bool, TAdapterReader::BitPackingEnabled>{});
|
||||
procEnableBitPacking(std::forward<Fnc>(fnc), std::integral_constant<bool, TInputAdapter::BitPackingEnabled>{});
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -129,7 +225,7 @@ namespace bitsery {
|
||||
static_assert(details::IsExtensionTraitsDefined<Ext, T>::value, "Please define ExtensionTraits");
|
||||
static_assert(traits::ExtensionTraits<Ext,T>::SupportLambdaOverload,
|
||||
"extension doesn't support overload with lambda");
|
||||
extension.deserialize(*this, _reader, obj, std::forward<Fnc>(fnc));
|
||||
extension.deserialize(*this, obj, std::forward<Fnc>(fnc));
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, typename Ext>
|
||||
@@ -139,7 +235,7 @@ namespace bitsery {
|
||||
"extension doesn't support overload with `value<N>`");
|
||||
using ExtVType = typename traits::ExtensionTraits<Ext, 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);});
|
||||
extension.deserialize(*this, obj, [](Deserializer& s, VType &v) { s.value<VSIZE>(v);});
|
||||
}
|
||||
|
||||
template<typename T, typename Ext>
|
||||
@@ -149,14 +245,16 @@ namespace bitsery {
|
||||
"extension doesn't support overload with `object`");
|
||||
using ExtVType = typename traits::ExtensionTraits<Ext, 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); });
|
||||
extension.deserialize(*this, obj, [](Deserializer& s, VType &v) { s.object(v); });
|
||||
}
|
||||
|
||||
/*
|
||||
* boolValue
|
||||
*/
|
||||
void boolValue(bool &v) {
|
||||
procBoolValue(v, std::integral_constant<bool, TAdapterReader::BitPackingEnabled>{});
|
||||
procBoolValue(v,
|
||||
std::integral_constant<bool, TInputAdapter::BitPackingEnabled>{},
|
||||
std::integral_constant<bool, TInputAdapter::TConfig::CheckDataErrors>{});
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -170,7 +268,7 @@ namespace bitsery {
|
||||
static_assert(traits::ContainerTraits<T>::isResizable,
|
||||
"use text(T&) overload without `maxSize` for static containers");
|
||||
size_t length;
|
||||
details::readSize(_reader, length, maxSize);
|
||||
readSize(length, maxSize);
|
||||
traits::ContainerTraits<T>::resize(str, length + (traits::TextTraits<T>::addNUL ? 1u : 0u));
|
||||
procText<VSIZE>(str, length);
|
||||
}
|
||||
@@ -182,7 +280,7 @@ namespace bitsery {
|
||||
static_assert(!traits::ContainerTraits<T>::isResizable,
|
||||
"use text(T&, size_t) overload with `maxSize` for dynamic containers");
|
||||
size_t length;
|
||||
details::readSize(_reader, length, traits::ContainerTraits<T>::size(str));
|
||||
readSize(length, traits::ContainerTraits<T>::size(str));
|
||||
procText<VSIZE>(str, length);
|
||||
}
|
||||
|
||||
@@ -199,7 +297,7 @@ namespace bitsery {
|
||||
static_assert(traits::ContainerTraits<T>::isResizable,
|
||||
"use container(T&) overload without `maxSize` for static containers");
|
||||
size_t size{};
|
||||
details::readSize(_reader, size, maxSize);
|
||||
readSize(size, maxSize);
|
||||
traits::ContainerTraits<T>::resize(obj, size);
|
||||
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
|
||||
}
|
||||
@@ -211,7 +309,7 @@ namespace bitsery {
|
||||
static_assert(traits::ContainerTraits<T>::isResizable,
|
||||
"use container(T&) overload without `maxSize` for static containers");
|
||||
size_t size{};
|
||||
details::readSize(_reader, size, maxSize);
|
||||
readSize(size, maxSize);
|
||||
traits::ContainerTraits<T>::resize(obj, size);
|
||||
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::integral_constant<bool, traits::ContainerTraits<T>::isContiguous>{});
|
||||
}
|
||||
@@ -223,7 +321,7 @@ namespace bitsery {
|
||||
static_assert(traits::ContainerTraits<T>::isResizable,
|
||||
"use container(T&) overload without `maxSize` for static containers");
|
||||
size_t size{};
|
||||
details::readSize(_reader, size, maxSize);
|
||||
readSize(size, maxSize);
|
||||
traits::ContainerTraits<T>::resize(obj, size);
|
||||
procContainer(std::begin(obj), std::end(obj));
|
||||
}
|
||||
@@ -257,10 +355,6 @@ namespace bitsery {
|
||||
procContainer(std::begin(obj), std::end(obj));
|
||||
}
|
||||
|
||||
void align() {
|
||||
_reader.align();
|
||||
}
|
||||
|
||||
//overloads for functions with explicit type size
|
||||
|
||||
template<typename T>
|
||||
@@ -330,12 +424,11 @@ namespace bitsery {
|
||||
void container8b(T &&obj) { container<8>(std::forward<T>(obj)); }
|
||||
|
||||
private:
|
||||
friend AdapterAccess;
|
||||
|
||||
TAdapterReader _reader;
|
||||
TContext* _context;
|
||||
typename TReader::TConfig::InternalContext _internalContext;
|
||||
|
||||
void readSize(size_t& size, size_t maxSize) {
|
||||
details::readSize(this->_adapter, size, maxSize,
|
||||
std::integral_constant<bool, TInputAdapter::TConfig::CheckDataErrors>{});
|
||||
}
|
||||
|
||||
//process value types
|
||||
//false_type means that we must process all elements individually
|
||||
@@ -351,15 +444,17 @@ namespace bitsery {
|
||||
void procContainer(It first, It last, std::true_type) {
|
||||
using TValue = typename std::decay<decltype(*first)>::type;
|
||||
using TIntegral = typename details::IntegralFromFundamental<TValue>::TValue;
|
||||
if (first != last)
|
||||
_reader.template readBuffer<VSIZE>(reinterpret_cast<TIntegral*>(&(*first)), std::distance(first, last));
|
||||
if (first != last){
|
||||
const auto distance = std::distance(first, last);
|
||||
this->_adapter.template readBuffer<VSIZE>(reinterpret_cast<TIntegral*>(&(*first)), static_cast<size_t>(distance));
|
||||
}
|
||||
}
|
||||
|
||||
//process by calling functions
|
||||
template<typename It, typename Fnc>
|
||||
void procContainer(It first, It last, Fnc fnc) {
|
||||
for (; first != last; ++first)
|
||||
fnc(*first);
|
||||
fnc(*this, *first);
|
||||
}
|
||||
|
||||
//process object types
|
||||
@@ -373,7 +468,8 @@ namespace bitsery {
|
||||
void procText(T& str, size_t length) {
|
||||
auto begin = std::begin(str);
|
||||
//end of string, not end of container
|
||||
auto end = std::next(begin, length);
|
||||
using diff_t = typename std::iterator_traits<decltype(begin)>::difference_type;
|
||||
auto end = std::next(begin, static_cast<diff_t>(length));
|
||||
procContainer<VSIZE>(begin, end, std::integral_constant<bool, traits::ContainerTraits<T>::isContiguous>{});
|
||||
//null terminated character at the end
|
||||
if (traits::TextTraits<T>::addNUL)
|
||||
@@ -381,20 +477,26 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
//proc bool writing bit or byte, depending on if BitPackingEnabled or not
|
||||
void procBoolValue(bool &v, std::true_type) {
|
||||
template <typename HandleDataErrors>
|
||||
void procBoolValue(bool &v, std::true_type, HandleDataErrors) {
|
||||
uint8_t tmp{};
|
||||
_reader.readBits(tmp, 1);
|
||||
this->_adapter.readBits(tmp, 1);
|
||||
v = tmp == 1;
|
||||
}
|
||||
|
||||
void procBoolValue(bool &v, std::false_type) {
|
||||
unsigned char tmp;
|
||||
_reader.template readBytes<1>(tmp);
|
||||
void procBoolValue(bool &v, std::false_type, std::true_type) {
|
||||
uint8_t tmp{};
|
||||
this->_adapter.template readBytes<1>(tmp);
|
||||
if (tmp > 1)
|
||||
_reader.setError(ReaderError::InvalidData);
|
||||
this->_adapter.error(ReaderError::InvalidData);
|
||||
v = tmp == 1;
|
||||
}
|
||||
|
||||
void procBoolValue(bool &v, std::false_type, std::false_type) {
|
||||
uint8_t tmp{};
|
||||
this->_adapter.template readBytes<1>(tmp);
|
||||
v = tmp > 0;
|
||||
}
|
||||
|
||||
//enable bit-packing or do nothing if it is already enabled
|
||||
template <typename Fnc>
|
||||
@@ -404,11 +506,20 @@ namespace bitsery {
|
||||
|
||||
template <typename Fnc>
|
||||
void procEnableBitPacking(const Fnc& fnc, std::false_type) {
|
||||
//create serializer using bitpacking wrapper
|
||||
BPEnabledType tmp(_reader, _context);
|
||||
fnc(tmp);
|
||||
//create deserializer using bitpacking wrapper
|
||||
auto des = createWithContext(std::integral_constant<bool, Deserializer::HasContext>{});
|
||||
fnc(des);
|
||||
}
|
||||
|
||||
BPEnabledType createWithContext(std::true_type) {
|
||||
return BPEnabledType{this->_context, this->_adapter};
|
||||
}
|
||||
|
||||
BPEnabledType createWithContext(std::false_type) {
|
||||
return BPEnabledType{this->_adapter};
|
||||
}
|
||||
|
||||
|
||||
//these are dummy functions for extensions that have TValue = void
|
||||
void object(details::DummyType&) {
|
||||
|
||||
@@ -419,6 +530,14 @@ namespace bitsery {
|
||||
|
||||
}
|
||||
|
||||
template<typename T, typename ... TArgs>
|
||||
void archive(T &&head, TArgs &&... tail) {
|
||||
//serialize object
|
||||
details::BriefSyntaxFunction<Deserializer, T>::invoke(*this, std::forward<T>(head));
|
||||
//expand other elements
|
||||
archive(std::forward<TArgs>(tail)...);
|
||||
}
|
||||
|
||||
//dummy function, that stops archive variadic arguments expansion
|
||||
void archive() {
|
||||
}
|
||||
@@ -426,16 +545,20 @@ namespace bitsery {
|
||||
};
|
||||
|
||||
//helper type
|
||||
template <typename Adapter>
|
||||
using Deserializer = BasicDeserializer<AdapterReader<Adapter, DefaultConfig>>;
|
||||
|
||||
//helper function that set ups all the basic steps and after deserialziation returns status
|
||||
template <typename Adapter, typename T>
|
||||
std::pair<ReaderError, bool> quickDeserialization(Adapter adapter, T& value) {
|
||||
Deserializer<Adapter> des{std::move(adapter)};
|
||||
template <typename InputAdapter, typename T>
|
||||
std::pair<ReaderError, bool> quickDeserialization(InputAdapter adapter, T& value) {
|
||||
Deserializer<InputAdapter> des{std::move(adapter)};
|
||||
des.object(value);
|
||||
auto& r = AdapterAccess::getReader(des);
|
||||
return {r.error(), r.isCompletedSuccessfully()};
|
||||
return {des.adapter().error(), des.adapter().isCompletedSuccessfully()};
|
||||
}
|
||||
|
||||
template <typename Context , typename InputAdapter, typename T>
|
||||
std::pair<ReaderError, bool> quickDeserialization(Context& ctx, InputAdapter adapter, T& value) {
|
||||
Deserializer<InputAdapter, Context> des{ctx, std::move(adapter)};
|
||||
des.object(value);
|
||||
return {des.adapter().error(), des.adapter().isCompletedSuccessfully()};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,19 +30,77 @@
|
||||
#include <stack>
|
||||
#include <cstring>
|
||||
#include <climits>
|
||||
#include "adapter_utils.h"
|
||||
#include "not_defined_type.h"
|
||||
|
||||
#include "../common.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
enum class ReaderError {
|
||||
NoError,
|
||||
ReadingError, // this might be used with stream adapter
|
||||
DataOverflow,
|
||||
InvalidData,
|
||||
InvalidPointer
|
||||
};
|
||||
|
||||
namespace details {
|
||||
|
||||
template<typename T>
|
||||
struct BitsSize:public std::integral_constant<size_t, sizeof(T) * 8> {
|
||||
static_assert(CHAR_BIT == 8, "only support systems with byte size of 8 bits");
|
||||
};
|
||||
/**
|
||||
* size read/write functions
|
||||
*/
|
||||
template <typename Reader, typename TCheckMaxSize>
|
||||
void readSize(Reader& r, size_t& size, size_t maxSize, TCheckMaxSize) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
handleReadMaxSize(r, size, maxSize, TCheckMaxSize{});
|
||||
}
|
||||
|
||||
template <typename Reader>
|
||||
void handleReadMaxSize(Reader& r, size_t& size, size_t maxSize, std::true_type) {
|
||||
if (size > maxSize) {
|
||||
r.error(ReaderError::InvalidData);
|
||||
size = {};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Reader>
|
||||
void handleReadMaxSize(Reader&, size_t&, size_t, std::false_type) {
|
||||
}
|
||||
|
||||
template <typename Writer>
|
||||
void writeSize(Writer& 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* swap utils
|
||||
*/
|
||||
|
||||
//add swap functions to class, to avoid compilation warning about unused functions
|
||||
struct SwapImpl {
|
||||
@@ -66,7 +124,7 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
static uint16_t exec(uint16_t value) {
|
||||
return (value & 0x00ff) << 8 | (value & 0xff00) >> 8;
|
||||
return static_cast<uint16_t>((value & 0x00ff) << 8 | (value & 0xff00) >> 8);
|
||||
}
|
||||
|
||||
static uint8_t exec(uint8_t value) {
|
||||
@@ -80,14 +138,20 @@ namespace bitsery {
|
||||
using UT = typename std::conditional<TSize == 1, uint8_t,
|
||||
typename std::conditional<TSize == 2, uint16_t,
|
||||
typename std::conditional<TSize == 4, uint32_t, uint64_t>::type>::type>::type;
|
||||
return SwapImpl::exec(static_cast<UT>(value));
|
||||
return static_cast<TValue>(SwapImpl::exec(static_cast<UT>(value)));
|
||||
}
|
||||
|
||||
//add test data in separate struct, because some compilers only support constexpr functions with return-only body
|
||||
/**
|
||||
* endianness utils
|
||||
*/
|
||||
// add test data in separate struct, because some compilers only support constexpr functions with return-only body
|
||||
// suppress msvc warnings.
|
||||
#pragma warning( disable : 4310 )
|
||||
struct EndiannessTestData {
|
||||
static constexpr uint32_t _sample4Bytes = 0x01020304;
|
||||
static constexpr uint8_t _sample1stByte = (const uint8_t &) _sample4Bytes;
|
||||
};
|
||||
#pragma warning( default : 4310 )
|
||||
|
||||
constexpr EndiannessType getSystemEndianness() {
|
||||
static_assert(EndiannessTestData::_sample1stByte == 0x04 || EndiannessTestData::_sample1stByte == 0x01,
|
||||
@@ -96,6 +160,16 @@ namespace bitsery {
|
||||
: EndiannessType::BigEndian;
|
||||
}
|
||||
|
||||
template <typename Config>
|
||||
using ShouldSwap = std::integral_constant<bool, Config::Endianness != details::getSystemEndianness()>;
|
||||
|
||||
/**
|
||||
* helper types to work with bits
|
||||
*/
|
||||
template<typename T>
|
||||
struct BitsSize:public std::integral_constant<size_t, sizeof(T) * 8> {
|
||||
static_assert(CHAR_BIT == 8, "only support systems with byte size of 8 bits");
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct ScratchType {
|
||||
@@ -104,21 +178,186 @@ namespace bitsery {
|
||||
|
||||
template<>
|
||||
struct ScratchType<uint8_t> {
|
||||
using type = uint16_t;
|
||||
using type = uint_fast16_t;
|
||||
};
|
||||
|
||||
/*
|
||||
* class used by session reader, to access underlying iterators of buffer
|
||||
template<typename T>
|
||||
struct FastType {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct FastType<uint8_t> {
|
||||
using type = uint_fast8_t;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct FastType<uint16_t> {
|
||||
using type = uint_fast16_t;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct FastType<uint32_t> {
|
||||
using type = uint_fast32_t;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct FastType<uint64_t> {
|
||||
using type = uint_fast64_t;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct FastType<int8_t> {
|
||||
using type = int_fast8_t;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct FastType<int16_t> {
|
||||
using type = int_fast16_t;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct FastType<int32_t> {
|
||||
using type = int_fast32_t;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct FastType<int64_t> {
|
||||
using type = int_fast64_t;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* output/input adapter base that handles endianness
|
||||
*/
|
||||
struct SessionAccess {
|
||||
template <typename TReader, typename Iterator>
|
||||
static Iterator& posIteratorRef(TReader& r) {
|
||||
return r.posIt;
|
||||
|
||||
template<typename Adapter>
|
||||
struct OutputAdapterBaseCRTP {
|
||||
|
||||
static constexpr bool BitPackingEnabled = false;
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBytes(const T &v) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
writeSwappedValue(&v, ShouldSwap<typename Adapter::TConfig>{});
|
||||
}
|
||||
template <typename TReader, typename Iterator>
|
||||
static Iterator& endIteratorRef(TReader& r) {
|
||||
return r.endIt;
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBuffer(const T *buf, size_t count) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
writeSwappedBuffer(buf, count, ShouldSwap<typename Adapter::TConfig>{});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void writeBits(const T &, size_t ) {
|
||||
static_assert(std::is_void<T>::value,
|
||||
"Bit-packing is not enabled.\nEnable by call to `enableBitPacking`) or create Serializer with bit packing enabled.");
|
||||
}
|
||||
|
||||
void align() {
|
||||
|
||||
}
|
||||
|
||||
OutputAdapterBaseCRTP() = default;
|
||||
OutputAdapterBaseCRTP(const OutputAdapterBaseCRTP&) = delete;
|
||||
OutputAdapterBaseCRTP& operator = (const OutputAdapterBaseCRTP&) = delete;
|
||||
OutputAdapterBaseCRTP(OutputAdapterBaseCRTP&&) = default;
|
||||
OutputAdapterBaseCRTP& operator = (OutputAdapterBaseCRTP&&) = default;
|
||||
|
||||
private:
|
||||
|
||||
template<typename T>
|
||||
void writeSwappedValue(const T *v, std::true_type) {
|
||||
const auto res = details::swap(*v);
|
||||
static_cast<Adapter*>(this)->template writeInternalValue<sizeof(T)>(reinterpret_cast<const typename Adapter::TValue *>(&res));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void writeSwappedValue(const T *v, std::false_type) {
|
||||
static_cast<Adapter*>(this)->template writeInternalValue<sizeof(T)>(reinterpret_cast<const typename Adapter::TValue *>(v));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void writeSwappedBuffer(const T *v, size_t count, std::true_type) {
|
||||
std::for_each(v, std::next(v, count), [this](const T &v) {
|
||||
const auto res = details::swap(v);
|
||||
static_cast<Adapter*>(this)->template writeInternalValue<sizeof(T)>(reinterpret_cast<const typename Adapter::TValue *>(&res));
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void writeSwappedBuffer(const T *v, size_t count, std::false_type) {
|
||||
static_cast<Adapter*>(this)->writeInternalBuffer(reinterpret_cast<const typename Adapter::TValue *>(v), count * sizeof(T));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template <typename Base>
|
||||
struct InputAdapterBaseCRTP {
|
||||
|
||||
static constexpr bool BitPackingEnabled = false;
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void readBytes(T& v) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
static_cast<Base*>(this)->template readInternalValue<sizeof(T)>(reinterpret_cast<typename Base::TValue *>(&v));
|
||||
swapDataBits(v, ShouldSwap<typename Base::TConfig>{});
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void readBuffer(T* buf, size_t count) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
static_cast<Base*>(this)->readInternalBuffer(reinterpret_cast<typename Base::TValue *>(buf), sizeof(T) * count);
|
||||
swapDataBits(buf, count, ShouldSwap<typename Base::TConfig>{});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void readBits(T&, size_t) {
|
||||
static_assert(std::is_void<T>::value,
|
||||
"Bit-packing is not enabled.\nEnable by call to `enableBitPacking`) or create Deserializer with bit packing enabled.");
|
||||
}
|
||||
|
||||
void align() {
|
||||
|
||||
}
|
||||
|
||||
InputAdapterBaseCRTP() = default;
|
||||
InputAdapterBaseCRTP(const InputAdapterBaseCRTP&) = delete;
|
||||
InputAdapterBaseCRTP& operator = (const InputAdapterBaseCRTP&) = delete;
|
||||
|
||||
InputAdapterBaseCRTP(InputAdapterBaseCRTP&&) = default;
|
||||
InputAdapterBaseCRTP& operator = (InputAdapterBaseCRTP&&) = default;
|
||||
|
||||
virtual ~InputAdapterBaseCRTP() = default;
|
||||
|
||||
private:
|
||||
|
||||
template<typename T>
|
||||
void swapDataBits(T *v, size_t count, std::true_type) {
|
||||
using diff_t = typename std::iterator_traits<T*>::difference_type;
|
||||
std::for_each(v, std::next(v, static_cast<diff_t>(count)), [](T &x) { x = details::swap(x); });
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void swapDataBits(T *, size_t , std::false_type) {
|
||||
//empty function because no swap is required
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void swapDataBits(T &v, std::true_type) {
|
||||
v = details::swap(v);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void swapDataBits(T &, std::false_type) {
|
||||
//empty function because no swap is required
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,87 +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_DETAILS_ADAPTER_UTILS_H
|
||||
#define BITSERY_DETAILS_ADAPTER_UTILS_H
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
enum class ReaderError {
|
||||
NoError,
|
||||
ReadingError, // this might be used with stream adapter
|
||||
DataOverflow,
|
||||
InvalidData,
|
||||
InvalidPointer
|
||||
};
|
||||
|
||||
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(ReaderError::InvalidData);
|
||||
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_ADAPTER_UTILS_H
|
||||
@@ -20,14 +20,14 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#ifndef BITSERY_DETAILS_FLEXIBLE_COMMON_H
|
||||
#define BITSERY_DETAILS_FLEXIBLE_COMMON_H
|
||||
#ifndef BITSERY_DETAILS_BRIEF_SYNTAX_COMMON_H
|
||||
#define BITSERY_DETAILS_BRIEF_SYNTAX_COMMON_H
|
||||
|
||||
#include "../traits/core/traits.h"
|
||||
#include <limits>
|
||||
|
||||
namespace bitsery {
|
||||
namespace flexible {
|
||||
namespace brief_syntax {
|
||||
|
||||
//these function overloads is required to apply maxSize, and optimize for fundamental types
|
||||
//for contigous arrays of fundamenal types, memcpy will be applied
|
||||
@@ -84,13 +84,13 @@ namespace bitsery {
|
||||
|
||||
|
||||
//all wrapper functions, that modify behaviour, should inherit from this
|
||||
struct ArchiveWrapperFnc {
|
||||
struct ModFnc {
|
||||
|
||||
};
|
||||
|
||||
//this type is used to differentiate between container and text behaviour
|
||||
template<typename T, size_t N, bool isText>
|
||||
struct CArray : public ArchiveWrapperFnc {
|
||||
struct CArray : public ModFnc {
|
||||
CArray(T (&data_)[N]) : data{data_} {};
|
||||
T (&data)[N];
|
||||
};
|
||||
@@ -107,7 +107,7 @@ namespace bitsery {
|
||||
|
||||
//used to set max container size
|
||||
template<typename T>
|
||||
struct MaxSize : public ArchiveWrapperFnc {
|
||||
struct MaxSize : public ModFnc {
|
||||
MaxSize(T &data_, size_t maxSize_) : data{data_}, maxSize{maxSize_} {};
|
||||
T &data;
|
||||
size_t maxSize;
|
||||
@@ -146,4 +146,4 @@ namespace bitsery {
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_DETAILS_FLEXIBLE_COMMON_H
|
||||
#endif //BITSERY_DETAILS_BRIEF_SYNTAX_COMMON_H
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <tuple>
|
||||
#include "adapter_utils.h"
|
||||
#include "adapter_common.h"
|
||||
#include "../traits/core/traits.h"
|
||||
|
||||
|
||||
@@ -47,12 +47,44 @@ namespace bitsery {
|
||||
return T{};
|
||||
}
|
||||
template <typename T>
|
||||
static T* createInHeap() {
|
||||
return new T{};
|
||||
static T* create(void* ptr) {
|
||||
return new(ptr) T{};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
// convenient functors that can be passed as lambda to serializer/deserializer instead of writing lambda
|
||||
// e.g. instead of writing this:
|
||||
// s.container(c, 100, [](S& s, float& v) { s.ext4b(v, CompactValue{});});
|
||||
// you can write like this
|
||||
// s.container(c, 100, FtorExtValue2b<CompactValue>{});
|
||||
template<size_t N, typename Ext>
|
||||
struct FtorExtValue : public Ext {
|
||||
template <typename S, typename T>
|
||||
void operator()(S& s, T& v) const {
|
||||
s.template ext<N>(v, static_cast<const Ext&>(*this));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Ext>
|
||||
struct FtorExtValue1b: FtorExtValue<1, Ext> {};
|
||||
template <typename Ext>
|
||||
struct FtorExtValue2b: FtorExtValue<2, Ext> {};
|
||||
template <typename Ext>
|
||||
struct FtorExtValue4b: FtorExtValue<4, Ext> {};
|
||||
template <typename Ext>
|
||||
struct FtorExtValue8b: FtorExtValue<8, Ext> {};
|
||||
|
||||
template<typename Ext>
|
||||
struct FtorExtObject : public Ext {
|
||||
template <typename S, typename T>
|
||||
void operator()(S& s, T& v) const {
|
||||
s.ext(v, static_cast<const Ext&>(*this));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//when call to serialize function is ambiguous (member and non-member serialize function exists for a type)
|
||||
//specialize this class by inheriting from either UseNonMemberFnc or UseMemberFnc
|
||||
//e.g.
|
||||
@@ -67,23 +99,6 @@ namespace bitsery {
|
||||
struct UseMemberFnc : std::integral_constant<int, 2> {
|
||||
};
|
||||
|
||||
|
||||
//serializer/deserializer, does not have public interface to get underlying writer/reader
|
||||
//to prevent users from using writer/reader directly, because they have different interface
|
||||
//and they cannot be used describing serialization flows.: use extensions for this reason.
|
||||
//this class allows to get underlying adapter writer/reader, and only should be used outside serialization functions.
|
||||
struct AdapterAccess {
|
||||
template<typename Serializer>
|
||||
static typename Serializer::TWriter &getWriter(Serializer &s) {
|
||||
return s._writer;
|
||||
}
|
||||
|
||||
template<typename Deserializer>
|
||||
static typename Deserializer::TReader &getReader(Deserializer &s) {
|
||||
return s._reader;
|
||||
}
|
||||
};
|
||||
|
||||
namespace details {
|
||||
|
||||
//helper types for error handling
|
||||
@@ -128,20 +143,20 @@ namespace bitsery {
|
||||
template <typename S, typename T>
|
||||
struct HasSerializeMethod :HasSerializeMethodHelper<S, T>::type {};
|
||||
|
||||
//helper types for IsFlexibleIncluded
|
||||
//helper types for IsBriefSyntaxIncluded
|
||||
template <typename S, typename T>
|
||||
using TryArchiveProcess = decltype(archiveProcess(std::declval<S &>(), std::declval<T &&>()));
|
||||
using TryProcessBriefSyntax = decltype(processBriefSyntax(std::declval<S &>(), std::declval<T &&>()));
|
||||
|
||||
template <typename S, typename T>
|
||||
struct IsFlexibleIncludedHelper {
|
||||
template <typename Q, typename R, typename = TryArchiveProcess<Q, R>>
|
||||
struct IsBriefSyntaxIncludedHelper {
|
||||
template <typename Q, typename R, typename = TryProcessBriefSyntax<Q, R>>
|
||||
static std::true_type tester(Q&&, R&&);
|
||||
static std::false_type tester(...);
|
||||
using type = decltype(tester(std::declval<S>(), std::declval<T>()));
|
||||
};
|
||||
|
||||
template <typename S, typename T>
|
||||
struct IsFlexibleIncluded :IsFlexibleIncludedHelper<S, T>::type {};
|
||||
struct IsBriefSyntaxIncluded :IsBriefSyntaxIncludedHelper<S, T>::type {};
|
||||
#else
|
||||
//helper metafunction, that is added to c++17
|
||||
template<typename... Ts>
|
||||
@@ -174,12 +189,12 @@ namespace bitsery {
|
||||
|
||||
//this solution doesn't work with visual studio, but is more elegant
|
||||
template<typename, typename, typename = void>
|
||||
struct IsFlexibleIncluded : std::false_type {
|
||||
struct IsBriefSyntaxIncluded : std::false_type {
|
||||
};
|
||||
|
||||
template<typename S, typename T>
|
||||
struct IsFlexibleIncluded<S, T,
|
||||
void_t<decltype(archiveProcess(std::declval<S &>(), std::declval<T &&>()))>
|
||||
struct IsBriefSyntaxIncluded<S, T,
|
||||
void_t<decltype(processBriefSyntax(std::declval<S &>(), std::declval<T &&>()))>
|
||||
> : std::true_type {
|
||||
};
|
||||
#endif
|
||||
@@ -271,13 +286,13 @@ namespace bitsery {
|
||||
*/
|
||||
|
||||
template<typename S, typename T, typename Enabled = void>
|
||||
struct ArchiveFunction {
|
||||
struct BriefSyntaxFunction {
|
||||
|
||||
static void invoke(S &s, T &&obj) {
|
||||
static_assert(IsFlexibleIncluded<S, T>::value,
|
||||
"\nPlease include '<bitsery/flexible.h>' to use 'archive' function:\n");
|
||||
static_assert(IsBriefSyntaxIncluded<S, T>::value,
|
||||
"\nPlease include '<bitsery/brief_syntax.h>' to use operator():\n");
|
||||
|
||||
archiveProcess(s, std::forward<T>(obj));
|
||||
processBriefSyntax(s, std::forward<T>(obj));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -285,6 +300,156 @@ namespace bitsery {
|
||||
* helper function for getting context from serializer/deserializer
|
||||
*/
|
||||
|
||||
template<int Index, typename... Conds>
|
||||
struct FindIndex : std::integral_constant<int, Index> {};
|
||||
|
||||
template<int Index, typename Cond, typename... Conds>
|
||||
struct FindIndex<Index, Cond, Conds...> :
|
||||
std::conditional<Cond::value, std::integral_constant<int, Index>, FindIndex<Index+1, Conds...>>::type
|
||||
{};
|
||||
|
||||
template <typename T, typename Tuple>
|
||||
struct GetConvertibleTypeIndexFromTuple;
|
||||
|
||||
template <typename T, typename... Us>
|
||||
struct GetConvertibleTypeIndexFromTuple<T, std::tuple<Us...>> : FindIndex<0, std::is_convertible<Us&, T&>...> {};
|
||||
|
||||
|
||||
template <typename T, typename Tuple>
|
||||
struct IsExistsConvertibleTupleType;
|
||||
|
||||
template <typename T, typename... Us>
|
||||
struct IsExistsConvertibleTupleType<T, std::tuple<Us...>> :
|
||||
std::integral_constant<bool, GetConvertibleTypeIndexFromTuple<T, std::tuple<Us...>>::value != sizeof...(Us)> {};
|
||||
|
||||
|
||||
/*
|
||||
* get context from internal or external, and check if it's convertible or not
|
||||
*/
|
||||
|
||||
|
||||
template<bool AssertExists, typename TCast, typename TContext>
|
||||
TCast* getDirectlyIfExists(TContext& ctx, std::true_type) {
|
||||
return &static_cast<TCast&>(ctx);
|
||||
}
|
||||
|
||||
template<bool AssertExists, typename TCast, typename TContext>
|
||||
TCast* getDirectlyIfExists(TContext& , std::false_type) {
|
||||
// TCast cannot be convertible from provided context
|
||||
static_assert(!AssertExists,
|
||||
"Invalid context cast. Context type doesn't exists.\nSome functionality requires (de)seserializer to have specific context.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
template<bool AssertExists, typename TCast, typename ... TArgs>
|
||||
TCast* getFromTupleIfExists(std::tuple<TArgs...>& ctx, std::true_type) {
|
||||
using TupleIndex = GetConvertibleTypeIndexFromTuple<TCast, std::tuple<TArgs...>>;
|
||||
return &static_cast<TCast&>(std::get<TupleIndex::value>(ctx));
|
||||
}
|
||||
|
||||
template<bool AssertExists, typename TCast, typename ... TArgs>
|
||||
TCast* getFromTupleIfExists(std::tuple<TArgs...>& , std::false_type) {
|
||||
// TCast cannot be convertible from provided context
|
||||
static_assert(!AssertExists,
|
||||
"Invalid context cast. Context type doesn't exists.\nSome functionality requires (de)seserializer to have specific context.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//non tuple context
|
||||
template<bool AssertExists, typename TCast, typename TContext>
|
||||
TCast* getContext(TContext& ctx) {
|
||||
return getDirectlyIfExists<AssertExists, TCast>(ctx, std::is_convertible<TContext&, TCast&>{});
|
||||
}
|
||||
|
||||
//tuple context
|
||||
template<bool AssertExists, typename TCast, typename ... TArgs>
|
||||
TCast* getContext(std::tuple<TArgs...>& ctx) {
|
||||
return getFromTupleIfExists<AssertExists, TCast>(ctx, IsExistsConvertibleTupleType<TCast, std::tuple<TArgs...>>{});
|
||||
}
|
||||
|
||||
template <typename Adapter, typename Context>
|
||||
class AdapterAndContextRef {
|
||||
public:
|
||||
static constexpr bool HasContext = true;
|
||||
using Config = typename Adapter::TConfig;
|
||||
|
||||
// constructing adapter in place is important,
|
||||
// because enableBitPacking might create instance with bit write/read enabled adapter wrapper,
|
||||
// which has non trivial destructor
|
||||
template <typename ... TArgs>
|
||||
explicit AdapterAndContextRef(Context& ctx, TArgs&& ... args)
|
||||
: _adapter{std::forward<TArgs>(args)...},
|
||||
_context{ctx}
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* get serialization context.
|
||||
* this is optional, but might be required for some specific serialization flows.
|
||||
*/
|
||||
|
||||
template <typename T>
|
||||
T& context() {
|
||||
return *getContext<true, T>(_context);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* contextOrNull() {
|
||||
return getContext<false, T>(_context);
|
||||
}
|
||||
|
||||
Adapter& adapter() & {
|
||||
return _adapter;
|
||||
}
|
||||
|
||||
Adapter adapter() && {
|
||||
return std::move(_adapter);
|
||||
}
|
||||
|
||||
protected:
|
||||
Adapter _adapter;
|
||||
Context& _context;
|
||||
};
|
||||
|
||||
template <typename Adapter>
|
||||
class AdapterAndContextRef<Adapter, void> {
|
||||
public:
|
||||
static constexpr bool HasContext = false;
|
||||
using Config = typename Adapter::TConfig;
|
||||
|
||||
template <typename ... TArgs>
|
||||
explicit AdapterAndContextRef(TArgs&& ... args)
|
||||
: _adapter{std::forward<TArgs>(args)...}
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T& context() {
|
||||
static_assert(std::is_void<T>::value, "Context is not defined (is void).");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* contextOrNull() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Adapter& adapter() & {
|
||||
return _adapter;
|
||||
}
|
||||
|
||||
Adapter adapter() && {
|
||||
return std::move(_adapter);
|
||||
}
|
||||
|
||||
protected:
|
||||
Adapter _adapter;
|
||||
};
|
||||
|
||||
/**
|
||||
* other helper meta-functions
|
||||
*/
|
||||
|
||||
template<typename T, template<typename...> class Template>
|
||||
struct IsSpecializationOf : std::false_type {
|
||||
};
|
||||
@@ -293,123 +458,6 @@ namespace bitsery {
|
||||
struct IsSpecializationOf<Template<Args...>, Template> : std::true_type {
|
||||
};
|
||||
|
||||
//helper types for better error messages
|
||||
template<typename Find, typename ... TList>
|
||||
struct GetTypeIndex : std::integral_constant<size_t, 0> {
|
||||
};
|
||||
|
||||
//found it
|
||||
template<typename Find, typename ... Tail>
|
||||
struct GetTypeIndex<Find, Find, Tail...> : std::integral_constant<size_t, 0> {
|
||||
};
|
||||
|
||||
//iteratates over types
|
||||
template<typename Find, typename Head, typename ... Tail>
|
||||
struct GetTypeIndex<Find, Head, Tail...> : std::integral_constant<size_t,
|
||||
1 + GetTypeIndex<Find, Tail...>::value> {
|
||||
};
|
||||
|
||||
template<typename Find, typename ... TList>
|
||||
struct HasType : std::integral_constant<bool, (GetTypeIndex<Find, TList...>::value<(sizeof ... (TList)))> {
|
||||
};
|
||||
|
||||
template<typename TCast, typename Tuple>
|
||||
struct HasContext : std::is_same<TCast, Tuple> {
|
||||
};
|
||||
|
||||
template<typename TCast, typename ... Args>
|
||||
struct HasContext<TCast, std::tuple<Args...>> : HasType<TCast, Args...> {
|
||||
};
|
||||
|
||||
/*
|
||||
* get context, and static assert if type doesn't exists
|
||||
*/
|
||||
|
||||
template<typename TCast, typename ... Args>
|
||||
TCast *getContextImpl(std::tuple<Args...> *ctx, std::true_type) {
|
||||
using TCastIndex = GetTypeIndex<TCast, Args...>;
|
||||
static_assert(HasType<TCast, Args...>::value, "Invalid context cast. Context type doesn't exists.\nSome functionality requires (de)seserializer to have specific context.");
|
||||
return std::addressof(std::get<TCastIndex::value>(*ctx));
|
||||
}
|
||||
|
||||
template<typename TCast, typename TContext>
|
||||
TCast *getContextImpl(TContext *ctx, std::false_type) {
|
||||
static_assert(std::is_convertible<TContext *, TCast *>::value, "Invalid context cast. Context type doesn't exists.\nSome functionality requires (de)seserializer to have specific context.");
|
||||
return static_cast<TCast *>(ctx);
|
||||
}
|
||||
|
||||
//get local ctx
|
||||
template<typename TCast, typename TContext, typename TInternalContext>
|
||||
TCast *chooseInternalOrExternalContext(TContext *, TInternalContext &internalCtx, std::true_type) {
|
||||
return getContextImpl<TCast>(&internalCtx, std::true_type{});
|
||||
}
|
||||
|
||||
//get external ctx
|
||||
template<typename TCast, typename TContext, typename TInternalContext>
|
||||
TCast *chooseInternalOrExternalContext(TContext *ctx, TInternalContext &, std::false_type) {
|
||||
return ctx
|
||||
? getContextImpl<TCast>(ctx, IsSpecializationOf<TContext, std::tuple>{})
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
template<typename TCast, typename TContext, typename TInternalContext>
|
||||
TCast *getContext(TContext *ctx, TInternalContext &internalCtx) {
|
||||
return chooseInternalOrExternalContext<TCast>(ctx, internalCtx, HasContext<TCast, TInternalContext>{});
|
||||
}
|
||||
|
||||
/*
|
||||
* get context, if type doesn't exists then do not static_assert but return null instead
|
||||
*/
|
||||
|
||||
template<typename TCast, typename TContext>
|
||||
TCast *getContextFromTypeIfExists(TContext *ctx, std::true_type) {
|
||||
return static_cast<TCast *>(ctx);
|
||||
}
|
||||
|
||||
template<typename TCast, typename TContext>
|
||||
TCast *getContextFromTypeIfExists(TContext *, std::false_type) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename TCast, typename TContext>
|
||||
TCast *getContextImplIfExists(TContext *ctx, std::false_type) {
|
||||
return getContextFromTypeIfExists<TCast>(ctx, std::is_convertible<TContext *, TCast *>{});
|
||||
}
|
||||
|
||||
template<typename TCast, typename TContext>
|
||||
TCast *getContextFromTupleIfExists(TContext *ctx, std::true_type tmp) {
|
||||
return getContextImpl<TCast>(ctx, tmp);
|
||||
}
|
||||
|
||||
template<typename TCast, typename TContext>
|
||||
TCast *getContextFromTupleIfExists(TContext *, std::false_type) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename TCast, typename TContext>
|
||||
TCast *getContextImplIfExists(TContext *ctx, std::true_type) {
|
||||
return getContextFromTupleIfExists<TCast>(ctx, HasContext<TCast, TContext>{});
|
||||
}
|
||||
|
||||
//get local ctx
|
||||
template<typename TCast, typename TContext, typename TInternalContext>
|
||||
TCast *chooseInternalOrExternalContextIfExists(TContext *, TInternalContext &internalCtx, std::true_type) {
|
||||
return getContextImplIfExists<TCast>(&internalCtx, std::true_type{});
|
||||
}
|
||||
|
||||
//get external ctx
|
||||
template<typename TCast, typename TContext, typename TInternalContext>
|
||||
TCast *chooseInternalOrExternalContextIfExists(TContext *ctx, TInternalContext &, std::false_type) {
|
||||
return ctx
|
||||
? getContextImplIfExists<TCast>(ctx, IsSpecializationOf<TContext, std::tuple>{})
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
template<typename TCast, typename TContext, typename TInternalContext>
|
||||
TCast *getContextIfTypeExists(TContext *ctx, TInternalContext &internalCtx) {
|
||||
return chooseInternalOrExternalContextIfExists<TCast>(ctx, internalCtx, HasContext<TCast, TInternalContext>{});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
//MIT License
|
||||
//
|
||||
//Copyright (c) 2018 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_SESSIONS_H
|
||||
#define BITSERY_DETAILS_SESSIONS_H
|
||||
|
||||
#include <vector>
|
||||
#include <stack>
|
||||
#include "adapter_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
namespace session {
|
||||
/*
|
||||
* writer/reader implementations that disable session support
|
||||
*/
|
||||
template <typename TWriter>
|
||||
struct DisabledSessionsWriter {
|
||||
void begin(TWriter& ) {
|
||||
static_assert(std::is_void<TWriter>::value, "Sessions is disabled, enable it via configuration");
|
||||
}
|
||||
void end(TWriter& ) {
|
||||
static_assert(std::is_void<TWriter>::value, "Sessions is disabled, enable it via configuration");
|
||||
}
|
||||
void flushSessions(TWriter& ) {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TReader>
|
||||
struct DisabledSessionsReader {
|
||||
template <typename TBufferContext>
|
||||
DisabledSessionsReader(TReader& , TBufferContext& ) {
|
||||
}
|
||||
|
||||
void begin() {
|
||||
static_assert(std::is_void<TReader>::value, "Sessions is disabled, enable it via configuration");
|
||||
}
|
||||
|
||||
void end() {
|
||||
static_assert(std::is_void<TReader>::value, "Sessions is disabled, enable it via configuration");
|
||||
}
|
||||
|
||||
bool hasActiveSessions() const {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* writer/reader real implementations
|
||||
* sessions reading requires to have random access iterators, so it cannot be used with streams
|
||||
*/
|
||||
template <typename TWriter>
|
||||
class SessionsWriter {
|
||||
public:
|
||||
|
||||
SessionsWriter() = default;
|
||||
SessionsWriter(const SessionsWriter&) = delete;
|
||||
SessionsWriter& operator = (const SessionsWriter& ) = delete;
|
||||
|
||||
SessionsWriter(SessionsWriter&&) = default;
|
||||
SessionsWriter& operator = (SessionsWriter&& ) = default;
|
||||
|
||||
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 sessionSize = writer.writtenBytesCount();
|
||||
assert(sessionSize > 0);
|
||||
*sessionIt = sessionSize;
|
||||
}
|
||||
|
||||
void flushSessions(TWriter& writer) {
|
||||
if (_sessions.size()) {
|
||||
assert(_sessionIndex.empty());
|
||||
auto dataSize = writer.writtenBytesCount();
|
||||
for(auto& s:_sessions) {
|
||||
details::writeSize(writer, s);
|
||||
}
|
||||
_sessions.clear();
|
||||
|
||||
auto totalSize = writer.writtenBytesCount();
|
||||
//write offset where actual data ends
|
||||
auto sessionsOffset = totalSize - dataSize + 4;//4 bytes for offset data
|
||||
writer.template writeBytes<4>(static_cast<uint32_t>(sessionsOffset));
|
||||
}
|
||||
}
|
||||
private:
|
||||
std::vector<size_t> _sessions{};
|
||||
std::stack<size_t> _sessionIndex{};
|
||||
};
|
||||
|
||||
template <typename TReader>
|
||||
struct SessionsReader {
|
||||
using TIterator = typename TReader::TIterator;
|
||||
|
||||
template <typename InputAdapter>
|
||||
SessionsReader(TReader& r, InputAdapter& adapter)
|
||||
:_reader{r},
|
||||
_beginIt{details::SessionAccess::posIteratorRef<InputAdapter, TIterator>(adapter)},
|
||||
_posItRef{details::SessionAccess::posIteratorRef<InputAdapter, TIterator>(adapter)},
|
||||
_endItRef{details::SessionAccess::endIteratorRef<InputAdapter, TIterator>(adapter)}
|
||||
{
|
||||
}
|
||||
|
||||
SessionsReader(const SessionsReader&) = delete;
|
||||
SessionsReader& operator = (const SessionsReader& ) = delete;
|
||||
|
||||
SessionsReader(SessionsReader&&) = default;
|
||||
SessionsReader& operator = (SessionsReader&& ) = default;
|
||||
|
||||
void begin() {
|
||||
if (_sessions.empty()) {
|
||||
if (!initializeSessions())
|
||||
return;
|
||||
}
|
||||
|
||||
//save end position for current session
|
||||
_sessionsStack.push(_endItRef);
|
||||
if (_nextSessionIt != std::end(_sessions)) {
|
||||
if (std::distance(_posItRef, _endItRef) > 0) {
|
||||
//set end position for new session
|
||||
auto newEnd = std::next(_beginIt, *_nextSessionIt);
|
||||
if (std::distance(newEnd, _endItRef) < 0)
|
||||
{
|
||||
//new session cannot end further than current end
|
||||
_reader.setError(ReaderError::InvalidData);
|
||||
return;
|
||||
}
|
||||
_endItRef = 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 (!(_posItRef == _endItRef || _reader.error() == ReaderError::NoError) || !(_sessionsStack.size() > 1)) {
|
||||
_reader.setError(ReaderError::InvalidData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
//error() == BUFFER_OVERFLOW: reading older version
|
||||
auto dist = std::distance(_posItRef, _endItRef);
|
||||
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(_beginIt, _endItRef));
|
||||
for (; _nextSessionIt != std::end(_sessions); ++_nextSessionIt) {
|
||||
if (*_nextSessionIt > currPos)
|
||||
break;
|
||||
}
|
||||
}
|
||||
//modify pointers only if no error or buffer overflow
|
||||
if (_reader.error() == ReaderError::NoError || _reader.error() == ReaderError::DataOverflow) {
|
||||
_posItRef = _endItRef;
|
||||
//restore end position
|
||||
_endItRef = _sessionsStack.top();
|
||||
}
|
||||
_sessionsStack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
bool hasActiveSessions() const {
|
||||
return _sessionsStack.size() > 0;
|
||||
}
|
||||
|
||||
private:
|
||||
TReader& _reader;
|
||||
TIterator _beginIt;
|
||||
TIterator& _posItRef;
|
||||
TIterator& _endItRef;
|
||||
std::vector<size_t> _sessions{};
|
||||
std::vector<size_t>::iterator _nextSessionIt{};
|
||||
std::stack<TIterator> _sessionsStack{};
|
||||
|
||||
bool initializeSessions() {
|
||||
//save current position
|
||||
auto currPos = _posItRef;
|
||||
//read size
|
||||
if (std::distance(_posItRef, _endItRef) < 4) {
|
||||
_reader.setError(ReaderError::InvalidData);
|
||||
return false;
|
||||
}
|
||||
auto endSessionsSizesIt = std::next(_endItRef, -4);
|
||||
_posItRef = endSessionsSizesIt;
|
||||
uint32_t sessionsOffset{};
|
||||
_reader.template readBytes<4>(sessionsOffset);
|
||||
|
||||
auto bufferSize = std::distance(_beginIt, _endItRef);
|
||||
if (static_cast<size_t>(bufferSize) < sessionsOffset) {
|
||||
_reader.setError(ReaderError::InvalidData);
|
||||
return false;
|
||||
}
|
||||
//we can initialy resizes to this value, and we'll shrink it after reading
|
||||
//read session sizes
|
||||
auto sessionsIt = std::back_inserter(_sessions);
|
||||
_posItRef = std::next(_endItRef, -static_cast<int32_t>(sessionsOffset));
|
||||
while (std::distance(_posItRef, endSessionsSizesIt) > 0) {
|
||||
size_t size;
|
||||
details::readSize(_reader, size, bufferSize);
|
||||
*sessionsIt++ = size;
|
||||
}
|
||||
_sessions.shrink_to_fit();
|
||||
//set iterators to data
|
||||
_posItRef = currPos;
|
||||
_endItRef = std::next(_endItRef, -static_cast<int32_t>(sessionsOffset));
|
||||
_nextSessionIt = std::begin(_sessions);//set before first session;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_DETAILS_SESSIONS_H
|
||||
@@ -35,46 +35,46 @@ namespace bitsery {
|
||||
class CompactValueImpl {
|
||||
public:
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &s, Writer &writer, const T &v, Fnc &&) const {
|
||||
template<typename Ser, typename T, typename Fnc>
|
||||
void serialize(Ser &s, const T &v, Fnc &&) const {
|
||||
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "");
|
||||
using TValue = typename IntegralFromFundamental<T>::TValue;
|
||||
serializeImpl(s, writer, reinterpret_cast<const TValue&>(v), std::integral_constant<bool, sizeof(T) != 1>{});
|
||||
serializeImpl(s.adapter(), reinterpret_cast<const TValue&>(v), std::integral_constant<bool, sizeof(T) != 1>{});
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &d, Reader &reader, T &v, Fnc &&) const {
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserialize(Des &d, T &v, Fnc &&) const {
|
||||
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "");
|
||||
using TValue = typename IntegralFromFundamental<T>::TValue;
|
||||
deserializeImpl(d, reader, reinterpret_cast<TValue &>(v), std::integral_constant<bool, sizeof(T) != 1>{});
|
||||
deserializeImpl(d.adapter(), reinterpret_cast<TValue &>(v), std::integral_constant<bool, sizeof(T) != 1>{});
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// if value is 1byte size, just serialize/ deserialize whole value
|
||||
template<typename Ser, typename Writer, typename T>
|
||||
void serializeImpl(Ser &s, Writer &, const T &v, std::false_type) const {
|
||||
s.value1b(v);
|
||||
template<typename Writer, typename T>
|
||||
void serializeImpl(Writer &writer, const T &v, std::false_type) const {
|
||||
writer.template writeBytes<1>(v);
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T>
|
||||
void deserializeImpl(Des &d, Reader &, T &v, std::false_type) const {
|
||||
d.value1b(v);
|
||||
template<typename Reader, typename T>
|
||||
void deserializeImpl(Reader &reader, T &v, std::false_type) const {
|
||||
reader.template readBytes<1>(v);
|
||||
}
|
||||
|
||||
// when value is bigger than 1byte size,
|
||||
template<typename Ser, typename Writer, typename T>
|
||||
void serializeImpl(Ser &, Writer &writer, const T &v, std::true_type) const {
|
||||
template<typename Writer, typename T>
|
||||
void serializeImpl(Writer &writer, const T &v, std::true_type) const {
|
||||
auto val = zigZagEncode(v, std::is_signed<typename IntegralFromFundamental<T>::TValue>{});
|
||||
writeBytes(writer, val);
|
||||
}
|
||||
|
||||
|
||||
template<typename Des, typename Reader, typename T>
|
||||
void deserializeImpl(Des &, Reader &reader, T &v, std::true_type) const {
|
||||
template<typename Reader, typename T>
|
||||
void deserializeImpl(Reader &reader, T &v, std::true_type) const {
|
||||
using TUnsigned = SameSizeUnsigned<T>;
|
||||
TUnsigned res{};
|
||||
readBytes(reader, res);
|
||||
readBytes<Reader::TConfig::CheckDataErrors>(reader, res);
|
||||
v = zigZagDecode<T>(res, std::is_signed<typename IntegralFromFundamental<T>::TValue>{});
|
||||
}
|
||||
|
||||
@@ -91,18 +91,19 @@ namespace bitsery {
|
||||
|
||||
template<typename T>
|
||||
SameSizeUnsigned<T> zigZagEncode(const T &v, std::true_type) const {
|
||||
return (v << 1) ^ (v >> (BitsSize<T>::value - 1));
|
||||
return static_cast<SameSizeUnsigned<T>>((v << 1) ^ (v >> (BitsSize<T>::value - 1)));
|
||||
}
|
||||
|
||||
template<typename TResult, typename TUnsigned>
|
||||
TResult zigZagDecode(TUnsigned v, std::true_type) const {
|
||||
return (v >> 1) ^ (~(v & 1) + 1); // same as -(v & 1), but no warning on VisualStudio
|
||||
return static_cast<TResult>((v >> 1) ^ (~(v & 1) + 1)); // same as -(v & 1), but no warning on VisualStudio
|
||||
}
|
||||
|
||||
// write/read bytes one by one
|
||||
template<typename Writer, typename T>
|
||||
void writeBytes(Writer &w, const T &v) const {
|
||||
auto val = v;
|
||||
using TFast = typename FastType<T>::type;
|
||||
auto val= static_cast<TFast>(v);
|
||||
while(val > 0x7Fu) {
|
||||
w.template writeBytes<1>(static_cast<uint8_t>(val | 0x80u));
|
||||
val >>=7u;
|
||||
@@ -110,27 +111,31 @@ namespace bitsery {
|
||||
w.template writeBytes<1>(static_cast<uint8_t>(val));
|
||||
}
|
||||
|
||||
template<typename Reader, typename T>
|
||||
template<bool CheckErrors, typename Reader, typename T>
|
||||
void readBytes(Reader &r, T &v) const {
|
||||
using TFast = typename FastType<T>::type;
|
||||
constexpr auto TBITS = sizeof(T)*8;
|
||||
uint8_t b1{0x80u};
|
||||
auto i = 0u;
|
||||
TFast tmp={};
|
||||
for (;i < TBITS && b1 > 0x7Fu; i +=7u) {
|
||||
r.template readBytes<1>(b1);
|
||||
v += static_cast<T>(b1 & 0x7Fu) << i;
|
||||
tmp += static_cast<TFast>(b1 & 0x7Fu) << i;
|
||||
}
|
||||
checkReadOverflow<Reader, T>(r, i, b1, std::integral_constant<bool, CheckOverflow>{});
|
||||
v = static_cast<T>(tmp);
|
||||
handleReadOverflow<Reader, T>(r, i, b1,
|
||||
std::integral_constant<bool, CheckOverflow && CheckErrors>{});
|
||||
}
|
||||
template <typename Reader, typename T>
|
||||
void checkReadOverflow(Reader &r, unsigned shiftedBy, uint8_t remainder, std::true_type) const {
|
||||
void handleReadOverflow(Reader& r, unsigned shiftedBy, uint8_t remainder, std::true_type) const {
|
||||
constexpr auto TBITS = sizeof(T)*8;
|
||||
if (shiftedBy > TBITS && remainder >> (TBITS + 7 - shiftedBy)) {
|
||||
r.setError(bitsery::ReaderError::DataOverflow);
|
||||
r.error(bitsery::ReaderError::InvalidData);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Reader, typename T>
|
||||
void checkReadOverflow(Reader &, unsigned , uint8_t , std::false_type) const {
|
||||
void handleReadOverflow(Reader &, unsigned , uint8_t , std::false_type) const {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -56,28 +56,30 @@ namespace bitsery {
|
||||
_alignBeforeData{alignBeforeData} {
|
||||
};
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &s, Writer &, const T &obj, Fnc &&fnc) const {
|
||||
template<typename Ser, typename T, typename Fnc>
|
||||
void serialize(Ser &s, const T &obj, Fnc &&fnc) const {
|
||||
assert(traits::ContainerTraits<TContainer>::size(_values) > 0);
|
||||
auto index = details::findEntropyIndex(obj, _values);
|
||||
s.ext(index, ext::ValueRange<size_t>{0u, traits::ContainerTraits<TContainer>::size(_values)});
|
||||
if (_alignBeforeData)
|
||||
s.align();
|
||||
s.adapter().align();
|
||||
if (!index)
|
||||
fnc(const_cast<T &>(obj));
|
||||
fnc(s, const_cast<T &>(obj));
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &d, Reader &, T &obj, Fnc &&fnc) const {
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserialize(Des &d, T &obj, Fnc &&fnc) const {
|
||||
assert(traits::ContainerTraits<TContainer>::size(_values) > 0);
|
||||
size_t index{};
|
||||
d.ext(index, ext::ValueRange<size_t>{0u, traits::ContainerTraits<TContainer>::size(_values)});
|
||||
if (_alignBeforeData)
|
||||
d.align();
|
||||
if (index)
|
||||
obj = *std::next(std::begin(_values), index-1);
|
||||
d.adapter().align();
|
||||
if (index) {
|
||||
using TDiff = typename std::iterator_traits<decltype(std::begin(_values))>::difference_type;
|
||||
obj = static_cast<T>(*std::next(std::begin(_values), static_cast<TDiff>(index-1)));
|
||||
}
|
||||
else
|
||||
fnc(obj);
|
||||
fnc(d, obj);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -30,23 +30,40 @@ namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
/*
|
||||
* enables to add additional serialization methods at the end of method, without breaking existing older code
|
||||
* enables forward and backward compatibility, by allowing to append additional data at the end of serialization
|
||||
* old deserialization method will ignore additional data by jumping through it at the end of deserialization flow
|
||||
* new deserialization method will read all 0 for new fields if there is no data for it
|
||||
*/
|
||||
class Growable {
|
||||
public:
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &, Writer &writer, const T &obj, Fnc &&fnc) const {
|
||||
writer.beginSession();
|
||||
fnc(const_cast<T&>(obj));
|
||||
writer.endSession();
|
||||
template<typename Ser, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, const T &obj, Fnc &&fnc) const {
|
||||
auto& writer = ser.adapter();
|
||||
const auto startPos = writer.currentWritePos();
|
||||
writer.template writeBytes<4>(static_cast<uint32_t>(0));
|
||||
|
||||
fnc(ser, const_cast<T&>(obj));
|
||||
|
||||
const auto endPos = writer.currentWritePos();
|
||||
writer.currentWritePos(startPos);
|
||||
writer.template writeBytes<4>(static_cast<uint32_t>(endPos - startPos));
|
||||
writer.currentWritePos(endPos);
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &, Reader &reader, T &obj, Fnc &&fnc) const {
|
||||
reader.beginSession();
|
||||
fnc(obj);
|
||||
reader.endSession();
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserialize(Des &des, T &obj, Fnc &&fnc) const {
|
||||
auto& reader = des.adapter();
|
||||
uint32_t size{};
|
||||
const auto readEndPos = reader.currentReadEndPos();
|
||||
const auto startPos = reader.currentReadPos();
|
||||
reader.template readBytes<4>(size);
|
||||
reader.currentReadEndPos(startPos + size);
|
||||
|
||||
fnc(des, obj);
|
||||
|
||||
reader.currentReadPos(startPos + size);
|
||||
reader.currentReadEndPos(readEndPos);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include <unordered_set>
|
||||
#include "../traits/core/traits.h"
|
||||
#include "../ext/utils/memory_resource.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
@@ -34,7 +35,9 @@ namespace bitsery {
|
||||
//for standard inheritance (ext::BaseClass) it is optional.
|
||||
class InheritanceContext {
|
||||
public:
|
||||
InheritanceContext() = default;
|
||||
explicit InheritanceContext(MemResourceBase* memResource = nullptr)
|
||||
:_virtualBases{pointer_utils::StdPolyAlloc<const void*>{memResource}}
|
||||
{}
|
||||
InheritanceContext(const InheritanceContext&) = delete;
|
||||
InheritanceContext&operator = (const InheritanceContext&) = delete;
|
||||
InheritanceContext(InheritanceContext&&) = default;
|
||||
@@ -66,34 +69,37 @@ namespace bitsery {
|
||||
size_t _depth{};
|
||||
const void* _parentPtr{};
|
||||
//add virtual bases to the list, as long as we're on the same parent
|
||||
std::unordered_set<const void*> _virtualBases{};
|
||||
std::unordered_set<const void*,
|
||||
std::hash<const void*>, std::equal_to<const void*>,
|
||||
pointer_utils::StdPolyAlloc<const void*>
|
||||
> _virtualBases;
|
||||
};
|
||||
|
||||
template <typename TBase>
|
||||
class BaseClass {
|
||||
public:
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &, const T &obj, Fnc &&fnc) const {
|
||||
template<typename Ser, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, const T &obj, Fnc &&fnc) const {
|
||||
auto& resObj = static_cast<const TBase&>(obj);
|
||||
if (auto ctx = ser.template contextOrNull<InheritanceContext>()) {
|
||||
ctx->beginBase(obj, resObj);
|
||||
fnc(const_cast<TBase&>(resObj));
|
||||
fnc(ser, const_cast<TBase&>(resObj));
|
||||
ctx->end();
|
||||
} else {
|
||||
fnc(const_cast<TBase&>(resObj));
|
||||
fnc(ser, const_cast<TBase&>(resObj));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &des, Reader &, T &obj, Fnc &&fnc) const {
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserialize(Des &des, T &obj, Fnc &&fnc) const {
|
||||
auto& resObj = static_cast<TBase&>(obj);
|
||||
if (auto ctx = des.template contextOrNull<InheritanceContext>()) {
|
||||
ctx->beginBase(obj, resObj);
|
||||
fnc(resObj);
|
||||
fnc(des, resObj);
|
||||
ctx->end();
|
||||
} else {
|
||||
fnc(resObj);
|
||||
fnc(des, resObj);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,22 +110,22 @@ namespace bitsery {
|
||||
class VirtualBaseClass {
|
||||
public:
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &, const T &obj, Fnc &&fnc) const {
|
||||
auto ctx = ser.template context<InheritanceContext>();
|
||||
template<typename Ser, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, const T &obj, Fnc &&fnc) const {
|
||||
auto& ctx = ser.template context<InheritanceContext>();
|
||||
auto& resObj = static_cast<const TBase&>(obj);
|
||||
if (ctx->beginVirtualBase(obj, resObj))
|
||||
fnc(const_cast<TBase&>(resObj));
|
||||
ctx->end();
|
||||
if (ctx.beginVirtualBase(obj, resObj))
|
||||
fnc(ser, const_cast<TBase&>(resObj));
|
||||
ctx.end();
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &des, Reader &, T &obj, Fnc &&fnc) const {
|
||||
auto ctx = des.template context<InheritanceContext>();
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserialize(Des &des, T &obj, Fnc &&fnc) const {
|
||||
auto& ctx = des.template context<InheritanceContext>();
|
||||
auto& resObj = static_cast<TBase&>(obj);
|
||||
if (ctx->beginVirtualBase(obj, resObj))
|
||||
fnc(resObj);
|
||||
ctx->end();
|
||||
if (ctx.beginVirtualBase(obj, resObj))
|
||||
fnc(des, resObj);
|
||||
ctx.end();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace bitsery {
|
||||
|
||||
using TElement = typename std::remove_pointer<T>::type;
|
||||
|
||||
static TElement* getPtr(T &obj) {
|
||||
static TElement* getPtr(T& obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -49,15 +49,26 @@ namespace bitsery {
|
||||
return PointerOwnershipType::Owner;
|
||||
}
|
||||
|
||||
static void assign(T& obj, TElement* valuePtr) {
|
||||
delete obj;
|
||||
obj = valuePtr;
|
||||
static void create(T& obj, pointer_utils::PolyAllocWithTypeId alloc, size_t typeId) {
|
||||
obj = alloc.newObject<TElement>(typeId);
|
||||
}
|
||||
|
||||
static void clear(T& obj) {
|
||||
delete obj;
|
||||
static void createPolymorphic(T& obj, pointer_utils::PolyAllocWithTypeId alloc,
|
||||
const std::shared_ptr<PolymorphicHandlerBase>& handler) {
|
||||
obj = static_cast<TElement*>(handler->create(alloc));
|
||||
}
|
||||
|
||||
static void destroy(T& obj, pointer_utils::PolyAllocWithTypeId alloc, size_t typeId) {
|
||||
alloc.deleteObject(obj, typeId);
|
||||
obj = nullptr;
|
||||
}
|
||||
|
||||
static void destroyPolymorphic(T& obj, pointer_utils::PolyAllocWithTypeId alloc,
|
||||
const std::shared_ptr<PolymorphicHandlerBase>& handler) {
|
||||
handler->destroy(alloc, obj);
|
||||
obj = nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
@@ -66,11 +77,6 @@ namespace bitsery {
|
||||
|
||||
using TElement = typename std::remove_pointer<T>::type;
|
||||
|
||||
//observer must return reference to pointer, so that it could be updated later
|
||||
static TElement*& getPtrRef(T& obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
static TElement* getPtr(T& obj) {
|
||||
return obj;
|
||||
}
|
||||
@@ -79,12 +85,17 @@ namespace bitsery {
|
||||
return PointerOwnershipType::Observer;
|
||||
}
|
||||
|
||||
static void assign(T& obj, TElement* valuePtr) {
|
||||
//do not delete existing object
|
||||
obj = valuePtr;
|
||||
//pure observer doesn't have create/createPolymorphic methods, but instead returns reference to pointer
|
||||
//which gets updated later
|
||||
static TElement*& getPtrRef(T& obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
static void clear(T& obj) {
|
||||
static void destroy(T& obj, MemResourceBase* , size_t ) {
|
||||
obj = nullptr;
|
||||
}
|
||||
|
||||
static void destroyPolymorphic(T& obj, MemResourceBase* , PolymorphicHandlerBase& ) {
|
||||
obj = nullptr;
|
||||
}
|
||||
|
||||
@@ -107,9 +118,22 @@ namespace bitsery {
|
||||
|
||||
// this code is unreachable for reference type, but is necessary to compile
|
||||
// LCOV_EXCL_START
|
||||
static void assign(T& , TElement* ) {}
|
||||
|
||||
static void clear(T& ) {}
|
||||
static void create(T& , MemResourceBase* , size_t ) {
|
||||
|
||||
}
|
||||
|
||||
static void createPolymorphic(T& , MemResourceBase* , PolymorphicHandlerBase& ) {
|
||||
|
||||
}
|
||||
|
||||
static void destroy(T& , MemResourceBase* , size_t ) {
|
||||
|
||||
}
|
||||
|
||||
static void destroyPolymorphic(T& , MemResourceBase* , PolymorphicHandlerBase& ) {
|
||||
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
};
|
||||
@@ -117,7 +141,7 @@ namespace bitsery {
|
||||
// this class is used by NonPtrManager
|
||||
struct NoRTTI {
|
||||
template<typename TBase>
|
||||
static size_t get(TBase& ) {
|
||||
static size_t get(TBase&) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -142,20 +166,20 @@ namespace bitsery {
|
||||
|
||||
template<typename RTTI>
|
||||
using PointerOwnerBase = pointer_utils::PointerObjectExtensionBase<
|
||||
pointer_details::PtrOwnerManager, PolymorphicContext, RTTI>;
|
||||
pointer_details::PtrOwnerManager, PolymorphicContext, RTTI>;
|
||||
|
||||
using PointerOwner = PointerOwnerBase<StandardRTTI>;
|
||||
|
||||
using PointerObserver = pointer_utils::PointerObjectExtensionBase<
|
||||
pointer_details::PtrObserverManager, PolymorphicContext, pointer_details::NoRTTI>;
|
||||
pointer_details::PtrObserverManager, PolymorphicContext, pointer_details::NoRTTI>;
|
||||
|
||||
//inherit from PointerObjectExtensionBase in order to specify PointerType::NotNull
|
||||
class ReferencedByPointer : public pointer_utils::PointerObjectExtensionBase<
|
||||
pointer_details::NonPtrManager, PolymorphicContext, pointer_details::NoRTTI> {
|
||||
pointer_details::NonPtrManager, PolymorphicContext, pointer_details::NoRTTI> {
|
||||
public:
|
||||
ReferencedByPointer() : pointer_utils::PointerObjectExtensionBase<
|
||||
pointer_details::NonPtrManager, PolymorphicContext, pointer_details::NoRTTI>(
|
||||
PointerType::NotNull) {}
|
||||
pointer_details::NonPtrManager, PolymorphicContext, pointer_details::NoRTTI>(
|
||||
PointerType::NotNull) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
65
include/bitsery/ext/std_atomic.h
Normal file
65
include/bitsery/ext/std_atomic.h
Normal file
@@ -0,0 +1,65 @@
|
||||
//MIT License
|
||||
//
|
||||
//Copyright (c) 2020 Nick Renieris
|
||||
//
|
||||
//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_STD_ATOMIC_H
|
||||
#define BITSERY_EXT_STD_ATOMIC_H
|
||||
|
||||
#include "../traits/core/traits.h"
|
||||
#include <atomic>
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
class StdAtomic {
|
||||
public:
|
||||
|
||||
template<typename Ser, typename T, typename Fnc>
|
||||
void serialize(Ser& ser, const std::atomic<T>& obj, Fnc&& fnc) const {
|
||||
auto res = obj.load();
|
||||
fnc(ser, res);
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserialize(Des& des, std::atomic<T>& obj, Fnc&& fnc) const {
|
||||
T res{};
|
||||
fnc(des, res);
|
||||
obj.store(res);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace traits {
|
||||
template<typename T>
|
||||
struct ExtensionTraits<ext::StdAtomic, std::atomic<T>> {
|
||||
using TValue = T;
|
||||
static constexpr bool SupportValueOverload = true;
|
||||
static constexpr bool SupportObjectOverload = false;
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif //BITSERY_EXT_STD_ATOMIC_H
|
||||
@@ -32,16 +32,16 @@ namespace bitsery {
|
||||
class StdDuration {
|
||||
public:
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Period, typename Fnc>
|
||||
void serialize(Ser&, Writer&, const std::chrono::duration<T, Period>& obj, Fnc&& fnc) const {
|
||||
template<typename Ser, typename T, typename Period, typename Fnc>
|
||||
void serialize(Ser& ser, const std::chrono::duration<T, Period>& obj, Fnc&& fnc) const {
|
||||
auto res = obj.count();
|
||||
fnc(res);
|
||||
fnc(ser, res);
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Period, typename Fnc>
|
||||
void deserialize(Des&, Reader&, std::chrono::duration<T, Period>& obj, Fnc&& fnc) const {
|
||||
template<typename Des, typename T, typename Period, typename Fnc>
|
||||
void deserialize(Des& des, std::chrono::duration<T, Period>& obj, Fnc&& fnc) const {
|
||||
T res{};
|
||||
fnc(res);
|
||||
fnc(des, res);
|
||||
obj = std::chrono::duration<T, Period>{res};
|
||||
}
|
||||
};
|
||||
@@ -49,18 +49,18 @@ namespace bitsery {
|
||||
class StdTimePoint {
|
||||
public:
|
||||
|
||||
template<typename Ser, typename Writer, typename Clock, typename T, typename Period, typename Fnc>
|
||||
void serialize(Ser&, Writer&, const std::chrono::time_point<Clock, std::chrono::duration<T, Period>>& obj,
|
||||
template<typename Ser, typename Clock, typename T, typename Period, typename Fnc>
|
||||
void serialize(Ser& ser, const std::chrono::time_point<Clock, std::chrono::duration<T, Period>>& obj,
|
||||
Fnc&& fnc) const {
|
||||
auto res = obj.time_since_epoch().count();
|
||||
fnc(res);
|
||||
fnc(ser, res);
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename Clock, typename T, typename Period, typename Fnc>
|
||||
void deserialize(Des&, Reader&, std::chrono::time_point<Clock, std::chrono::duration<T, Period>>& obj,
|
||||
template<typename Des, typename Clock, typename T, typename Period, typename Fnc>
|
||||
void deserialize(Des& des, std::chrono::time_point<Clock, std::chrono::duration<T, Period>>& obj,
|
||||
Fnc&& fnc) const {
|
||||
T res{};
|
||||
fnc(res);
|
||||
fnc(des, res);
|
||||
auto dur = std::chrono::duration<T, Period>{res};
|
||||
obj = std::chrono::time_point<Clock, std::chrono::duration<T, Period>>{dur};
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#define BITSERY_EXT_STD_MAP_H
|
||||
|
||||
#include "../traits/core/traits.h"
|
||||
#include "../details/adapter_utils.h"
|
||||
#include "../details/adapter_common.h"
|
||||
#include "../details/serialization_common.h"
|
||||
//we need this, so we could reserve for non ordered map
|
||||
#include <unordered_map>
|
||||
@@ -37,33 +37,34 @@ namespace bitsery {
|
||||
|
||||
constexpr explicit StdMap(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 {
|
||||
template<typename Ser, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, const T &obj, Fnc &&fnc) const {
|
||||
using TKey = typename T::key_type;
|
||||
using TValue = typename T::mapped_type;
|
||||
auto size = obj.size();
|
||||
assert(size <= _maxSize);
|
||||
details::writeSize(writer, size);
|
||||
details::writeSize(ser.adapter(), size);
|
||||
|
||||
for (auto &v:obj)
|
||||
fnc(const_cast<TKey &>(v.first), const_cast<TValue &>(v.second));
|
||||
fnc(ser, 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 {
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserialize(Des &des, 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);
|
||||
details::readSize(des.adapter(), size, _maxSize,
|
||||
std::integral_constant<bool, Des::TConfig::CheckDataErrors>{});
|
||||
obj.clear();
|
||||
reserve(obj, size);
|
||||
|
||||
auto hint = obj.begin();
|
||||
for (auto i = 0u; i < size; ++i) {
|
||||
auto key{bitsery::Access::create<TKey>()};
|
||||
auto value{bitsery::Access::create<TValue>()};
|
||||
fnc(key, value);
|
||||
auto key = bitsery::Access::create<TKey>();
|
||||
auto value = bitsery::Access::create<TValue>();
|
||||
fnc(des, key, value);
|
||||
hint = obj.emplace_hint(hint, std::move(key), std::move(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,24 +40,24 @@ namespace bitsery {
|
||||
*/
|
||||
explicit StdOptional(bool alignBeforeData=true):_alignBeforeData{alignBeforeData} {}
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &, const std::optional<T> &obj, Fnc &&fnc) const {
|
||||
template<typename Ser, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, const std::optional<T> &obj, Fnc &&fnc) const {
|
||||
ser.boolValue(static_cast<bool>(obj));
|
||||
if (_alignBeforeData)
|
||||
ser.align();
|
||||
ser.adapter().align();
|
||||
if (obj)
|
||||
fnc(const_cast<T&>(*obj));
|
||||
fnc(ser, const_cast<T&>(*obj));
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &des, Reader &, std::optional<T> &obj, Fnc &&fnc) const {
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserialize(Des &des, std::optional<T> &obj, Fnc &&fnc) const {
|
||||
bool exists{};
|
||||
des.boolValue(exists);
|
||||
if (_alignBeforeData)
|
||||
des.align();
|
||||
des.adapter().align();
|
||||
if (exists) {
|
||||
obj = ::bitsery::Access::create<T>();
|
||||
fnc(*obj);
|
||||
fnc(des, *obj);
|
||||
} else {
|
||||
obj = std::nullopt;
|
||||
}
|
||||
|
||||
@@ -71,24 +71,24 @@ namespace bitsery {
|
||||
explicit StdQueue(size_t maxSize):_maxSize{maxSize} {};
|
||||
|
||||
//for queue
|
||||
template<typename Ser, typename Writer, typename T, typename C, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &, const std::queue<T,C> &obj, Fnc &&fnc) const {
|
||||
template<typename Ser, typename T, typename C, typename Fnc>
|
||||
void serialize(Ser &ser, const std::queue<T,C> &obj, Fnc &&fnc) const {
|
||||
ser.container(QueueCnt<T,C>::getContainer(obj), _maxSize, std::forward<Fnc>(fnc));
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename C, typename Fnc>
|
||||
void deserialize(Des &des, Reader &, std::queue<T,C> &obj, Fnc &&fnc) const {
|
||||
template<typename Des, typename T, typename C, typename Fnc>
|
||||
void deserialize(Des &des, std::queue<T,C> &obj, Fnc &&fnc) const {
|
||||
des.container(QueueCnt<T,C>::getContainer(obj), _maxSize, std::forward<Fnc>(fnc));
|
||||
}
|
||||
|
||||
//for priority_queue
|
||||
template<typename Ser, typename Writer, typename T, typename C, typename Comp, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &, const std::priority_queue<T,C, Comp> &obj, Fnc &&fnc) const {
|
||||
template<typename Ser, typename T, typename C, typename Comp, typename Fnc>
|
||||
void serialize(Ser &ser, const std::priority_queue<T,C, Comp> &obj, Fnc &&fnc) const {
|
||||
ser.container(PriorityQueueCnt<T,C, Comp>::getContainer(obj), _maxSize, std::forward<Fnc>(fnc));
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename C, typename Comp, typename Fnc>
|
||||
void deserialize(Des &des, Reader &, std::priority_queue<T,C, Comp> &obj, Fnc &&fnc) const {
|
||||
template<typename Des, typename T, typename C, typename Comp, typename Fnc>
|
||||
void deserialize(Des &des, std::priority_queue<T,C, Comp> &obj, Fnc &&fnc) const {
|
||||
des.container(PriorityQueueCnt<T,C, Comp>::getContainer(obj), _maxSize, std::forward<Fnc>(fnc));
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#define BITSERY_EXT_STD_SET_H
|
||||
|
||||
#include <cassert>
|
||||
#include "../details/adapter_utils.h"
|
||||
#include "../details/adapter_common.h"
|
||||
#include "../details/serialization_common.h"
|
||||
//we need this, so we could reserve for non ordered set
|
||||
#include <unordered_set>
|
||||
@@ -37,29 +37,29 @@ namespace bitsery {
|
||||
|
||||
constexpr explicit StdSet(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 {
|
||||
template<typename Ser, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, const T &obj, Fnc &&fnc) const {
|
||||
using TKey = typename T::key_type;
|
||||
auto size = obj.size();
|
||||
assert(size <= _maxSize);
|
||||
details::writeSize(writer, size);
|
||||
details::writeSize(ser.adapter(), size);
|
||||
|
||||
for (auto &v:obj)
|
||||
fnc(const_cast<TKey &>(v));
|
||||
fnc(ser, const_cast<TKey &>(v));
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &, Reader &reader, T &obj, Fnc &&fnc) const {
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserialize(Des &des, T &obj, Fnc &&fnc) const {
|
||||
using TKey = typename T::key_type;
|
||||
|
||||
size_t size{};
|
||||
details::readSize(reader, size, _maxSize);
|
||||
details::readSize(des.adapter(), size, _maxSize, std::integral_constant<bool, Des::TConfig::CheckDataErrors>{});
|
||||
obj.clear();
|
||||
reserve(obj, size);
|
||||
auto hint = obj.begin();
|
||||
for (auto i = 0u; i < size; ++i) {
|
||||
auto key{bitsery::Access::create<TKey>()};
|
||||
fnc(key);
|
||||
auto key = bitsery::Access::create<TKey>();
|
||||
fnc(des, key);
|
||||
hint = obj.emplace_hint(hint, std::move(key));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,16 +46,16 @@ namespace bitsery {
|
||||
|
||||
using TElement = typename T::element_type;
|
||||
|
||||
template <typename TDeleter>
|
||||
static TElement *getPtr(std::unique_ptr<TElement, TDeleter> &obj) {
|
||||
template<typename TDeleter>
|
||||
static TElement* getPtr(std::unique_ptr<TElement, TDeleter>& obj) {
|
||||
return obj.get();
|
||||
}
|
||||
|
||||
static TElement *getPtr(std::shared_ptr<TElement> &obj) {
|
||||
static TElement* getPtr(std::shared_ptr<TElement>& obj) {
|
||||
return obj.get();
|
||||
}
|
||||
|
||||
static TElement *getPtr(std::weak_ptr<TElement> &obj) {
|
||||
static TElement* getPtr(std::weak_ptr<TElement>& obj) {
|
||||
if (auto ptr = obj.lock())
|
||||
return ptr.get();
|
||||
return nullptr;
|
||||
@@ -69,34 +69,103 @@ namespace bitsery {
|
||||
: PointerOwnershipType::SharedObserver;
|
||||
}
|
||||
|
||||
static void clear(T &obj) {
|
||||
template<typename TDeleter>
|
||||
static void create(std::unique_ptr<TElement, TDeleter>& obj, pointer_utils::PolyAllocWithTypeId alloc,
|
||||
size_t typeId) {
|
||||
obj.reset(alloc.newObject<TElement>(typeId));
|
||||
}
|
||||
|
||||
template<typename TDeleter>
|
||||
static void createPolymorphic(std::unique_ptr<TElement, TDeleter>& obj, pointer_utils::PolyAllocWithTypeId alloc,
|
||||
const std::shared_ptr<PolymorphicHandlerBase>& handler) {
|
||||
obj.reset(static_cast<TElement*>(handler->create(alloc)));
|
||||
}
|
||||
|
||||
template<typename TDel>
|
||||
static void destroy(std::unique_ptr<TElement, TDel>& obj, pointer_utils::PolyAllocWithTypeId alloc, size_t typeId) {
|
||||
auto ptr = obj.release();
|
||||
alloc.deleteObject(ptr, typeId);
|
||||
}
|
||||
|
||||
template<typename TDel>
|
||||
static void destroyPolymorphic(std::unique_ptr<TElement, TDel>& obj, pointer_utils::PolyAllocWithTypeId alloc,
|
||||
const std::shared_ptr<PolymorphicHandlerBase>& handler) {
|
||||
auto ptr = obj.release();
|
||||
handler->destroy(alloc, ptr);
|
||||
}
|
||||
|
||||
static void destroy(std::shared_ptr<TElement>& obj, MemResourceBase*, size_t) {
|
||||
obj.reset();
|
||||
}
|
||||
|
||||
static void assign(T &obj, TElement *valuePtr) {
|
||||
obj.reset(valuePtr);
|
||||
static void destroyPolymorphic(std::shared_ptr<TElement>& obj, MemResourceBase*,
|
||||
const std::shared_ptr<PolymorphicHandlerBase>&) {
|
||||
obj.reset();
|
||||
}
|
||||
|
||||
//this is used, when old object exists and is the same type
|
||||
static std::unique_ptr<pointer_utils::PointerSharedStateBase> saveToSharedState(T &obj) {
|
||||
auto state = new SharedPtrSharedState{};
|
||||
//to work with weak_ptr and shared_ptr create new std::shared_ptr
|
||||
state->obj = std::shared_ptr<TElement>(obj);
|
||||
return std::unique_ptr<pointer_utils::PointerSharedStateBase>{state};
|
||||
static void destroy(std::weak_ptr<TElement>& obj, MemResourceBase*, size_t) {
|
||||
obj.reset();
|
||||
}
|
||||
|
||||
//this is used, when old object doesn't exists or is not the same type
|
||||
static std::unique_ptr<pointer_utils::PointerSharedStateBase> createSharedState(TElement *valuePtr) {
|
||||
auto state = new SharedPtrSharedState{};
|
||||
state->obj = std::shared_ptr<TElement>(valuePtr);
|
||||
return std::unique_ptr<pointer_utils::PointerSharedStateBase>{state};
|
||||
static void destroyPolymorphic(std::weak_ptr<TElement>& obj, MemResourceBase*,
|
||||
const std::shared_ptr<PolymorphicHandlerBase>&) {
|
||||
obj.reset();
|
||||
}
|
||||
|
||||
static void loadFromSharedState(pointer_utils::PointerSharedStateBase *ctx, T &obj) {
|
||||
auto state = dynamic_cast<SharedPtrSharedState *>(ctx);
|
||||
// define a type that will store shared state for shared and weak ptrs
|
||||
using TSharedState = SharedPtrSharedState;
|
||||
|
||||
static void createShared(TSharedState& state,
|
||||
std::shared_ptr<TElement>& obj, MemResourceBase* memResource, size_t typeId) {
|
||||
// capture deleter parameters by value
|
||||
pointer_utils::PolyAllocWithTypeId alloc{memResource};
|
||||
obj.reset(alloc.newObject<TElement>(typeId), [alloc, typeId](TElement* data) {
|
||||
alloc.deleteObject(data, typeId);
|
||||
}, pointer_utils::StdPolyAlloc<TElement>(memResource));
|
||||
state.obj = obj;
|
||||
}
|
||||
|
||||
static void createSharedPolymorphic(TSharedState& state,
|
||||
std::shared_ptr<TElement>& obj, MemResourceBase* memResource,
|
||||
const std::shared_ptr<PolymorphicHandlerBase>& handler) {
|
||||
// capture deleter parameters by value
|
||||
pointer_utils::PolyAllocWithTypeId alloc{memResource};
|
||||
obj.reset(static_cast<TElement*>(handler->create(alloc)), [alloc, handler](TElement* data) {
|
||||
handler->destroy(alloc, data);
|
||||
}, pointer_utils::StdPolyAlloc<TElement>(memResource));
|
||||
state.obj = obj;
|
||||
}
|
||||
|
||||
static void createShared(TSharedState& state,
|
||||
std::weak_ptr<TElement>& obj, MemResourceBase* memResource, size_t typeId) {
|
||||
pointer_utils::PolyAllocWithTypeId alloc{memResource};
|
||||
std::shared_ptr<TElement> res(alloc.newObject<TElement>(typeId),[alloc, typeId](TElement* data) {
|
||||
alloc.deleteObject(data, typeId);
|
||||
}, pointer_utils::StdPolyAlloc<TElement>(memResource));
|
||||
obj = res;
|
||||
state.obj = res;
|
||||
}
|
||||
|
||||
static void createSharedPolymorphic(TSharedState& state,
|
||||
std::weak_ptr<TElement>& obj, MemResourceBase* memResource,
|
||||
const std::shared_ptr<PolymorphicHandlerBase>& handler) {
|
||||
pointer_utils::PolyAllocWithTypeId alloc{memResource};
|
||||
std::shared_ptr<TElement> res(static_cast<TElement*>(handler->create(alloc)),
|
||||
[alloc, handler](TElement* data) {
|
||||
handler->destroy(alloc, data);
|
||||
}, pointer_utils::StdPolyAlloc<TElement>(memResource));
|
||||
obj = res;
|
||||
state.obj = res;
|
||||
}
|
||||
|
||||
static void saveToSharedState(TSharedState& state, T& obj) {
|
||||
state.obj = std::shared_ptr<TElement>(obj);
|
||||
}
|
||||
|
||||
static void loadFromSharedState(TSharedState& state, T& obj) {
|
||||
//reinterpret_pointer_cast is only since c++17
|
||||
auto p = reinterpret_cast<TElement *>(state->obj.get());
|
||||
obj = std::shared_ptr<TElement>(state->obj, p);
|
||||
auto p = reinterpret_cast<TElement*>(state.obj.get());
|
||||
obj = std::shared_ptr<TElement>(state.obj, p);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -104,7 +173,7 @@ namespace bitsery {
|
||||
|
||||
template<typename RTTI>
|
||||
using StdSmartPtrBase = pointer_utils::PointerObjectExtensionBase<
|
||||
smart_ptr_details::SmartPtrOwnerManager, PolymorphicContext, RTTI>;
|
||||
smart_ptr_details::SmartPtrOwnerManager, PolymorphicContext, RTTI>;
|
||||
|
||||
//helper type for convienience
|
||||
using StdSmartPtr = StdSmartPtrBase<StandardRTTI>;
|
||||
|
||||
@@ -53,13 +53,13 @@ namespace bitsery {
|
||||
public:
|
||||
explicit StdStack(size_t maxSize):_maxSize{maxSize} {};
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename C, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &, const std::stack<T,C> &obj, Fnc &&fnc) const {
|
||||
template<typename Ser, typename T, typename C, typename Fnc>
|
||||
void serialize(Ser &ser, const std::stack<T,C> &obj, Fnc &&fnc) const {
|
||||
ser.container(StackCnt<T,C>::getContainer(obj), _maxSize, std::forward<Fnc>(fnc));
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename C, typename Fnc>
|
||||
void deserialize(Des &des, Reader &, std::stack<T,C> &obj, Fnc &&fnc) const {
|
||||
template<typename Des, typename T, typename C, typename Fnc>
|
||||
void deserialize(Des &des, std::stack<T,C> &obj, Fnc &&fnc) const {
|
||||
des.container(StackCnt<T,C>::getContainer(obj), _maxSize, std::forward<Fnc>(fnc));
|
||||
}
|
||||
|
||||
|
||||
@@ -36,13 +36,13 @@ namespace bitsery {
|
||||
class StdTuple : public details::CompositeTypeOverloadsUtils<std::tuple, Overloads...> {
|
||||
public:
|
||||
|
||||
template<typename Ser, typename Writer, typename Fnc, typename ...Ts>
|
||||
void serialize(Ser& ser, Writer&, const std::tuple<Ts...>& obj, Fnc&&) const {
|
||||
template<typename Ser, typename Fnc, typename ...Ts>
|
||||
void serialize(Ser& ser, const std::tuple<Ts...>& obj, Fnc&&) const {
|
||||
serializeAll(ser, const_cast<std::tuple<Ts...>&>(obj));
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename Fnc, typename ...Ts>
|
||||
void deserialize(Des& des, Reader&, std::tuple<Ts...>& obj, Fnc&&) const {
|
||||
template<typename Des, typename Fnc, typename ...Ts>
|
||||
void deserialize(Des& des, std::tuple<Ts...>& obj, Fnc&&) const {
|
||||
serializeAll(des, obj);
|
||||
}
|
||||
|
||||
|
||||
@@ -36,21 +36,21 @@ namespace bitsery {
|
||||
class StdVariant : public details::CompositeTypeOverloadsUtils<std::variant, Overloads...> {
|
||||
public:
|
||||
|
||||
template<typename Ser, typename Writer, typename Fnc, typename ...Ts>
|
||||
void serialize(Ser& ser, Writer& writer, const std::variant<Ts...>& obj, Fnc&&) const {
|
||||
template<typename Ser, typename Fnc, typename ...Ts>
|
||||
void serialize(Ser& ser, const std::variant<Ts...>& obj, Fnc&&) const {
|
||||
auto index = obj.index();
|
||||
assert(index != std::variant_npos);
|
||||
details::writeSize(writer, index);
|
||||
details::writeSize(ser.adapter(), index);
|
||||
this->execIndex(index, const_cast<std::variant<Ts...>&>(obj), [this, &ser](auto& data, auto index) {
|
||||
constexpr size_t Index = decltype(index)::value;
|
||||
this->serializeType(ser, std::get<Index>(data));
|
||||
});
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename Fnc, typename ...Ts>
|
||||
void deserialize(Des& des, Reader& reader, std::variant<Ts...>& obj, Fnc&&) const {
|
||||
template<typename Des, typename Fnc, typename ...Ts>
|
||||
void deserialize(Des& des, std::variant<Ts...>& obj, Fnc&&) const {
|
||||
size_t index{};
|
||||
details::readSize(reader, index, sizeof...(Ts));
|
||||
details::readSize(des.adapter(), index, sizeof...(Ts), std::integral_constant<bool, Des::TConfig::CheckDataErrors>{});
|
||||
this->execIndex(index, obj, [this, &des](auto& data, auto index) {
|
||||
constexpr size_t Index = decltype(index)::value;
|
||||
using TElem = typename std::variant_alternative<Index, std::variant<Ts...>>::type;
|
||||
|
||||
171
include/bitsery/ext/utils/memory_resource.h
Normal file
171
include/bitsery/ext/utils/memory_resource.h
Normal file
@@ -0,0 +1,171 @@
|
||||
//MIT License
|
||||
//
|
||||
//Copyright (c) 2018 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_MEMORY_RESOURCE_H
|
||||
#define BITSERY_EXT_MEMORY_RESOURCE_H
|
||||
|
||||
#include "../../details/serialization_common.h"
|
||||
#include <new>
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
// these are very similar to c++17 polymorphic allocator and memory resource classes
|
||||
// but i don't want to enforce users to use c++17 if they want to use pointers
|
||||
// plus this has additional information from RTTI about runtime type information,
|
||||
// might be useful working with polymorphic types.
|
||||
// The same memory resource is used to allocate internal data in various contexts,
|
||||
// (typeId is always 0 for internal data allocation in contexts).
|
||||
|
||||
class MemResourceBase {
|
||||
public:
|
||||
virtual void* allocate(size_t bytes, size_t alignment, size_t typeId) = 0;
|
||||
|
||||
virtual void deallocate(void* ptr, size_t bytes, size_t alignment, size_t typeId) noexcept = 0;
|
||||
|
||||
virtual ~MemResourceBase() noexcept = default;
|
||||
};
|
||||
|
||||
// default implementation for MemResourceBase using new and delete
|
||||
class MemResourceNewDelete final: public MemResourceBase {
|
||||
public:
|
||||
inline void* allocate(size_t bytes, size_t /*alignment*/, size_t /*typeId*/) final {
|
||||
return (::operator new(bytes));
|
||||
}
|
||||
|
||||
inline void
|
||||
deallocate(void* ptr, size_t /*bytes*/, size_t /*alignment*/, size_t /*typeId*/) noexcept final {
|
||||
(::operator delete(ptr));
|
||||
}
|
||||
|
||||
~MemResourceNewDelete() noexcept final = default;
|
||||
};
|
||||
|
||||
// these classes are used internally by bitsery extensions and and pointer utils
|
||||
namespace pointer_utils {
|
||||
// this is helper class that stores memory resource and knows how to construct/destroy objects
|
||||
// capture this by value for custom deleters, because during deserialization mem resource can be changed
|
||||
class PolyAllocWithTypeId final {
|
||||
public:
|
||||
|
||||
constexpr PolyAllocWithTypeId(MemResourceBase* memResource = nullptr)
|
||||
:_resource{memResource} {}
|
||||
|
||||
template<typename T>
|
||||
T* allocate(size_t n, size_t typeId) const {
|
||||
const auto bytes = sizeof(T) * n;
|
||||
constexpr auto alignment = std::alignment_of<T>::value;
|
||||
void* ptr = _resource
|
||||
? _resource->allocate(bytes, alignment, typeId)
|
||||
: ext::MemResourceNewDelete{}.allocate(bytes, alignment, typeId);
|
||||
return static_cast<T*>(ptr);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void deallocate(T* ptr, size_t n, size_t typeId) const noexcept {
|
||||
const auto bytes = sizeof(T) * n;
|
||||
constexpr auto alignment = std::alignment_of<T>::value;
|
||||
_resource
|
||||
? _resource->deallocate(ptr, bytes, alignment, typeId)
|
||||
: ext::MemResourceNewDelete{}.deallocate(ptr, bytes, alignment, typeId);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T* newObject(size_t typeId) const {
|
||||
auto ptr = allocate<T>(1, typeId);
|
||||
return ::bitsery::Access::create<T>(ptr);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void deleteObject(T* obj, size_t typeId) const {
|
||||
obj->~T();
|
||||
deallocate(obj, 1, typeId);
|
||||
}
|
||||
|
||||
void setMemResource(ext::MemResourceBase* resource) {
|
||||
_resource = resource;
|
||||
}
|
||||
|
||||
ext::MemResourceBase* getMemResource() const {
|
||||
return _resource;
|
||||
}
|
||||
|
||||
bool operator==(const PolyAllocWithTypeId& rhs) const noexcept {
|
||||
return _resource == rhs._resource;
|
||||
}
|
||||
|
||||
bool operator!=(const PolyAllocWithTypeId& rhs) const noexcept {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
ext::MemResourceBase* _resource;
|
||||
};
|
||||
|
||||
// this is very similar to c++17 PolymorphicAllocator
|
||||
// it just wraps our PolyAllocWithTypeId and pass 0 as typeId
|
||||
// and defines core functions for c++ Allocator concept,
|
||||
template<class T>
|
||||
class StdPolyAlloc {
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
explicit constexpr StdPolyAlloc(MemResourceBase* memResource)
|
||||
:_alloc{memResource} {}
|
||||
explicit constexpr StdPolyAlloc(PolyAllocWithTypeId alloc) : _alloc{alloc} {}
|
||||
|
||||
template <typename U>
|
||||
friend class StdPolyAlloc;
|
||||
|
||||
template<class U>
|
||||
constexpr explicit StdPolyAlloc(const StdPolyAlloc<U>& other) noexcept
|
||||
:_alloc{other._alloc} {
|
||||
}
|
||||
|
||||
T* allocate(std::size_t n) {
|
||||
return _alloc.allocate<T>(n, 0);
|
||||
}
|
||||
|
||||
void deallocate(T* p, std::size_t n) noexcept {
|
||||
return _alloc.deallocate(p, n, 0);
|
||||
}
|
||||
|
||||
template<class U>
|
||||
friend bool operator==(const StdPolyAlloc<T>& lhs,
|
||||
const StdPolyAlloc<U>& rhs) noexcept {
|
||||
return lhs._alloc == rhs._alloc;
|
||||
}
|
||||
|
||||
template<class U>
|
||||
friend bool operator!=(const StdPolyAlloc<T>& lhs,
|
||||
const StdPolyAlloc<U>& rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
PolyAllocWithTypeId _alloc;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif //BITSERY_EXT_MEMORY_RESOURCE_H
|
||||
@@ -28,14 +28,15 @@
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include "../../details/adapter_utils.h"
|
||||
#include "polymorphism_utils.h"
|
||||
#include "../../details/adapter_common.h"
|
||||
#include "../../details/serialization_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
//change name
|
||||
enum class PointerType {
|
||||
enum class PointerType : uint8_t {
|
||||
Nullable,
|
||||
NotNull
|
||||
};
|
||||
@@ -54,9 +55,6 @@ namespace bitsery {
|
||||
SharedObserver
|
||||
};
|
||||
|
||||
//forward declaration
|
||||
class PointerLinkingContext;
|
||||
|
||||
namespace pointer_utils {
|
||||
|
||||
//this class is used to store context for shared ptr owners
|
||||
@@ -64,11 +62,23 @@ namespace bitsery {
|
||||
virtual ~PointerSharedStateBase() = default;
|
||||
};
|
||||
|
||||
struct PointerSharedStateDeleter {
|
||||
PointerSharedStateDeleter() = default;
|
||||
explicit PointerSharedStateDeleter(MemResourceBase* memResource)
|
||||
:_memResource{memResource} {}
|
||||
void operator()(PointerSharedStateBase* data) const {
|
||||
data->~PointerSharedStateBase();
|
||||
StdPolyAlloc<PointerSharedStateBase> alloc{_memResource};
|
||||
alloc.deallocate(data, 1);
|
||||
}
|
||||
MemResourceBase* _memResource = nullptr;
|
||||
};
|
||||
|
||||
//PLC info is internal classes for serializer, and deserializer
|
||||
struct PLCInfo {
|
||||
explicit PLCInfo(PointerOwnershipType ownershipType_)
|
||||
: ownershipType{ownershipType_},
|
||||
isSharedProcessed{false} {};
|
||||
: ownershipType{ownershipType_},
|
||||
isSharedProcessed{false} {};
|
||||
PointerOwnershipType ownershipType;
|
||||
bool isSharedProcessed;
|
||||
|
||||
@@ -82,7 +92,8 @@ namespace bitsery {
|
||||
return;
|
||||
}
|
||||
//only shared ownership can get here multiple times
|
||||
assert(ptrType == PointerOwnershipType::SharedOwner || ptrType == PointerOwnershipType::SharedObserver);
|
||||
assert(ptrType == PointerOwnershipType::SharedOwner ||
|
||||
ptrType == PointerOwnershipType::SharedObserver);
|
||||
//check if need to update to SharedOwner
|
||||
if (ptrType == PointerOwnershipType::SharedOwner)
|
||||
ownershipType = ptrType;
|
||||
@@ -91,33 +102,39 @@ namespace bitsery {
|
||||
}
|
||||
};
|
||||
|
||||
struct PLCInfoSerializer: PLCInfo {
|
||||
struct PLCInfoSerializer : PLCInfo {
|
||||
PLCInfoSerializer(size_t id_, PointerOwnershipType ownershipType_)
|
||||
: PLCInfo(ownershipType_), id{id_} {}
|
||||
: PLCInfo(ownershipType_), id{id_} {}
|
||||
|
||||
size_t id;
|
||||
};
|
||||
|
||||
struct PLCInfoDeserializer : PLCInfo {
|
||||
PLCInfoDeserializer(void *ptr, PointerOwnershipType ownershipType_)
|
||||
: PLCInfo(ownershipType_),
|
||||
ownerPtr{ptr} {};
|
||||
PLCInfoDeserializer(void* ptr, PointerOwnershipType ownershipType_, MemResourceBase* memResource_)
|
||||
: PLCInfo(ownershipType_),
|
||||
ownerPtr{ptr},
|
||||
memResource{memResource_},
|
||||
observersList{StdPolyAlloc<std::reference_wrapper<void*>>{memResource_}} {};
|
||||
|
||||
//need to override these explicitly because we have pointer member
|
||||
PLCInfoDeserializer(const PLCInfoDeserializer&) = delete;
|
||||
PLCInfoDeserializer(PLCInfoDeserializer&&) = default;
|
||||
PLCInfoDeserializer& operator =(const PLCInfoDeserializer&) = delete;
|
||||
PLCInfoDeserializer& operator =(PLCInfoDeserializer&&) = default;
|
||||
|
||||
void processOwner(void *ptr) {
|
||||
PLCInfoDeserializer(PLCInfoDeserializer&&) = default;
|
||||
|
||||
PLCInfoDeserializer& operator=(const PLCInfoDeserializer&) = delete;
|
||||
|
||||
PLCInfoDeserializer& operator=(PLCInfoDeserializer&&) = default;
|
||||
|
||||
void processOwner(void* ptr) {
|
||||
ownerPtr = ptr;
|
||||
assert(ownershipType != PointerOwnershipType::Observer);
|
||||
for (auto &o:observersList)
|
||||
for (auto& o:observersList)
|
||||
o.get() = ptr;
|
||||
observersList.clear();
|
||||
observersList.shrink_to_fit();
|
||||
}
|
||||
|
||||
void processObserver(void *(&ptr)) {
|
||||
void processObserver(void* (& ptr)) {
|
||||
if (ownerPtr) {
|
||||
ptr = ownerPtr;
|
||||
} else {
|
||||
@@ -125,30 +142,32 @@ namespace bitsery {
|
||||
}
|
||||
}
|
||||
|
||||
void *ownerPtr;
|
||||
std::vector<std::reference_wrapper<void *>> observersList{};
|
||||
std::unique_ptr<PointerSharedStateBase> sharedState{};
|
||||
void* ownerPtr;
|
||||
MemResourceBase* memResource;
|
||||
std::vector<std::reference_wrapper<void*>,
|
||||
StdPolyAlloc<std::reference_wrapper<void*>>> observersList;
|
||||
std::unique_ptr<PointerSharedStateBase, PointerSharedStateDeleter> sharedState{};
|
||||
};
|
||||
|
||||
class PointerLinkingContextSerialization {
|
||||
public:
|
||||
explicit PointerLinkingContextSerialization()
|
||||
: _currId{0},
|
||||
_ptrMap{} {}
|
||||
explicit PointerLinkingContextSerialization(MemResourceBase* memResource = nullptr)
|
||||
: _currId{0},
|
||||
_ptrMap{StdPolyAlloc<std::pair<const void* const, PLCInfoSerializer>>{memResource}} {}
|
||||
|
||||
PointerLinkingContextSerialization(const PointerLinkingContextSerialization &) = delete;
|
||||
PointerLinkingContextSerialization(const PointerLinkingContextSerialization&) = delete;
|
||||
|
||||
PointerLinkingContextSerialization &operator=(const PointerLinkingContextSerialization &) = delete;
|
||||
PointerLinkingContextSerialization& operator=(const PointerLinkingContextSerialization&) = delete;
|
||||
|
||||
PointerLinkingContextSerialization(PointerLinkingContextSerialization &&) = default;
|
||||
PointerLinkingContextSerialization(PointerLinkingContextSerialization&&) = default;
|
||||
|
||||
PointerLinkingContextSerialization &operator=(PointerLinkingContextSerialization &&) = default;
|
||||
PointerLinkingContextSerialization& operator=(PointerLinkingContextSerialization&&) = default;
|
||||
|
||||
~PointerLinkingContextSerialization() = default;
|
||||
|
||||
const PLCInfoSerializer &getInfoByPtr(const void *ptr, PointerOwnershipType ptrType) {
|
||||
const PLCInfoSerializer& getInfoByPtr(const void* ptr, PointerOwnershipType ptrType) {
|
||||
auto res = _ptrMap.emplace(ptr, PLCInfoSerializer{_currId + 1u, ptrType});
|
||||
auto &ptrInfo = res.first->second;
|
||||
auto& ptrInfo = res.first->second;
|
||||
if (res.second) {
|
||||
++_currId;
|
||||
return ptrInfo;
|
||||
@@ -161,7 +180,7 @@ namespace bitsery {
|
||||
//we cannot serialize pointers, if we haven't serialized objects themselves
|
||||
bool isPointerSerializationValid() const {
|
||||
return std::all_of(_ptrMap.begin(), _ptrMap.end(),
|
||||
[](const std::pair<const void *, PLCInfoSerializer> &p) {
|
||||
[](const std::pair<const void*, PLCInfoSerializer>& p) {
|
||||
return p.second.ownershipType == PointerOwnershipType::SharedOwner ||
|
||||
p.second.ownershipType == PointerOwnershipType::Owner;
|
||||
});
|
||||
@@ -169,218 +188,299 @@ namespace bitsery {
|
||||
|
||||
private:
|
||||
size_t _currId;
|
||||
std::unordered_map<const void *, PLCInfoSerializer> _ptrMap;
|
||||
|
||||
std::unordered_map<const void*, PLCInfoSerializer,
|
||||
std::hash<const void*>, std::equal_to<const void*>,
|
||||
StdPolyAlloc<std::pair<const void* const, PLCInfoSerializer>>
|
||||
> _ptrMap;
|
||||
};
|
||||
|
||||
class PointerLinkingContextDeserialization {
|
||||
public:
|
||||
explicit PointerLinkingContextDeserialization()
|
||||
: _idMap{} {}
|
||||
explicit PointerLinkingContextDeserialization(MemResourceBase* memResource = nullptr)
|
||||
: _memResource{memResource},
|
||||
_idMap{StdPolyAlloc<std::pair<const size_t, PLCInfoDeserializer>>{memResource}} {}
|
||||
|
||||
PointerLinkingContextDeserialization(const PointerLinkingContextDeserialization &) = delete;
|
||||
PointerLinkingContextDeserialization(const PointerLinkingContextDeserialization&) = delete;
|
||||
|
||||
PointerLinkingContextDeserialization &operator=(const PointerLinkingContextDeserialization &) = delete;
|
||||
PointerLinkingContextDeserialization& operator=(const PointerLinkingContextDeserialization&) = delete;
|
||||
|
||||
PointerLinkingContextDeserialization(PointerLinkingContextDeserialization &&) = default;
|
||||
PointerLinkingContextDeserialization(PointerLinkingContextDeserialization&&) = default;
|
||||
|
||||
PointerLinkingContextDeserialization &operator=(PointerLinkingContextDeserialization &&) = default;
|
||||
PointerLinkingContextDeserialization& operator=(PointerLinkingContextDeserialization&&) = default;
|
||||
|
||||
~PointerLinkingContextDeserialization() = default;
|
||||
|
||||
PLCInfoDeserializer &getInfoById(size_t id, PointerOwnershipType ptrType) {
|
||||
auto res = _idMap.emplace(id, PLCInfoDeserializer{nullptr, ptrType});
|
||||
auto &ptrInfo = res.first->second;
|
||||
PLCInfoDeserializer& getInfoById(size_t id, PointerOwnershipType ptrType) {
|
||||
auto res = _idMap.emplace(id, PLCInfoDeserializer{nullptr, ptrType, _memResource});
|
||||
auto& ptrInfo = res.first->second;
|
||||
if (!res.second)
|
||||
ptrInfo.update(ptrType);
|
||||
return ptrInfo;
|
||||
}
|
||||
|
||||
void clearSharedState() {
|
||||
for (auto &item: _idMap)
|
||||
for (auto& item: _idMap)
|
||||
item.second.sharedState.reset();
|
||||
}
|
||||
|
||||
//valid, when all pointers has owners
|
||||
bool isPointerDeserializationValid() const {
|
||||
return std::all_of(_idMap.begin(), _idMap.end(),
|
||||
[](const std::pair<const size_t, PLCInfoDeserializer> &p) {
|
||||
[](const std::pair<const size_t, PLCInfoDeserializer>& p) {
|
||||
return p.second.ownershipType == PointerOwnershipType::SharedOwner ||
|
||||
p.second.ownershipType == PointerOwnershipType::Owner;
|
||||
});
|
||||
}
|
||||
|
||||
MemResourceBase* getMemResource() noexcept {
|
||||
return _memResource;
|
||||
}
|
||||
|
||||
void setMemResource(MemResourceBase* resource) noexcept {
|
||||
_memResource = resource;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<size_t, PLCInfoDeserializer> _idMap;
|
||||
MemResourceBase* _memResource;
|
||||
std::unordered_map<size_t, PLCInfoDeserializer,
|
||||
std::hash<size_t>, std::equal_to<size_t>,
|
||||
StdPolyAlloc<std::pair<const size_t, PLCInfoDeserializer>>> _idMap;
|
||||
};
|
||||
}
|
||||
|
||||
//this class is for convenience
|
||||
class PointerLinkingContext :
|
||||
public pointer_utils::PointerLinkingContextSerialization,
|
||||
public pointer_utils::PointerLinkingContextDeserialization {
|
||||
public:
|
||||
explicit PointerLinkingContext(MemResourceBase* memResource = nullptr)
|
||||
:pointer_utils::PointerLinkingContextSerialization(memResource),
|
||||
pointer_utils::PointerLinkingContextDeserialization(memResource) {};
|
||||
|
||||
bool isValid() const {
|
||||
return isPointerSerializationValid() && isPointerDeserializationValid();
|
||||
}
|
||||
};
|
||||
|
||||
namespace pointer_utils {
|
||||
|
||||
template<template<typename> class TPtrManager,
|
||||
template<typename> class TPolymorphicContext, typename RTTI>
|
||||
template<typename> class TPolymorphicContext, typename RTTI>
|
||||
class PointerObjectExtensionBase {
|
||||
public:
|
||||
|
||||
explicit PointerObjectExtensionBase(PointerType ptrType = PointerType::Nullable) :
|
||||
_ptrType{ptrType} {}
|
||||
// helper types
|
||||
template<typename T>
|
||||
struct IsPolymorphic : std::integral_constant<bool,
|
||||
RTTI::template isPolymorphic<typename TPtrManager<T>::TElement>()> {
|
||||
};
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &w, const T &obj, Fnc &&fnc) const {
|
||||
template<PointerOwnershipType Value>
|
||||
using OwnershipType = std::integral_constant<PointerOwnershipType, Value>;
|
||||
|
||||
auto ptr = TPtrManager<T>::getPtr(const_cast<T &>(obj));
|
||||
|
||||
explicit PointerObjectExtensionBase(PointerType ptrType = PointerType::Nullable,
|
||||
MemResourceBase* resource = nullptr,
|
||||
bool resourcePropagate = false) :
|
||||
_ptrType{ptrType},
|
||||
_resourcePropagate{resourcePropagate},
|
||||
_resource{resource} {
|
||||
}
|
||||
|
||||
template<typename Ser, typename T, typename Fnc>
|
||||
void serialize(Ser& ser, const T& obj, Fnc&& fnc) const {
|
||||
|
||||
auto ptr = TPtrManager<T>::getPtr(const_cast<T&>(obj));
|
||||
if (ptr) {
|
||||
auto ctx = ser.template context<PointerLinkingContext>();
|
||||
assert(ctx != nullptr);
|
||||
auto &ptrInfo = ctx->getInfoByPtr(getBasePtr(ptr), TPtrManager<T>::getOwnership());
|
||||
details::writeSize(w, ptrInfo.id);
|
||||
auto& ctx = ser.template context<pointer_utils::PointerLinkingContextSerialization>();
|
||||
auto& ptrInfo = ctx.getInfoByPtr(getBasePtr(ptr), TPtrManager<T>::getOwnership());
|
||||
details::writeSize(ser.adapter(), ptrInfo.id);
|
||||
if (TPtrManager<T>::getOwnership() != PointerOwnershipType::Observer) {
|
||||
if (!ptrInfo.isSharedProcessed)
|
||||
serializeImpl(ser, ptr, std::forward<Fnc>(fnc), w, IsPolymorphic<T>{});
|
||||
serializeImpl(ser, ptr, std::forward<Fnc>(fnc), IsPolymorphic<T>{});
|
||||
}
|
||||
} else {
|
||||
assert(_ptrType == PointerType::Nullable);
|
||||
details::writeSize(w, 0);
|
||||
details::writeSize(ser.adapter(), 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &des, Reader &r, T &obj, Fnc &&fnc) const {
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserialize(Des& des, T& obj, Fnc&& fnc) const {
|
||||
size_t id{};
|
||||
details::readSize(r, id, std::numeric_limits<size_t>::max());
|
||||
details::readSize(des.adapter(), id, 0, std::false_type{});
|
||||
auto& ctx = des.template context<pointer_utils::PointerLinkingContextDeserialization>();
|
||||
auto prevResource = ctx.getMemResource();
|
||||
auto memResource = _resource ? _resource : prevResource;
|
||||
// if we have resource and propagate is true, then change current resource
|
||||
// so that deserializing nested pointers it will be used
|
||||
if (_resource && _resourcePropagate) {
|
||||
ctx.setMemResource(memResource);
|
||||
}
|
||||
if (id) {
|
||||
auto ctx = des.template context<PointerLinkingContext>();
|
||||
assert(ctx != nullptr);
|
||||
auto &ptrInfo = ctx->getInfoById(id, TPtrManager<T>::getOwnership());
|
||||
deserializeImpl(ptrInfo, des, obj, std::forward<Fnc>(fnc), r, IsPolymorphic<T>{},
|
||||
std::integral_constant<PointerOwnershipType, TPtrManager<T>::getOwnership()>{});
|
||||
auto& ptrInfo = ctx.getInfoById(id, TPtrManager<T>::getOwnership());
|
||||
deserializeImpl(memResource, ptrInfo, des, obj, std::forward<Fnc>(fnc), IsPolymorphic<T>{},
|
||||
OwnershipType<TPtrManager<T>::getOwnership()>{});
|
||||
} else {
|
||||
if (_ptrType == PointerType::Nullable) {
|
||||
TPtrManager<T>::clear(obj);
|
||||
if (auto ptr = TPtrManager<T>::getPtr(obj)) {
|
||||
destroyPtr(memResource, des, obj, IsPolymorphic<T>{});
|
||||
};
|
||||
} else
|
||||
r.setError(ReaderError::InvalidPointer);
|
||||
des.adapter().error(ReaderError::InvalidPointer);
|
||||
}
|
||||
if (_resource && _resourcePropagate) {
|
||||
ctx.setMemResource(prevResource);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<typename T>
|
||||
struct IsPolymorphic : std::integral_constant<bool,
|
||||
RTTI::template isPolymorphic<typename TPtrManager<T>::TElement>()> {
|
||||
};
|
||||
template<typename Des, typename TObj>
|
||||
void destroyPtr(MemResourceBase* memResource, Des& des, TObj& obj,
|
||||
std::true_type /*polymorphic*/) const {
|
||||
const auto& ctx = des.template context<TPolymorphicContext<RTTI>>();
|
||||
auto ptr = TPtrManager<TObj>::getPtr(obj);
|
||||
TPtrManager<TObj>::destroyPolymorphic(obj, memResource, ctx.getPolymorphicHandler(*ptr));
|
||||
}
|
||||
|
||||
template<typename Des, typename TObj>
|
||||
void destroyPtr(MemResourceBase* memResource, Des&, TObj& obj,
|
||||
std::false_type /*polymorphic*/) const {
|
||||
TPtrManager<TObj>::destroy(obj, memResource, RTTI::template get<typename TPtrManager<TObj>::TElement>());
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
const void *getBasePtr(const T *ptr) const {
|
||||
const void* getBasePtr(const T* ptr) const {
|
||||
// todo implement handling of types with virtual inheritance
|
||||
// this is required to correctly track same object, when one object is derived and other is base class
|
||||
// e.g. shared_ptr<Base> and weak_ptr<Derived> or pointer observer Base*
|
||||
return ptr;
|
||||
}
|
||||
|
||||
template<typename Ser, typename TPtr, typename Fnc, typename Writer>
|
||||
void serializeImpl(Ser &ser, TPtr &ptr, Fnc &&, Writer &w, std::true_type) const {
|
||||
const auto &ctx = ser.template context<TPolymorphicContext<RTTI>>();
|
||||
ctx->serialize(ser, w, *ptr);
|
||||
template<typename Ser, typename TPtr, typename Fnc>
|
||||
void serializeImpl(Ser& ser, TPtr& ptr, Fnc&&, std::true_type) const {
|
||||
const auto& ctx = ser.template context<TPolymorphicContext<RTTI>>();
|
||||
ctx.serialize(ser, *ptr);
|
||||
}
|
||||
|
||||
template<typename Ser, typename TPtr, typename Fnc, typename Writer>
|
||||
void serializeImpl(Ser &, TPtr &ptr, Fnc &&fnc, Writer &, std::false_type) const {
|
||||
fnc(*ptr);
|
||||
template<typename Ser, typename TPtr, typename Fnc>
|
||||
void serializeImpl(Ser& ser, TPtr& ptr, Fnc&& fnc, std::false_type) const {
|
||||
fnc(ser, *ptr);
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc, typename Reader>
|
||||
void deserializeImpl(PLCInfoDeserializer &ptrInfo, Des &des, T &obj, Fnc &&,
|
||||
Reader &r, std::true_type ,
|
||||
std::integral_constant<PointerOwnershipType, PointerOwnershipType::Owner>) const {
|
||||
const auto &ctx = des.template context<TPolymorphicContext<RTTI>>();
|
||||
ctx->deserialize(des, r, TPtrManager<T>::getPtr(obj),
|
||||
[&obj, this](typename TPtrManager<T>::TElement *valuePtr) {
|
||||
TPtrManager<T>::assign(obj, valuePtr);
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserializeImpl(MemResourceBase* memResource, PLCInfoDeserializer& ptrInfo, Des& des, T& obj, Fnc&&,
|
||||
std::true_type, OwnershipType<PointerOwnershipType::Owner>) const {
|
||||
const auto& ctx = des.template context<TPolymorphicContext<RTTI>>();
|
||||
ctx.deserialize(des, TPtrManager<T>::getPtr(obj),
|
||||
[&obj, this, memResource](
|
||||
const std::shared_ptr<PolymorphicHandlerBase>& handler) {
|
||||
TPtrManager<T>::createPolymorphic(obj, memResource, handler);
|
||||
return TPtrManager<T>::getPtr(obj);
|
||||
},
|
||||
[&obj, memResource, this](const std::shared_ptr<PolymorphicHandlerBase>& handler) {
|
||||
TPtrManager<T>::destroyPolymorphic(obj, memResource, handler);
|
||||
});
|
||||
ptrInfo.processOwner(TPtrManager<T>::getPtr(obj));
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc, typename Reader>
|
||||
void deserializeImpl(PLCInfoDeserializer &ptrInfo, Des &, T &obj, Fnc &&fnc,
|
||||
Reader &, std::false_type ,
|
||||
std::integral_constant<PointerOwnershipType, PointerOwnershipType::Owner>) const {
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserializeImpl(MemResourceBase* memResource, PLCInfoDeserializer& ptrInfo, Des& des, T& obj, Fnc&& fnc,
|
||||
std::false_type, OwnershipType<PointerOwnershipType::Owner>) const {
|
||||
auto ptr = TPtrManager<T>::getPtr(obj);
|
||||
if (ptr) {
|
||||
fnc(*ptr);
|
||||
fnc(des, *ptr);
|
||||
} else {
|
||||
ptr = ::bitsery::Access::createInHeap<typename TPtrManager<T>::TElement>();
|
||||
fnc(*ptr);
|
||||
TPtrManager<T>::assign(obj, ptr);
|
||||
TPtrManager<T>::create(obj, memResource, RTTI::template get<typename TPtrManager<T>::TElement>());
|
||||
ptr = TPtrManager<T>::getPtr(obj);
|
||||
fnc(des, *ptr);
|
||||
}
|
||||
ptrInfo.processOwner(ptr);
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc, typename Reader>
|
||||
void deserializeImpl(PLCInfoDeserializer &ptrInfo, Des &des, T &obj, Fnc &&,
|
||||
Reader &r, std::true_type ,
|
||||
std::integral_constant<PointerOwnershipType, PointerOwnershipType::SharedOwner>) const {
|
||||
auto &sharedState = ptrInfo.sharedState;
|
||||
if (!sharedState) {
|
||||
const auto &ctx = des.template context<TPolymorphicContext<RTTI>>();
|
||||
ctx->deserialize(des, r, TPtrManager<T>::getPtr(obj),
|
||||
[&obj, &sharedState](typename TPtrManager<T>::TElement *valuePtr) {
|
||||
sharedState = TPtrManager<T>::createSharedState(valuePtr);
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserializeImpl(MemResourceBase* memResource, PLCInfoDeserializer& ptrInfo, Des& des, T& obj, Fnc&&,
|
||||
std::true_type, OwnershipType<PointerOwnershipType::SharedOwner>) const {
|
||||
if (!ptrInfo.sharedState) {
|
||||
const auto& ctx = des.template context<TPolymorphicContext<RTTI>>();
|
||||
ctx.deserialize(des, TPtrManager<T>::getPtr(obj),
|
||||
[&obj, &ptrInfo, memResource, this](
|
||||
const std::shared_ptr<PolymorphicHandlerBase>& handler) {
|
||||
TPtrManager<T>::createSharedPolymorphic(
|
||||
createAndGetSharedStateObj<T>(ptrInfo),
|
||||
obj, memResource, handler);
|
||||
return TPtrManager<T>::getPtr(obj);
|
||||
},
|
||||
[&obj, memResource, this](const std::shared_ptr<PolymorphicHandlerBase>& handler) {
|
||||
TPtrManager<T>::destroyPolymorphic(obj, memResource, handler);
|
||||
});
|
||||
if (!sharedState)
|
||||
sharedState = TPtrManager<T>::saveToSharedState(obj);
|
||||
if (!ptrInfo.sharedState)
|
||||
TPtrManager<T>::saveToSharedState(createAndGetSharedStateObj<T>(ptrInfo), obj);
|
||||
}
|
||||
TPtrManager<T>::loadFromSharedState(sharedState.get(), obj);
|
||||
TPtrManager<T>::loadFromSharedState(getSharedStateObj<T>(ptrInfo), obj);
|
||||
ptrInfo.processOwner(TPtrManager<T>::getPtr(obj));
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc, typename Reader>
|
||||
void deserializeImpl(PLCInfoDeserializer &ptrInfo, Des &, T &obj, Fnc &&fnc,
|
||||
Reader &, std::false_type ,
|
||||
std::integral_constant<PointerOwnershipType, PointerOwnershipType::SharedOwner>) const {
|
||||
auto &sharedState = ptrInfo.sharedState;
|
||||
if (!sharedState) {
|
||||
if (auto ptr = TPtrManager<T>::getPtr(obj)) {
|
||||
fnc(*ptr);
|
||||
sharedState = TPtrManager<T>::saveToSharedState(obj);
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserializeImpl(MemResourceBase* memResource, PLCInfoDeserializer& ptrInfo, Des& des, T& obj, Fnc&& fnc,
|
||||
std::false_type, OwnershipType<PointerOwnershipType::SharedOwner>) const {
|
||||
if (!ptrInfo.sharedState) {
|
||||
auto ptr = TPtrManager<T>::getPtr(obj);
|
||||
if (ptr) {
|
||||
TPtrManager<T>::saveToSharedState(createAndGetSharedStateObj<T>(ptrInfo), obj);
|
||||
} else {
|
||||
auto res = ::bitsery::Access::createInHeap<typename TPtrManager<T>::TElement>();
|
||||
fnc(*res);
|
||||
sharedState = TPtrManager<T>::createSharedState(res);
|
||||
TPtrManager<T>::createShared(
|
||||
createAndGetSharedStateObj<T>(ptrInfo),
|
||||
obj, memResource, RTTI::template get<typename TPtrManager<T>::TElement>());
|
||||
ptr = TPtrManager<T>::getPtr(obj);
|
||||
}
|
||||
fnc(des, *ptr);
|
||||
}
|
||||
TPtrManager<T>::loadFromSharedState(sharedState.get(), obj);
|
||||
TPtrManager<T>::loadFromSharedState(getSharedStateObj<T>(ptrInfo), obj);
|
||||
ptrInfo.processOwner(TPtrManager<T>::getPtr(obj));
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc, typename Reader, typename isPolymorph>
|
||||
void deserializeImpl(PLCInfoDeserializer &ptrInfo, Des &des, T &obj, Fnc &&fnc,
|
||||
Reader &r, isPolymorph polymorph,
|
||||
std::integral_constant<PointerOwnershipType, PointerOwnershipType::SharedObserver>) const {
|
||||
deserializeImpl(ptrInfo, des, obj, fnc, r, polymorph,
|
||||
std::integral_constant<PointerOwnershipType, PointerOwnershipType::SharedOwner>{});
|
||||
template<typename Des, typename T, typename Fnc, typename isPolymorph>
|
||||
void deserializeImpl(MemResourceBase* memResource, PLCInfoDeserializer& ptrInfo, Des& des, T& obj,
|
||||
Fnc&& fnc, isPolymorph polymorph,
|
||||
OwnershipType<PointerOwnershipType::SharedObserver>) const {
|
||||
deserializeImpl(memResource, ptrInfo, des, obj, fnc, polymorph,
|
||||
OwnershipType<PointerOwnershipType::SharedOwner>{});
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc, typename Reader, typename isPolymorphic>
|
||||
void deserializeImpl(PLCInfoDeserializer &ptrInfo, Des &, T &obj, Fnc &&,
|
||||
Reader &, isPolymorphic,
|
||||
std::integral_constant<PointerOwnershipType, PointerOwnershipType::Observer>) const {
|
||||
ptrInfo.processObserver(reinterpret_cast<void *&>(TPtrManager<T>::getPtrRef(obj)));
|
||||
template<typename Des, typename T, typename Fnc, typename isPolymorphic>
|
||||
void deserializeImpl(MemResourceBase* , PLCInfoDeserializer& ptrInfo, Des&, T& obj, Fnc&&,
|
||||
isPolymorphic, OwnershipType<PointerOwnershipType::Observer>) const {
|
||||
ptrInfo.processObserver(reinterpret_cast<void*&>(TPtrManager<T>::getPtrRef(obj)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename TPtrManager<T>::TSharedState& createAndGetSharedStateObj(PLCInfoDeserializer& info) const {
|
||||
using TSharedState = typename TPtrManager<T>::TSharedState;
|
||||
StdPolyAlloc<TSharedState> alloc {info.memResource};
|
||||
auto* ptr = alloc.allocate(1);
|
||||
auto* obj = new (ptr)TSharedState{};
|
||||
info.sharedState = std::unique_ptr<PointerSharedStateBase, PointerSharedStateDeleter>(
|
||||
obj, PointerSharedStateDeleter{info.memResource});
|
||||
return *obj;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename TPtrManager<T>::TSharedState& getSharedStateObj(PLCInfoDeserializer& info) const {
|
||||
return static_cast<typename TPtrManager<T>::TSharedState&>(*info.sharedState);
|
||||
}
|
||||
|
||||
|
||||
PointerType _ptrType;
|
||||
bool _resourcePropagate;
|
||||
MemResourceBase* _resource;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//this class is for convenience
|
||||
class PointerLinkingContext :
|
||||
public pointer_utils::PointerLinkingContextSerialization,
|
||||
public pointer_utils::PointerLinkingContextDeserialization {
|
||||
public:
|
||||
explicit PointerLinkingContext() = default;
|
||||
bool isValid() {
|
||||
return isPointerSerializationValid() && isPointerDeserializationValid();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include "memory_resource.h"
|
||||
#include "../../details/adapter_common.h"
|
||||
#include "../../details/serialization_common.h"
|
||||
|
||||
@@ -60,8 +61,12 @@ namespace bitsery {
|
||||
|
||||
class PolymorphicHandlerBase {
|
||||
public:
|
||||
virtual void *create() const = 0;
|
||||
virtual void process(void *ser, void *obj) const = 0;
|
||||
virtual void* create(const pointer_utils::PolyAllocWithTypeId& alloc) const = 0;
|
||||
|
||||
virtual void destroy(const pointer_utils::PolyAllocWithTypeId& alloc, void* ptr) const = 0;
|
||||
|
||||
virtual void process(void* ser, void* obj) const = 0;
|
||||
|
||||
virtual ~PolymorphicHandlerBase() = default;
|
||||
};
|
||||
|
||||
@@ -69,22 +74,26 @@ namespace bitsery {
|
||||
class PolymorphicHandler : public PolymorphicHandlerBase {
|
||||
public:
|
||||
|
||||
void *create() const final {
|
||||
return toBase(::bitsery::Access::createInHeap<TDerived>());
|
||||
void* create(const pointer_utils::PolyAllocWithTypeId& alloc) const final {
|
||||
return toBase(alloc.newObject<TDerived>(RTTI::template get<TDerived>()));
|
||||
}
|
||||
|
||||
void process(void *ser, void *obj) const final {
|
||||
static_cast<TSerializer *>(ser)->object(*static_cast<TDerived *>(fromBase(obj)));
|
||||
void destroy(const pointer_utils::PolyAllocWithTypeId& alloc, void* ptr) const final {
|
||||
alloc.deleteObject<TDerived>(fromBase(ptr), RTTI::template get<TDerived>());
|
||||
}
|
||||
|
||||
void process(void* ser, void* obj) const final {
|
||||
static_cast<TSerializer*>(ser)->object(*fromBase(obj));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void *fromBase(void *obj) const {
|
||||
return RTTI::template cast<TBase, TDerived>(static_cast<TBase *>(obj));
|
||||
TDerived* fromBase(void* obj) const {
|
||||
return RTTI::template cast<TBase, TDerived>(static_cast<TBase*>(obj));
|
||||
}
|
||||
|
||||
void *toBase(void *obj) const {
|
||||
return RTTI::template cast<TDerived, TBase>(static_cast<TDerived *>(obj));
|
||||
TBase* toBase(void* obj) const {
|
||||
return RTTI::template cast<TDerived, TBase>(static_cast<TDerived*>(obj));
|
||||
}
|
||||
|
||||
};
|
||||
@@ -98,13 +107,13 @@ namespace bitsery {
|
||||
std::size_t baseHash;
|
||||
std::size_t derivedHash;
|
||||
|
||||
bool operator==(const BaseToDerivedKey &other) const {
|
||||
bool operator==(const BaseToDerivedKey& other) const {
|
||||
return baseHash == other.baseHash && derivedHash == other.derivedHash;
|
||||
}
|
||||
};
|
||||
|
||||
struct BaseToDerivedKeyHashier {
|
||||
size_t operator()(const BaseToDerivedKey &key) const {
|
||||
size_t operator()(const BaseToDerivedKey& key) const {
|
||||
return (key.baseHash + (key.baseHash << 6) + (key.derivedHash >> 2)) ^ key.derivedHash;
|
||||
}
|
||||
};
|
||||
@@ -131,10 +140,26 @@ namespace bitsery {
|
||||
|
||||
template<typename TSerializer, typename TBase, typename TDerived>
|
||||
void addToMap(std::false_type) {
|
||||
using THandler = PolymorphicHandler<RTTI, TSerializer, TBase, TDerived>;
|
||||
BaseToDerivedKey key{RTTI::template get<TBase>(), RTTI::template get<TDerived>()};
|
||||
if (_baseToDerivedMap.emplace(key, std::unique_ptr<PolymorphicHandlerBase>(
|
||||
new PolymorphicHandler<RTTI, TSerializer, TBase, TDerived>{})).second)
|
||||
_baseToDerivedArray[key.baseHash].push_back(key.derivedHash);
|
||||
pointer_utils::StdPolyAlloc<THandler> alloc{_memResource};
|
||||
auto ptr = alloc.allocate(1);
|
||||
std::shared_ptr<THandler> handler(new (ptr)THandler{}, [alloc](THandler* data) mutable {
|
||||
data->~THandler();
|
||||
alloc.deallocate(data, 1);
|
||||
}, alloc);
|
||||
if (_baseToDerivedMap
|
||||
.emplace(key, std::move(handler))
|
||||
.second) {
|
||||
auto it = _baseToDerivedArray.find(key.baseHash);
|
||||
if (it == _baseToDerivedArray.end()) {
|
||||
it = _baseToDerivedArray.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(key.baseHash),
|
||||
std::forward_as_tuple(pointer_utils::StdPolyAlloc<size_t>{_memResource})).first;
|
||||
}
|
||||
it->second.push_back(key.derivedHash);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TSerializer, typename TBase, typename TDerived>
|
||||
@@ -142,29 +167,41 @@ namespace bitsery {
|
||||
//cannot add abstract class
|
||||
}
|
||||
|
||||
std::unordered_map<BaseToDerivedKey, std::unique_ptr<PolymorphicHandlerBase>, BaseToDerivedKeyHashier> _baseToDerivedMap{};
|
||||
MemResourceBase* _memResource;
|
||||
// store shared ptr to polymorphic handler, because it might be copied to "smart pointer" deleter
|
||||
std::unordered_map<BaseToDerivedKey, std::shared_ptr<PolymorphicHandlerBase>,
|
||||
BaseToDerivedKeyHashier, std::equal_to<BaseToDerivedKey>,
|
||||
pointer_utils::StdPolyAlloc<std::pair<const BaseToDerivedKey, std::shared_ptr<PolymorphicHandlerBase>>>
|
||||
> _baseToDerivedMap;
|
||||
// this will allow convert from platform specific type information, to platform independent base->derived index
|
||||
// this only works if all polymorphic relationships (PolymorphicBaseClass<TBase> -> PolymorphicDerivedClasses<TDerived...>)
|
||||
// is equal between platforms.
|
||||
std::unordered_map<size_t, std::vector<size_t>> _baseToDerivedArray{};
|
||||
std::unordered_map<size_t, std::vector<size_t, pointer_utils::StdPolyAlloc<size_t>>,
|
||||
std::hash<size_t>, std::equal_to<size_t>,
|
||||
pointer_utils::StdPolyAlloc<std::pair<const size_t, std::vector<size_t, pointer_utils::StdPolyAlloc<size_t>>>>
|
||||
> _baseToDerivedArray;
|
||||
|
||||
public:
|
||||
|
||||
explicit PolymorphicContext(MemResourceBase* memResource = nullptr)
|
||||
:_memResource{memResource},
|
||||
_baseToDerivedMap{pointer_utils::StdPolyAlloc<std::pair<const BaseToDerivedKey,
|
||||
std::shared_ptr<PolymorphicHandlerBase>>>{memResource}},
|
||||
_baseToDerivedArray{pointer_utils::StdPolyAlloc<std::pair<const size_t,
|
||||
std::vector<size_t, pointer_utils::StdPolyAlloc<size_t>>>>{memResource}}
|
||||
{}
|
||||
|
||||
PolymorphicContext(const PolymorphicContext& ) = delete;
|
||||
PolymorphicContext& operator = (const PolymorphicContext&) = delete;
|
||||
PolymorphicContext(PolymorphicContext&& ) = default;
|
||||
PolymorphicContext& operator = (PolymorphicContext&&) = default;
|
||||
|
||||
|
||||
void clear() {
|
||||
_baseToDerivedMap.clear();
|
||||
_baseToDerivedArray.clear();
|
||||
}
|
||||
|
||||
template<typename TSerializer, template<typename> class THierarchy = PolymorphicBaseClass, typename T1, typename ...Tn>
|
||||
[[deprecated("de/serializer instance is not required")]] void registerBasesList(const TSerializer &s, PolymorphicClassesList<T1, Tn...>) {
|
||||
add<TSerializer, THierarchy, T1, T1>();
|
||||
registerBasesList<TSerializer, THierarchy>(s, PolymorphicClassesList<Tn...>{});
|
||||
}
|
||||
|
||||
template<typename TSerializer, template<typename> class THierarchy>
|
||||
[[deprecated]] void registerBasesList(const TSerializer &, PolymorphicClassesList<>) {
|
||||
}
|
||||
|
||||
// THierarchy is the name of class, that defines hierarchy
|
||||
// PolymorphicBaseClass is defined as default parameter, so that at instantiation time
|
||||
// it will get unique symbol in translation unit for PolymorphicBaseClass (which is defined in anonymous namespace)
|
||||
@@ -180,7 +217,7 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
// optional method, in case you want to construct base class hierarchy your self
|
||||
template <typename TSerializer, typename TBase, typename TDerived>
|
||||
template<typename TSerializer, typename TBase, typename TDerived>
|
||||
void registerSingleBaseBranch() {
|
||||
static_assert(std::is_base_of<TBase, TDerived>::value, "TDerived must be derived from TBase");
|
||||
static_assert(!std::is_abstract<TDerived>::value, "TDerived cannot be abstract");
|
||||
@@ -188,27 +225,28 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
|
||||
template<typename Serializer, typename Writer, typename TBase>
|
||||
void serialize(Serializer &ser, Writer &writer, TBase &obj) {
|
||||
template<typename Serializer, typename TBase>
|
||||
void serialize(Serializer& ser, TBase& obj) const {
|
||||
//get derived key
|
||||
BaseToDerivedKey key{RTTI::template get<TBase>(), RTTI::template get<TBase>(obj)};
|
||||
auto it = _baseToDerivedMap.find(key);
|
||||
assert(it != _baseToDerivedMap.end());
|
||||
|
||||
//convert derived hash to derived index, to make it work in cross-platform environment
|
||||
auto &vec = _baseToDerivedArray.find(key.baseHash)->second;
|
||||
auto& vec = _baseToDerivedArray.find(key.baseHash)->second;
|
||||
auto derivedIndex = static_cast<size_t>(std::distance(vec.begin(), std::find(vec.begin(), vec.end(),
|
||||
key.derivedHash)));
|
||||
details::writeSize(writer, derivedIndex);
|
||||
details::writeSize(ser.adapter(), derivedIndex);
|
||||
|
||||
//serialize
|
||||
it->second->process(&ser, &obj);
|
||||
}
|
||||
|
||||
template<typename Deserializer, typename Reader, typename TBase, typename TAssignFnc>
|
||||
void deserialize(Deserializer &des, Reader &reader, TBase *obj, TAssignFnc assignFnc) {
|
||||
template<typename Deserializer, typename TBase, typename TCreateFnc, typename TDestroyFnc>
|
||||
void deserialize(Deserializer& des, TBase* obj,
|
||||
TCreateFnc createFnc, TDestroyFnc destroyFnc) const {
|
||||
size_t derivedIndex{};
|
||||
details::readSize(reader, derivedIndex, std::numeric_limits<size_t>::max());
|
||||
details::readSize(des.adapter(), derivedIndex, 0, std::false_type{});
|
||||
|
||||
auto baseToDerivedVecIt = _baseToDerivedArray.find(RTTI::template get<TBase>());
|
||||
//base class is known at compile time, so we can assert on this one
|
||||
@@ -217,18 +255,29 @@ namespace bitsery {
|
||||
if (baseToDerivedVecIt->second.size() > derivedIndex) {
|
||||
//convert derived index to derived hash, to make it work in cross-platform environment
|
||||
auto derivedHash = baseToDerivedVecIt->second[derivedIndex];
|
||||
auto &handler = _baseToDerivedMap.find(
|
||||
BaseToDerivedKey{RTTI::template get<TBase>(), derivedHash})->second;
|
||||
auto& handler = _baseToDerivedMap.find(
|
||||
BaseToDerivedKey{RTTI::template get<TBase>(), derivedHash})->second;
|
||||
//if object is null or different type, create new and assign it
|
||||
if (obj == nullptr || RTTI::template get<TBase>(*obj) != derivedHash) {
|
||||
obj = static_cast<TBase *>(handler->create());
|
||||
assignFnc(obj);
|
||||
if (obj) {
|
||||
destroyFnc(getPolymorphicHandler(*obj));
|
||||
}
|
||||
obj = createFnc(handler);
|
||||
}
|
||||
handler->process(&des, obj);
|
||||
} else
|
||||
reader.setError(ReaderError::InvalidPointer);
|
||||
des.adapter().error(ReaderError::InvalidPointer);
|
||||
}
|
||||
|
||||
template<typename TBase>
|
||||
const std::shared_ptr<PolymorphicHandlerBase>& getPolymorphicHandler(TBase& obj) const {
|
||||
auto deleteHandlerIt = _baseToDerivedMap.find(
|
||||
BaseToDerivedKey{RTTI::template get<TBase>(), RTTI::template get<TBase>(obj)});
|
||||
assert(deleteHandlerIt != _baseToDerivedMap.end());
|
||||
return deleteHandlerIt->second;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace bitsery {
|
||||
// !std::is_volatile<TBase>::value, "");
|
||||
|
||||
template<typename TBase>
|
||||
static size_t get(TBase &obj) {
|
||||
static size_t get(TBase& obj) {
|
||||
return typeid(obj).hash_code();
|
||||
}
|
||||
|
||||
@@ -47,9 +47,9 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
template<typename TBase, typename TDerived>
|
||||
static constexpr TDerived *cast(TBase *obj) {
|
||||
static constexpr TDerived* cast(TBase* obj) {
|
||||
static_assert(!std::is_pointer<TDerived>::value, "");
|
||||
return dynamic_cast<TDerived *>(obj);
|
||||
return dynamic_cast<TDerived*>(obj);
|
||||
}
|
||||
|
||||
template<typename TBase>
|
||||
|
||||
@@ -121,8 +121,8 @@ namespace bitsery {
|
||||
details::SameSizeUnsigned<T> getRangeValue(const T &v, const RangeSpec<T> &r) {
|
||||
using VT = details::SameSizeUnsigned<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);
|
||||
const T ratio = (v - r.min) / (r.max - r.min);
|
||||
return static_cast<VT>(ratio * static_cast<T>(maxUint));
|
||||
}
|
||||
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
|
||||
@@ -141,7 +141,7 @@ namespace bitsery {
|
||||
using UIT = details::SameSizeUnsigned<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);
|
||||
v = r.min + (static_cast<T>(intRep) / static_cast<T>(maxUint)) * (r.max - r.min);
|
||||
}
|
||||
|
||||
template<typename T, typename std::enable_if<std::is_arithmetic<T>::value>::type * = nullptr>
|
||||
@@ -167,27 +167,38 @@ namespace bitsery {
|
||||
constexpr ValueRange(const TValue& min, const TValue& max, Args &&... args)
|
||||
:_range{min, max, std::forward<Args>(args)...} {}
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &, Writer &writer, const T &v, Fnc &&) const {
|
||||
template<typename Ser, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, 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);
|
||||
ser.adapter().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 {
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserialize(Des &des, T &v, Fnc &&) const {
|
||||
auto& reader = des.adapter();
|
||||
reader.readBits(reinterpret_cast<details::SameSizeUnsigned<T> &>(v), _range.bitsRequired);
|
||||
details::setRangeValue(v, _range);
|
||||
if (!details::isRangeValid(v, _range)) {
|
||||
reader.setError(ReaderError::InvalidData);
|
||||
v = _range.min;
|
||||
}
|
||||
handleInvalidRange(reader, v, std::integral_constant<bool, Des::TConfig::CheckDataErrors>{});
|
||||
}
|
||||
|
||||
constexpr size_t getRequiredBits() const {
|
||||
return _range.bitsRequired;
|
||||
};
|
||||
private:
|
||||
|
||||
template <typename Reader, typename T>
|
||||
void handleInvalidRange(Reader& reader, T& v, std::true_type) const {
|
||||
if (!details::isRangeValid(v, _range)) {
|
||||
reader.error(ReaderError::InvalidData);
|
||||
v = _range.min;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Reader, typename T>
|
||||
void handleInvalidRange(Reader&, T&, std::false_type) const {
|
||||
}
|
||||
|
||||
details::RangeSpec<TValue> _range;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,89 +25,183 @@
|
||||
#define BITSERY_SERIALIZER_H
|
||||
|
||||
#include "details/serialization_common.h"
|
||||
#include "adapter_writer.h"
|
||||
#include "details/adapter_common.h"
|
||||
#include <cassert>
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
template<typename TAdapterWriter, typename TContext = void>
|
||||
class BasicSerializer {
|
||||
namespace details {
|
||||
template<typename TAdapter>
|
||||
class OutputAdapterBitPackingWrapper {
|
||||
public:
|
||||
|
||||
static constexpr bool BitPackingEnabled = true;
|
||||
using TConfig = typename TAdapter::TConfig;
|
||||
using TValue = typename TAdapter::TValue;
|
||||
|
||||
OutputAdapterBitPackingWrapper(TAdapter& adapter)
|
||||
: _wrapped{adapter}
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
~OutputAdapterBitPackingWrapper() {
|
||||
align();
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBytes(const T &v) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
|
||||
if (!_scratchBits) {
|
||||
this->_wrapped.template writeBytes<SIZE,T>(v);
|
||||
} else {
|
||||
using UT = typename std::make_unsigned<T>::type;
|
||||
writeBitsInternal(reinterpret_cast<const UT &>(v), details::BitsSize<T>::value);
|
||||
}
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBuffer(const T *buf, size_t count) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
if (!_scratchBits) {
|
||||
this->_wrapped.template writeBuffer<SIZE,T>(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)
|
||||
writeBitsInternal(reinterpret_cast<const UT &>(*it), details::BitsSize<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::BitsSize<T>::value);
|
||||
assert(v <= (bitsCount < 64
|
||||
? (1ULL << bitsCount) - 1
|
||||
: (1ULL << (bitsCount-1)) + ((1ULL << (bitsCount-1)) -1)));
|
||||
writeBitsInternal(v, bitsCount);
|
||||
}
|
||||
|
||||
void align() {
|
||||
writeBitsInternal(UnsignedType{}, (details::BitsSize<UnsignedType>::value - _scratchBits) % 8);
|
||||
}
|
||||
|
||||
void currentWritePos(size_t pos) {
|
||||
align();
|
||||
this->_wrapped.currentWritePos(pos);
|
||||
}
|
||||
|
||||
size_t currentWritePos() const {
|
||||
return this->_wrapped.currentWritePos();
|
||||
}
|
||||
|
||||
void flush() {
|
||||
align();
|
||||
this->_wrapped.flush();
|
||||
}
|
||||
|
||||
size_t writtenBytesCount() const {
|
||||
return this->_wrapped.writtenBytesCount();
|
||||
}
|
||||
|
||||
private:
|
||||
TAdapter& _wrapped;
|
||||
|
||||
using UnsignedType = typename std::make_unsigned<typename TAdapter::TValue>::type;
|
||||
using ScratchType = typename details::ScratchType<UnsignedType>::type;
|
||||
static_assert(details::IsDefined<ScratchType>::value, "Underlying adapter value type is not supported");
|
||||
|
||||
|
||||
template<typename T>
|
||||
void writeBitsInternal(const T &v, size_t size) {
|
||||
constexpr size_t valueSize = details::BitsSize<UnsignedType>::value;
|
||||
T value = v;
|
||||
size_t bitsLeft = size;
|
||||
while (bitsLeft > 0) {
|
||||
auto bits = (std::min)(bitsLeft, valueSize);
|
||||
_scratch |= static_cast<ScratchType>( value ) << _scratchBits;
|
||||
_scratchBits += bits;
|
||||
if (_scratchBits >= valueSize) {
|
||||
auto tmp = static_cast<UnsignedType>(_scratch & _MASK);
|
||||
this->_wrapped.template writeBytes<sizeof(UnsignedType), UnsignedType >(tmp);
|
||||
_scratch >>= valueSize;
|
||||
_scratchBits -= valueSize;
|
||||
|
||||
value = static_cast<T>(value >> valueSize);
|
||||
}
|
||||
bitsLeft -= bits;
|
||||
}
|
||||
}
|
||||
|
||||
//overload for TValue, for better performance
|
||||
void writeBitsInternal(const UnsignedType &v, size_t size) {
|
||||
if (size > 0) {
|
||||
_scratch |= static_cast<ScratchType>( v ) << _scratchBits;
|
||||
_scratchBits += size;
|
||||
if (_scratchBits >= details::BitsSize<UnsignedType>::value) {
|
||||
auto tmp = static_cast<UnsignedType>(_scratch & _MASK);
|
||||
this->_wrapped.template writeBytes<sizeof(UnsignedType), UnsignedType>(tmp);
|
||||
_scratch >>= details::BitsSize<UnsignedType>::value;
|
||||
_scratchBits -= details::BitsSize<UnsignedType>::value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const UnsignedType _MASK = (std::numeric_limits<UnsignedType>::max)();
|
||||
ScratchType _scratch{};
|
||||
size_t _scratchBits{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<typename TOutputAdapter, typename TContext = void>
|
||||
class Serializer: public details::AdapterAndContextRef<TOutputAdapter, TContext> {
|
||||
public:
|
||||
//this is used by AdapterAccess class
|
||||
using TWriter = TAdapterWriter;
|
||||
//helper type, that always returns bit-packing enabled type, useful inside serialize function when enabling bitpacking
|
||||
using BPEnabledType = BasicSerializer<typename std::conditional<TAdapterWriter::BitPackingEnabled,
|
||||
TAdapterWriter, AdapterWriterBitPackingWrapper<TAdapterWriter>>::type, TContext>;
|
||||
using BPEnabledType = Serializer<typename std::conditional<TOutputAdapter::BitPackingEnabled,
|
||||
TOutputAdapter,
|
||||
details::OutputAdapterBitPackingWrapper<TOutputAdapter>>::type, TContext>;
|
||||
using TConfig = typename TOutputAdapter::TConfig;
|
||||
|
||||
static_assert(details::IsSpecializationOf<typename TWriter::TConfig::InternalContext, std::tuple>::value,
|
||||
"Config::InternalContext must be std::tuple");
|
||||
|
||||
template <typename WriterParam>
|
||||
explicit BasicSerializer(WriterParam&& w, TContext* context = nullptr)
|
||||
: _writer{std::forward<WriterParam>(w)},
|
||||
_context{context},
|
||||
_internalContext{}
|
||||
{
|
||||
}
|
||||
|
||||
//copying disabled
|
||||
BasicSerializer(const BasicSerializer&) = delete;
|
||||
BasicSerializer& operator = (const BasicSerializer&) = delete;
|
||||
|
||||
//move enabled
|
||||
BasicSerializer(BasicSerializer&& ) = default;
|
||||
BasicSerializer& operator = (BasicSerializer&& ) = default;
|
||||
|
||||
/*
|
||||
* get serialization context.
|
||||
* this is optional, but might be required for some specific serialization flows.
|
||||
*/
|
||||
TContext* context() {
|
||||
return _context;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* context() {
|
||||
return details::getContext<T>(_context, _internalContext);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* contextOrNull() {
|
||||
return details::getContextIfTypeExists<T>(_context, _internalContext);
|
||||
}
|
||||
using details::AdapterAndContextRef<TOutputAdapter, TContext>::AdapterAndContextRef;
|
||||
|
||||
/*
|
||||
* object function
|
||||
*/
|
||||
template<typename T>
|
||||
void object(const T &obj) {
|
||||
details::SerializeFunction<BasicSerializer, T>::invoke(*this, const_cast<T& >(obj));
|
||||
details::SerializeFunction<Serializer, T>::invoke(*this, const_cast<T& >(obj));
|
||||
}
|
||||
|
||||
template<typename T, typename Fnc>
|
||||
void object(const T &obj, Fnc &&fnc) {
|
||||
fnc(const_cast<T& >(obj));
|
||||
fnc(*this, const_cast<T& >(obj));
|
||||
}
|
||||
|
||||
/*
|
||||
* functionality, that enables simpler serialization syntax, by including additional header
|
||||
*/
|
||||
template<typename T, typename ... TArgs>
|
||||
void archive(T &&head, TArgs &&... tail) {
|
||||
//serialize object
|
||||
details::ArchiveFunction<BasicSerializer, T>::invoke(*this, std::forward<T>(head));
|
||||
//expand other elements
|
||||
archive(std::forward<TArgs>(tail)...);
|
||||
|
||||
template <typename... TArgs>
|
||||
Serializer &operator()(TArgs &&... args) {
|
||||
archive(std::forward<TArgs>(args)...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* value overloads
|
||||
*/
|
||||
|
||||
template<size_t VSIZE, typename T, typename std::enable_if<details::IsFundamentalType<T>::value>::type * = nullptr>
|
||||
template<size_t VSIZE, typename T>
|
||||
void value(const T &v) {
|
||||
static_assert(details::IsFundamentalType<T>::value, "Value must be integral, float or enum type.");
|
||||
using TValue = typename details::IntegralFromFundamental<T>::TValue;
|
||||
_writer.template writeBytes<VSIZE>(reinterpret_cast<const TValue &>(v));
|
||||
this->_adapter.template writeBytes<VSIZE>(reinterpret_cast<const TValue &>(v));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -115,7 +209,9 @@ namespace bitsery {
|
||||
*/
|
||||
template <typename Fnc>
|
||||
void enableBitPacking(Fnc&& fnc) {
|
||||
procEnableBitPacking(std::forward<Fnc>(fnc), std::integral_constant<bool, TAdapterWriter::BitPackingEnabled>{});
|
||||
procEnableBitPacking(std::forward<Fnc>(fnc),
|
||||
std::integral_constant<bool, TOutputAdapter::BitPackingEnabled>{},
|
||||
std::integral_constant<bool, Serializer::HasContext>{});
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -127,7 +223,7 @@ namespace bitsery {
|
||||
static_assert(details::IsExtensionTraitsDefined<Ext, T>::value, "Please define ExtensionTraits");
|
||||
static_assert(traits::ExtensionTraits<Ext,T>::SupportLambdaOverload,
|
||||
"extension doesn't support overload with lambda");
|
||||
extension.serialize(*this, _writer, obj, std::forward<Fnc>(fnc));
|
||||
extension.serialize(*this, obj, std::forward<Fnc>(fnc));
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, typename Ext>
|
||||
@@ -137,7 +233,7 @@ namespace bitsery {
|
||||
"extension doesn't support overload with `value<N>`");
|
||||
using ExtVType = typename traits::ExtensionTraits<Ext, T>::TValue;
|
||||
using VType = typename std::conditional<std::is_void<ExtVType>::value, details::DummyType, ExtVType>::type;
|
||||
extension.serialize(*this, _writer, obj, [this](VType &v) { value<VSIZE>(v); });
|
||||
extension.serialize(*this, obj, [](Serializer& s, VType &v) { s.value<VSIZE>(v); });
|
||||
}
|
||||
|
||||
template<typename T, typename Ext>
|
||||
@@ -147,7 +243,7 @@ namespace bitsery {
|
||||
"extension doesn't support overload with `object`");
|
||||
using ExtVType = typename traits::ExtensionTraits<Ext, T>::TValue;
|
||||
using VType = typename std::conditional<std::is_void<ExtVType>::value, details::DummyType, ExtVType>::type;
|
||||
extension.serialize(*this, _writer, obj, [this](VType &v) { object(v); });
|
||||
extension.serialize(*this, obj, [](Serializer& s, VType &v) { s.object(v); });
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -155,7 +251,7 @@ namespace bitsery {
|
||||
*/
|
||||
|
||||
void boolValue(bool v) {
|
||||
procBoolValue(v, std::integral_constant<bool, TAdapterWriter::BitPackingEnabled>{});
|
||||
procBoolValue(v, std::integral_constant<bool, TOutputAdapter::BitPackingEnabled>{});
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -194,7 +290,7 @@ namespace bitsery {
|
||||
"use container(const T&, Fnc) overload without `maxSize` for static containers");
|
||||
auto size = traits::ContainerTraits<T>::size(obj);
|
||||
assert(size <= maxSize);
|
||||
details::writeSize(_writer, size);
|
||||
details::writeSize(this->_adapter, size);
|
||||
procContainer(std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
|
||||
}
|
||||
|
||||
@@ -207,7 +303,7 @@ namespace bitsery {
|
||||
static_assert(VSIZE > 0, "");
|
||||
auto size = traits::ContainerTraits<T>::size(obj);
|
||||
assert(size <= maxSize);
|
||||
details::writeSize(_writer, size);
|
||||
details::writeSize(this->_adapter, size);
|
||||
|
||||
procContainer<VSIZE>(std::begin(obj), std::end(obj), std::integral_constant<bool, traits::ContainerTraits<T>::isContiguous>{});
|
||||
}
|
||||
@@ -220,7 +316,7 @@ namespace bitsery {
|
||||
"use container(const T&) overload without `maxSize` for static containers");
|
||||
auto size = traits::ContainerTraits<T>::size(obj);
|
||||
assert(size <= maxSize);
|
||||
details::writeSize(_writer, size);
|
||||
details::writeSize(this->_adapter, size);
|
||||
procContainer(std::begin(obj), std::end(obj));
|
||||
}
|
||||
|
||||
@@ -254,10 +350,6 @@ namespace bitsery {
|
||||
procContainer(std::begin(obj), std::end(obj));
|
||||
}
|
||||
|
||||
void align() {
|
||||
_writer.align();
|
||||
}
|
||||
|
||||
//overloads for functions with explicit type size
|
||||
|
||||
template<typename T>
|
||||
@@ -326,12 +418,8 @@ namespace bitsery {
|
||||
template<typename T>
|
||||
void container8b(T &&obj) { container<8>(std::forward<T>(obj)); }
|
||||
|
||||
private:
|
||||
friend AdapterAccess;
|
||||
|
||||
TAdapterWriter _writer;
|
||||
TContext* _context;
|
||||
typename TWriter::TConfig::InternalContext _internalContext;
|
||||
private:
|
||||
|
||||
//process value types
|
||||
//false_type means that we must process all elements individually
|
||||
@@ -348,7 +436,7 @@ namespace bitsery {
|
||||
using TValue = typename std::decay<decltype(*first)>::type;
|
||||
using TIntegral = typename details::IntegralFromFundamental<TValue>::TValue;
|
||||
if (first != last)
|
||||
_writer.template writeBuffer<VSIZE>(reinterpret_cast<const TIntegral*>(&(*first)),
|
||||
this->_adapter.template writeBuffer<VSIZE>(reinterpret_cast<const TIntegral*>(&(*first)),
|
||||
static_cast<size_t>(std::distance(first, last)));
|
||||
}
|
||||
|
||||
@@ -357,18 +445,19 @@ namespace bitsery {
|
||||
void procContainer(It first, It last, Fnc fnc) {
|
||||
using TValue = typename std::decay<decltype(*first)>::type;
|
||||
for (; first != last; ++first) {
|
||||
fnc(const_cast<TValue&>(*first));
|
||||
fnc(*this, const_cast<TValue&>(*first));
|
||||
}
|
||||
}
|
||||
|
||||
//process text,
|
||||
template<size_t VSIZE, typename T>
|
||||
void procText(const T& str, size_t maxSize) {
|
||||
auto length = traits::TextTraits<T>::length(str);
|
||||
const size_t length = traits::TextTraits<T>::length(str);
|
||||
assert((length + (traits::TextTraits<T>::addNUL ? 1u : 0u)) <= maxSize);
|
||||
details::writeSize(_writer, length);
|
||||
details::writeSize(this->_adapter, length);
|
||||
auto begin = std::begin(str);
|
||||
procContainer<VSIZE>(begin, std::next(begin, length), std::integral_constant<bool, traits::ContainerTraits<T>::isContiguous>{});
|
||||
using diff_t = typename std::iterator_traits<decltype(begin)>::difference_type;
|
||||
procContainer<VSIZE>(begin, std::next(begin, static_cast<diff_t>(length)), std::integral_constant<bool, traits::ContainerTraits<T>::isContiguous>{});
|
||||
}
|
||||
|
||||
//process object types
|
||||
@@ -380,24 +469,31 @@ namespace bitsery {
|
||||
|
||||
//proc bool writing bit or byte, depending on if BitPackingEnabled or not
|
||||
void procBoolValue(bool v, std::true_type) {
|
||||
_writer.writeBits(static_cast<unsigned char>(v ? 1 : 0), 1);
|
||||
this->_adapter.writeBits(static_cast<unsigned char>(v ? 1 : 0), 1);
|
||||
}
|
||||
|
||||
void procBoolValue(bool v, std::false_type) {
|
||||
_writer.template writeBytes<1>(static_cast<unsigned char>(v ? 1 : 0));
|
||||
this->_adapter.template writeBytes<1>(static_cast<unsigned char>(v ? 1 : 0));
|
||||
}
|
||||
|
||||
//enable bit-packing or do nothing if it is already enabled
|
||||
template <typename Fnc>
|
||||
void procEnableBitPacking(const Fnc& fnc, std::true_type) {
|
||||
template <typename Fnc, typename HasContext>
|
||||
void procEnableBitPacking(const Fnc& fnc, std::true_type, HasContext) {
|
||||
fnc(*this);
|
||||
}
|
||||
|
||||
template <typename Fnc>
|
||||
void procEnableBitPacking(const Fnc& fnc, std::false_type) {
|
||||
void procEnableBitPacking(const Fnc& fnc, std::false_type, std::true_type) {
|
||||
//create serializer using bitpacking wrapper
|
||||
BPEnabledType tmp(_writer, _context);
|
||||
fnc(tmp);
|
||||
BPEnabledType ser{this->_context, this->_adapter};
|
||||
fnc(ser);
|
||||
}
|
||||
|
||||
template <typename Fnc>
|
||||
void procEnableBitPacking(const Fnc& fnc, std::false_type, std::false_type) {
|
||||
//create serializer using bitpacking wrapper
|
||||
BPEnabledType ser{this->_adapter};
|
||||
fnc(ser);
|
||||
}
|
||||
|
||||
//these are dummy functions for extensions that have TValue = void
|
||||
@@ -410,33 +506,34 @@ namespace bitsery {
|
||||
|
||||
}
|
||||
|
||||
template<typename T, typename ... TArgs>
|
||||
void archive(T &&head, TArgs &&... tail) {
|
||||
//serialize object
|
||||
details::BriefSyntaxFunction<Serializer, T>::invoke(*this, std::forward<T>(head));
|
||||
//expand other elements
|
||||
archive(std::forward<TArgs>(tail)...);
|
||||
}
|
||||
//dummy function, that stops archive variadic arguments expansion
|
||||
void archive() {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//helper type
|
||||
template <typename Adapter>
|
||||
using Serializer = BasicSerializer<AdapterWriter<Adapter, DefaultConfig>>;
|
||||
|
||||
//helper function that set ups all the basic steps and after serialziation returns serialized bytes count
|
||||
template <typename Adapter, typename T>
|
||||
size_t quickSerialization(Adapter adapter, const T& value) {
|
||||
Serializer<Adapter> ser{std::move(adapter)};
|
||||
template <typename OutputAdapter, typename T>
|
||||
size_t quickSerialization(OutputAdapter adapter, const T& value) {
|
||||
Serializer<OutputAdapter> ser{std::move(adapter)};
|
||||
ser.object(value);
|
||||
auto& w = AdapterAccess::getWriter(ser);
|
||||
w.flush();
|
||||
return w.writtenBytesCount();
|
||||
ser.adapter().flush();
|
||||
return ser.adapter().writtenBytesCount();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t quickMeasureSize(const T& value) {
|
||||
BasicSerializer<MeasureSize> ser{MeasureSize{}};
|
||||
template <typename Context, typename OutputAdapter, typename T>
|
||||
size_t quickSerialization(Context& ctx, OutputAdapter adapter, const T& value) {
|
||||
Serializer<OutputAdapter, Context> ser{ctx, std::move(adapter)};
|
||||
ser.object(value);
|
||||
auto& w = AdapterAccess::getWriter(ser);
|
||||
w.flush();
|
||||
return w.writtenBytesCount();
|
||||
ser.adapter().flush();
|
||||
return ser.adapter().writtenBytesCount();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -56,6 +56,8 @@ namespace bitsery {
|
||||
resizeImpl(container, size, std::is_default_constructible<TValue>{});
|
||||
}
|
||||
private:
|
||||
using diff_t = typename T::difference_type;
|
||||
|
||||
static void resizeImpl(T& container, size_t size, std::true_type) {
|
||||
container.resize(size);
|
||||
}
|
||||
@@ -65,7 +67,7 @@ namespace bitsery {
|
||||
container.push_back(::bitsery::Access::create<TValue>());
|
||||
}
|
||||
if (oldSize > newSize) {
|
||||
container.erase(std::next(std::begin(container), newSize));
|
||||
container.erase(std::next(std::begin(container), static_cast<diff_t>(newSize)), std::end(container));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +87,7 @@ namespace bitsery {
|
||||
static void increaseBufferSize(T& container) {
|
||||
//since we're writing to buffer use different resize strategy than default implementation
|
||||
//when small size grow faster, to avoid thouse 2/4/8/16... byte allocations
|
||||
auto newSize = static_cast<size_t>(container.size() * 1.5 + 128);
|
||||
auto newSize = static_cast<size_t>(static_cast<double>(container.size()) * 1.5) + 128;
|
||||
//make data cache friendly
|
||||
newSize -= newSize % 64;//64 is cache line size
|
||||
container.resize((std::max)(newSize, container.capacity()));
|
||||
|
||||
@@ -147,7 +147,6 @@ namespace bitsery {
|
||||
//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 Writer return range iterators
|
||||
|
||||
static void increaseBufferSize(T& ) {
|
||||
static_assert(std::is_void<T>::value,
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace bitsery {
|
||||
resizeImpl(container, size, std::is_default_constructible<TValue>{});
|
||||
}
|
||||
private:
|
||||
using diff_t = typename std::forward_list<T, Allocator>::difference_type;
|
||||
static void resizeImpl(std::forward_list<T, Allocator>& container, size_t size, std::true_type) {
|
||||
container.resize(size);
|
||||
}
|
||||
@@ -55,7 +56,7 @@ namespace bitsery {
|
||||
if (oldSize > newSize) {
|
||||
//erase_after must have atleast one element to work
|
||||
if (newSize > 0)
|
||||
container.erase_after(std::next(std::begin(container), newSize-1));
|
||||
container.erase_after(std::next(std::begin(container), static_cast<diff_t>(newSize-1)));
|
||||
else
|
||||
container.clear();
|
||||
}
|
||||
|
||||
11
patches/README.md
Normal file
11
patches/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Compiler specific patches
|
||||
|
||||
This folder will provide patches for various C++ compilers that are not C++11 compatible yet. This allows providing any fix for any compiler, without polluting core library with compiler-specific fixes.
|
||||
|
||||
A patch can be applied either with `git apply` or `patch` command, like this:
|
||||
```bash
|
||||
git apply patches/<patch_name>
|
||||
patch -p1 < patches/<patch_name>
|
||||
```
|
||||
|
||||
* [centos7_gcc4.8.2.diff](centos7_gcc4.8.2.diff) in this version, unordered_map is not fully C++11 compatible yet. It is lacking some constructors that accept allocator, and isn't using `std::allocator_traits`.
|
||||
119
patches/centos7_gcc4.8.2.diff
Normal file
119
patches/centos7_gcc4.8.2.diff
Normal file
@@ -0,0 +1,119 @@
|
||||
diff --git a/include/bitsery/details/serialization_common.h b/include/bitsery/details/serialization_common.h
|
||||
index 6d5a441..462cee2 100644
|
||||
--- a/include/bitsery/details/serialization_common.h
|
||||
+++ b/include/bitsery/details/serialization_common.h
|
||||
@@ -380,7 +380,7 @@ namespace bitsery {
|
||||
template <typename ... TArgs>
|
||||
explicit AdapterAndContextRef(Context& ctx, TArgs&& ... args)
|
||||
: _adapter{std::forward<TArgs>(args)...},
|
||||
- _context{ctx}
|
||||
+ _context(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
diff --git a/include/bitsery/ext/inheritance.h b/include/bitsery/ext/inheritance.h
|
||||
index f4c6655..5cd44ab 100644
|
||||
--- a/include/bitsery/ext/inheritance.h
|
||||
+++ b/include/bitsery/ext/inheritance.h
|
||||
@@ -36,7 +36,7 @@ namespace bitsery {
|
||||
class InheritanceContext {
|
||||
public:
|
||||
explicit InheritanceContext(MemResourceBase* memResource = nullptr)
|
||||
- :_virtualBases{pointer_utils::StdPolyAlloc<const void*>{memResource}}
|
||||
+ :_virtualBases{0, std::hash<const void*>{}, std::equal_to<const void*>{}, pointer_utils::StdPolyAlloc<const void*>{memResource}}
|
||||
{}
|
||||
InheritanceContext(const InheritanceContext&) = delete;
|
||||
InheritanceContext&operator = (const InheritanceContext&) = delete;
|
||||
diff --git a/include/bitsery/ext/utils/memory_resource.h b/include/bitsery/ext/utils/memory_resource.h
|
||||
index 472965a..18b3f31 100644
|
||||
--- a/include/bitsery/ext/utils/memory_resource.h
|
||||
+++ b/include/bitsery/ext/utils/memory_resource.h
|
||||
@@ -24,6 +24,7 @@
|
||||
#define BITSERY_EXT_MEMORY_RESOURCE_H
|
||||
|
||||
#include "../../details/serialization_common.h"
|
||||
+#include <cstddef>
|
||||
#include <new>
|
||||
|
||||
namespace bitsery {
|
||||
@@ -128,6 +129,40 @@ namespace bitsery {
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
+ using pointer = T*;
|
||||
+ using const_pointer = const T*;
|
||||
+ using reference = T&;
|
||||
+ using const_reference = const T&;
|
||||
+ using size_type = size_t;
|
||||
+ using difference_type = ptrdiff_t;
|
||||
+
|
||||
+ size_t max_size() const noexcept {
|
||||
+ return std::numeric_limits<size_t>::max() / sizeof(value_type);
|
||||
+ }
|
||||
+
|
||||
+ void construct(T *p, const T &val) {
|
||||
+ new((void *) p) T(val);
|
||||
+ }
|
||||
+
|
||||
+ template<class U, class... Args>
|
||||
+ void construct(U *p, Args &&... args) {
|
||||
+ new((void *) p) U(std::forward<Args>(args)...);
|
||||
+ }
|
||||
+
|
||||
+ void destroy(T *p) {
|
||||
+ p->~T();
|
||||
+ }
|
||||
+
|
||||
+ template<class U>
|
||||
+ void destroy(U *p) {
|
||||
+ p->~U();
|
||||
+ }
|
||||
+
|
||||
+ template<typename U>
|
||||
+ struct rebind {
|
||||
+ using other = StdPolyAlloc<U>;
|
||||
+ };
|
||||
+
|
||||
explicit constexpr StdPolyAlloc(MemResourceBase* memResource)
|
||||
:_alloc{memResource} {}
|
||||
explicit constexpr StdPolyAlloc(PolyAllocWithTypeId alloc) : _alloc{alloc} {}
|
||||
diff --git a/include/bitsery/ext/utils/pointer_utils.h b/include/bitsery/ext/utils/pointer_utils.h
|
||||
index f6f90da..6b65600 100644
|
||||
--- a/include/bitsery/ext/utils/pointer_utils.h
|
||||
+++ b/include/bitsery/ext/utils/pointer_utils.h
|
||||
@@ -153,7 +153,7 @@ namespace bitsery {
|
||||
public:
|
||||
explicit PointerLinkingContextSerialization(MemResourceBase* memResource = nullptr)
|
||||
: _currId{0},
|
||||
- _ptrMap{StdPolyAlloc<std::pair<const void* const, PLCInfoSerializer>>{memResource}} {}
|
||||
+ _ptrMap{0, std::hash<const void*>{}, std::equal_to<const void*>{}, StdPolyAlloc<std::pair<const void* const, PLCInfoSerializer>>{memResource}} {}
|
||||
|
||||
PointerLinkingContextSerialization(const PointerLinkingContextSerialization&) = delete;
|
||||
|
||||
@@ -198,7 +198,7 @@ namespace bitsery {
|
||||
public:
|
||||
explicit PointerLinkingContextDeserialization(MemResourceBase* memResource = nullptr)
|
||||
: _memResource{memResource},
|
||||
- _idMap{StdPolyAlloc<std::pair<const size_t, PLCInfoDeserializer>>{memResource}} {}
|
||||
+ _idMap{0, std::hash<size_t>{}, std::equal_to<size_t>{}, StdPolyAlloc<std::pair<const size_t, PLCInfoDeserializer>>{memResource}} {}
|
||||
|
||||
PointerLinkingContextDeserialization(const PointerLinkingContextDeserialization&) = delete;
|
||||
|
||||
diff --git a/include/bitsery/ext/utils/polymorphism_utils.h b/include/bitsery/ext/utils/polymorphism_utils.h
|
||||
index 6678230..a2cef4d 100644
|
||||
--- a/include/bitsery/ext/utils/polymorphism_utils.h
|
||||
+++ b/include/bitsery/ext/utils/polymorphism_utils.h
|
||||
@@ -185,11 +185,8 @@ namespace bitsery {
|
||||
|
||||
explicit PolymorphicContext(MemResourceBase* memResource = nullptr)
|
||||
:_memResource{memResource},
|
||||
- _baseToDerivedMap{pointer_utils::StdPolyAlloc<std::pair<const BaseToDerivedKey,
|
||||
- std::shared_ptr<PolymorphicHandlerBase>>>{memResource}},
|
||||
- _baseToDerivedArray{pointer_utils::StdPolyAlloc<std::pair<const size_t,
|
||||
- std::vector<size_t, pointer_utils::StdPolyAlloc<size_t>>>>{memResource}}
|
||||
- {}
|
||||
+ _baseToDerivedMap{0, BaseToDerivedKeyHashier{}, std::equal_to<BaseToDerivedKey>{}, pointer_utils::StdPolyAlloc<std::pair<const BaseToDerivedKey, std::shared_ptr<PolymorphicHandlerBase>>>{memResource}},
|
||||
+ _baseToDerivedArray{0, std::hash<size_t>{}, std::equal_to<size_t>{}, pointer_utils::StdPolyAlloc<std::pair<const size_t, std::vector<size_t, pointer_utils::StdPolyAlloc<size_t>>>>{memResource}} {}
|
||||
|
||||
PolymorphicContext(const PolymorphicContext& ) = delete;
|
||||
PolymorphicContext& operator = (const PolymorphicContext&) = delete;
|
||||
@@ -23,7 +23,7 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(bitsery_tests CXX)
|
||||
|
||||
find_package(GTest 1.8 REQUIRED)
|
||||
find_package(GTest 1.10 REQUIRED)
|
||||
|
||||
if (NOT TARGET Bitsery::bitsery)
|
||||
message(FATAL_ERROR "Bitsery::bitsery alias not set. Please generate CMake from bitsery root directory.")
|
||||
@@ -39,7 +39,10 @@ foreach (TestFile ${TestSourceFiles})
|
||||
add_executable(${TestName} ${TestFile})
|
||||
target_link_libraries(${TestName} PRIVATE GTest::Main Bitsery::bitsery)
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||
target_compile_options(${TestName} PRIVATE -Wextra -Wno-missing-braces -Wpedantic -Weffc++ -Wno-c++14-extensions)
|
||||
target_compile_options(${TestName} PRIVATE -Wextra -Wconversion -Wno-missing-braces -Wpedantic -Weffc++ -Werror)
|
||||
endif()
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
target_compile_options(${TestName} PRIVATE -Wno-c++14-extensions)
|
||||
endif()
|
||||
gtest_discover_tests(${TestName})
|
||||
|
||||
|
||||
560
tests/adapter.cpp
Normal file
560
tests/adapter.cpp
Normal file
@@ -0,0 +1,560 @@
|
||||
//MIT License
|
||||
//
|
||||
//Copyright (c) 2019 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 <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/adapter/stream.h>
|
||||
#include <bitsery/adapter/measure_size.h>
|
||||
#include <bitsery/deserializer.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
#include <bitsery/traits/array.h>
|
||||
#include <bitsery/traits/string.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<char>;
|
||||
using OutputAdapter = bitsery::OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = bitsery::InputBufferAdapter<Buffer>;
|
||||
|
||||
using bitsery::ReaderError;
|
||||
|
||||
using testing::Eq;
|
||||
using testing::Ge;
|
||||
|
||||
struct DisableAdapterErrorsConfig {
|
||||
static constexpr bitsery::EndiannessType Endianness = bitsery::DefaultConfig::Endianness;
|
||||
static constexpr bool CheckAdapterErrors = false;
|
||||
static constexpr bool CheckDataErrors = true;
|
||||
};
|
||||
|
||||
TEST(OutputBuffer, WhenInitialBufferIsEmptyThenResizeInAdapterConstructor) {
|
||||
//setup data
|
||||
Buffer buf{};
|
||||
EXPECT_THAT(buf.size(), Eq(0));
|
||||
OutputAdapter adapter{buf};
|
||||
EXPECT_THAT(buf.size(), Ge(1));
|
||||
}
|
||||
|
||||
TEST(OutputBuffer, WhenSetWritePositionThenResizeUnderlyingBufferIfRequired) {
|
||||
//setup data
|
||||
Buffer buf{};
|
||||
OutputAdapter w{buf};
|
||||
const auto initialSize = buf.size();
|
||||
EXPECT_THAT(buf.size(), Eq(initialSize));
|
||||
EXPECT_THAT(w.currentWritePos(), Eq(0));
|
||||
w.currentWritePos(initialSize + 10);
|
||||
EXPECT_THAT(w.currentWritePos(), Eq(initialSize + 10));
|
||||
EXPECT_THAT(buf.size(), Ge(initialSize + 10));
|
||||
}
|
||||
|
||||
TEST(OutputBuffer, WhenSettingCurrentPositionBeforeBufferEndThenWrittenBytesCountIsNotAffected) {
|
||||
//setup data
|
||||
Buffer buf{};
|
||||
OutputAdapter w{buf};
|
||||
const auto initialSize = buf.size();
|
||||
EXPECT_THAT(buf.size(), Eq(initialSize));
|
||||
EXPECT_THAT(w.writtenBytesCount(), Eq(0));
|
||||
w.currentWritePos(initialSize + 10);
|
||||
w.writeBytes<8>(static_cast<uint64_t>(1));
|
||||
EXPECT_THAT(w.writtenBytesCount(), Eq(initialSize + 10 + 8));
|
||||
w.currentWritePos(0);
|
||||
EXPECT_THAT(w.writtenBytesCount(), Eq(initialSize + 10 + 8));
|
||||
}
|
||||
|
||||
TEST(OutputBuffer, CanWorkWithFixedSizeBuffer) {
|
||||
//setup data
|
||||
std::array<uint8_t, 10> buf{};
|
||||
bitsery::OutputBufferAdapter<std::array<uint8_t, 10>> w{buf};
|
||||
const auto initialSize = buf.size();
|
||||
EXPECT_THAT(buf.size(), Eq(initialSize));
|
||||
EXPECT_THAT(w.currentWritePos(), Eq(0));
|
||||
w.currentWritePos(5);
|
||||
EXPECT_THAT(w.currentWritePos(), Eq(5));
|
||||
}
|
||||
|
||||
TEST(InputBuffer, CorrectlySetsAndGetsCurrentReadPosition) {
|
||||
|
||||
Buffer buf{};
|
||||
buf.resize(100);
|
||||
InputAdapter r{buf.begin(), 10};
|
||||
r.currentReadPos(5);
|
||||
EXPECT_THAT(r.currentReadPos(), Eq(5));
|
||||
r.currentReadPos(0);
|
||||
EXPECT_THAT(r.currentReadPos(), Eq(0));
|
||||
uint8_t tmp;
|
||||
r.readBytes<1>(tmp);
|
||||
EXPECT_THAT(r.currentReadPos(), Eq(1));
|
||||
}
|
||||
|
||||
|
||||
TEST(InputBuffer, WhenSetReadPositionOutOfRangeThenDataOverflow) {
|
||||
|
||||
Buffer buf{};
|
||||
buf.resize(100);
|
||||
InputAdapter r{buf.begin(), 10};
|
||||
r.currentReadPos(10);
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::NoError));
|
||||
r.currentReadPos(11);
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::DataOverflow));
|
||||
}
|
||||
|
||||
TEST(InputBuffer, WhenSetReadEndPositionOutOfRangeThenDataOverflow) {
|
||||
Buffer buf{};
|
||||
buf.resize(100);
|
||||
InputAdapter r{buf.begin(), 10};
|
||||
r.currentReadEndPos(11);
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::DataOverflow));
|
||||
}
|
||||
|
||||
TEST(InputBuffer, WhenReadEndPositionIsNotSetThenReturnZeroAsBufferEndPosition) {
|
||||
Buffer buf{};
|
||||
buf.resize(100);
|
||||
InputAdapter r{buf.begin(), 10};
|
||||
EXPECT_THAT(r.currentReadEndPos(), Eq(0));
|
||||
r.currentReadEndPos(5);
|
||||
EXPECT_THAT(r.currentReadEndPos(), Eq(5));
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::NoError));
|
||||
}
|
||||
|
||||
|
||||
TEST(InputBuffer, WhenReadEndPositionIsNotZeroThenDataOverflowErrorWillBeIgnored) {
|
||||
Buffer buf{};
|
||||
buf.resize(100);
|
||||
InputAdapter r{buf.begin(), 1};
|
||||
r.currentReadEndPos(1);
|
||||
uint32_t tmp{};
|
||||
r.readBytes<4>(tmp);
|
||||
EXPECT_THAT(tmp, Eq(0));
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::NoError));
|
||||
r.currentReadEndPos(0);
|
||||
r.readBytes<4>(tmp);
|
||||
EXPECT_THAT(tmp, Eq(0));
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::DataOverflow));
|
||||
}
|
||||
|
||||
|
||||
TEST(InputBuffer, WhenReadingPastReadEndPositionOrBufferEndThenReadPositionDoesntChange) {
|
||||
Buffer buf{};
|
||||
buf.resize(10);
|
||||
InputAdapter r{buf.begin(), 3};
|
||||
uint32_t tmp{};
|
||||
r.currentReadEndPos(2);
|
||||
r.readBytes<4>(tmp);
|
||||
EXPECT_THAT(r.currentReadPos(), Eq(0));
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::NoError));
|
||||
EXPECT_THAT(tmp, Eq(0));
|
||||
r.currentReadEndPos(0);
|
||||
r.readBytes<4>(tmp);
|
||||
EXPECT_THAT(r.currentReadPos(), Eq(0));
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::DataOverflow));
|
||||
EXPECT_THAT(tmp, Eq(0));
|
||||
}
|
||||
|
||||
TEST(InputBuffer, WhenReaderHasErrorsThenSettingReadPosAndReadEndPosIsIgnoredAndGettingAlwaysReturnsZero) {
|
||||
Buffer buf{};
|
||||
buf.resize(10);
|
||||
InputAdapter r{buf.begin(), 10};
|
||||
uint32_t tmp{};
|
||||
r.readBytes<4>(tmp);
|
||||
r.currentReadEndPos(5);
|
||||
EXPECT_THAT(r.currentReadPos(), Eq(4));
|
||||
EXPECT_THAT(r.currentReadEndPos(), Eq(5));
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::NoError));
|
||||
r.currentReadEndPos(11);
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::DataOverflow));
|
||||
EXPECT_THAT(r.currentReadPos(), Eq(0));
|
||||
EXPECT_THAT(r.currentReadEndPos(), Eq(0));
|
||||
r.currentReadPos(1);
|
||||
r.currentReadEndPos(1);
|
||||
EXPECT_THAT(r.currentReadPos(), Eq(0));
|
||||
EXPECT_THAT(r.currentReadEndPos(), Eq(0));
|
||||
}
|
||||
|
||||
TEST(InputBuffer, ConstDataForBufferAllAdapters) {
|
||||
//create and write to buffer
|
||||
uint16_t data = 7549;
|
||||
Buffer bufWrite{};
|
||||
OutputAdapter bw{bufWrite};
|
||||
bw.writeBytes<2>(data);
|
||||
bw.flush();
|
||||
const Buffer buf{bufWrite};
|
||||
|
||||
//read from buffer
|
||||
|
||||
bitsery::InputBufferAdapter<const Buffer> r1{buf.begin(), buf.end()};
|
||||
|
||||
uint16_t res1{};
|
||||
r1.readBytes<2>(res1);
|
||||
EXPECT_THAT(res1, Eq(data));
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
TEST(InputBuffer, WhenAdapterErrorsIsDisabledThenCanChangeAnyReadPositionAndReadsAsserts) {
|
||||
//create and write to buffer
|
||||
uint64_t data = 0x1122334455667788;
|
||||
Buffer buf{};
|
||||
OutputAdapter bw{buf};
|
||||
bw.writeBytes<8>(data);
|
||||
bw.flush();
|
||||
|
||||
bitsery::InputBufferAdapter<Buffer, DisableAdapterErrorsConfig> r1{buf.begin(), 2};
|
||||
uint16_t res1{};
|
||||
r1.readBytes<2>(res1);
|
||||
EXPECT_THAT(res1, Eq(0x7788)); // default config is little endian
|
||||
EXPECT_THAT(r1.currentReadPos(), Eq(2));
|
||||
r1.currentReadPos(4);
|
||||
EXPECT_THAT(r1.currentReadPos(), Eq(4));
|
||||
EXPECT_DEATH(r1.readBytes<2>(res1), ""); // default config is little endian
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST(InputStream, WhenAdapterErrorsIsDisabledThenReadingPastEndDoesntSetErrorAndDoesntReturnZero) {
|
||||
//create and write to buffer
|
||||
std::stringstream ss{};
|
||||
bitsery::OutputStreamAdapter bw{ss};
|
||||
uint32_t data = 0x12345678;
|
||||
bw.writeBytes<4>(data);
|
||||
bw.flush();
|
||||
|
||||
bitsery::BasicInputStreamAdapter<char, DisableAdapterErrorsConfig, std::char_traits<char>> br{ss};
|
||||
uint32_t res{};
|
||||
br.readBytes<4>(res);
|
||||
EXPECT_THAT(res, Eq(data));
|
||||
br.readBytes<4>(res);
|
||||
EXPECT_THAT(res, Eq(data));
|
||||
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(true));
|
||||
}
|
||||
|
||||
template <template<typename...> class TAdapter>
|
||||
struct InBufferConfig {
|
||||
using Data = std::vector<char>;
|
||||
using Adapter = TAdapter<Data>;
|
||||
|
||||
Data data{};
|
||||
Adapter createReader(const std::vector<char>& buffer) {
|
||||
data = buffer;
|
||||
return Adapter{data.begin(), data.size()};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TAdapter>
|
||||
struct InStreamConfig {
|
||||
using Data = std::stringstream;
|
||||
using Adapter = TAdapter;
|
||||
|
||||
Data data{};
|
||||
Adapter createReader(const std::vector<char>& buffer) {
|
||||
std::string str(buffer.begin(), buffer.end());
|
||||
data = std::stringstream{str};
|
||||
return Adapter{data};
|
||||
}
|
||||
};
|
||||
|
||||
template<typename TAdapterWithData>
|
||||
class AdapterConfig : public testing::Test {
|
||||
public:
|
||||
|
||||
TAdapterWithData config{};
|
||||
};
|
||||
|
||||
using AdapterInputTypes = ::testing::Types<
|
||||
InBufferConfig<bitsery::InputBufferAdapter>,
|
||||
InStreamConfig<bitsery::InputStreamAdapter>
|
||||
>;
|
||||
|
||||
template <typename TConfig>
|
||||
class InputAll: public AdapterConfig<TConfig> {
|
||||
};
|
||||
|
||||
TYPED_TEST_SUITE(InputAll, AdapterInputTypes,);
|
||||
|
||||
|
||||
TYPED_TEST(InputAll, SettingMultipleErrorsAlwaysReturnsFirstError) {
|
||||
auto r = this->config.createReader({0,0,0,0});
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::NoError));
|
||||
r.error(ReaderError::InvalidPointer);
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::InvalidPointer));
|
||||
r.error(ReaderError::DataOverflow);
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::InvalidPointer));
|
||||
r.error(ReaderError::NoError);
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::InvalidPointer));
|
||||
}
|
||||
|
||||
TYPED_TEST(InputAll, CanBeMoveConstructedAndMoveAssigned) {
|
||||
auto r = this->config.createReader({1,2,3});
|
||||
uint8_t res{};
|
||||
r.template readBytes<1>(res);
|
||||
EXPECT_THAT(res, Eq(1));
|
||||
// move construct
|
||||
auto r1 = std::move(r);
|
||||
r1.template readBytes<1>(res);
|
||||
EXPECT_THAT(res, Eq(2));
|
||||
// move assign
|
||||
r = std::move(r1);
|
||||
r.template readBytes<1>(res);
|
||||
EXPECT_THAT(res, Eq(3));
|
||||
}
|
||||
|
||||
|
||||
TYPED_TEST(InputAll, WhenAlignHasNonZerosThenInvalidDataError) {
|
||||
|
||||
auto r = this->config.createReader({0x7F});
|
||||
bitsery::details::InputAdapterBitPackingWrapper<decltype(r)> bpr{r};
|
||||
|
||||
uint8_t tmp{0xFF};
|
||||
bpr.readBits(tmp,3);
|
||||
bpr.align();
|
||||
EXPECT_THAT(bpr.error(), Eq(ReaderError::InvalidData));
|
||||
}
|
||||
|
||||
|
||||
TYPED_TEST(InputAll, WhenAllBytesAreReadWithoutErrorsThenIsCompletedSuccessfully) {
|
||||
//setup data
|
||||
|
||||
uint32_t tb = 94545646;
|
||||
int16_t tc = -8778;
|
||||
uint8_t td = 200;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
OutputAdapter bw{buf};
|
||||
|
||||
bw.writeBytes<4>(tb);
|
||||
bw.writeBytes<2>(tc);
|
||||
bw.writeBytes<1>(td);
|
||||
bw.flush();
|
||||
buf.resize(bw.writtenBytesCount());
|
||||
|
||||
auto br = this->config.createReader(buf);
|
||||
|
||||
uint32_t rb = 94545646;
|
||||
int16_t rc = -8778;
|
||||
uint8_t rd = 200;
|
||||
|
||||
br.template readBytes<4>(rb);
|
||||
EXPECT_THAT(br.error(), Eq(bitsery::ReaderError::NoError));
|
||||
br.template readBytes<2>(rc);
|
||||
EXPECT_THAT(br.error(), Eq(bitsery::ReaderError::NoError));
|
||||
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(false));
|
||||
br.template readBytes<1>(rd);
|
||||
EXPECT_THAT(br.error(), Eq(bitsery::ReaderError::NoError));
|
||||
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(true));
|
||||
|
||||
EXPECT_THAT(rb, Eq(tb));
|
||||
EXPECT_THAT(rc, Eq(tc));
|
||||
EXPECT_THAT(rd, Eq(td));
|
||||
}
|
||||
|
||||
|
||||
TYPED_TEST(InputAll, WhenReadingMoreThanAvailableThenDataOverflow) {
|
||||
//setup data
|
||||
uint8_t t1 = 111;
|
||||
|
||||
Buffer buf{};
|
||||
OutputAdapter w{buf};
|
||||
w.writeBytes<1>(t1);
|
||||
w.flush();
|
||||
buf.resize(w.writtenBytesCount());
|
||||
|
||||
auto r = this->config.createReader(buf);
|
||||
|
||||
uint8_t r1{};
|
||||
EXPECT_THAT(r.isCompletedSuccessfully(), Eq(false));
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::NoError));
|
||||
r.template readBytes<1>(r1);
|
||||
EXPECT_THAT(r.isCompletedSuccessfully(), Eq(true));
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::NoError));
|
||||
EXPECT_THAT(r1, Eq(t1));
|
||||
r.template readBytes<1>(r1);
|
||||
r.template readBytes<1>(r1);
|
||||
EXPECT_THAT(r1, Eq(0));
|
||||
EXPECT_THAT(r.isCompletedSuccessfully(), Eq(false));
|
||||
EXPECT_THAT(r.error(), Eq(ReaderError::DataOverflow));
|
||||
|
||||
}
|
||||
|
||||
TYPED_TEST(InputAll, WhenReaderHasErrorsAllThenReadsReturnZero) {
|
||||
//setup data
|
||||
uint8_t t1 = 111;
|
||||
|
||||
Buffer buf{};
|
||||
OutputAdapter w{buf};
|
||||
w.writeBytes<1>(t1);
|
||||
w.writeBytes<1>(t1);
|
||||
w.flush();
|
||||
buf.resize(w.writtenBytesCount());
|
||||
|
||||
auto r = this->config.createReader(buf);
|
||||
|
||||
uint8_t r1{};
|
||||
r.template readBytes<1>(r1);
|
||||
EXPECT_THAT(r1, Eq(t1));
|
||||
r.error(ReaderError::InvalidPointer);
|
||||
r.template readBytes<1>(r1);
|
||||
EXPECT_THAT(r1, Eq(0));
|
||||
}
|
||||
|
||||
|
||||
template <template<typename...> class TAdapter>
|
||||
struct OutBufferConfig {
|
||||
using Data = std::vector<char>;
|
||||
using Adapter = TAdapter<Data>;
|
||||
|
||||
Data data{};
|
||||
Adapter createWriter() {
|
||||
return Adapter{data};
|
||||
}
|
||||
|
||||
bitsery::InputBufferAdapter<Data> getReader() {
|
||||
return bitsery::InputBufferAdapter<Data>{data.begin(), data.end()};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename TAdapter>
|
||||
struct OutStreamConfig {
|
||||
using Data = std::stringstream;
|
||||
using Adapter = TAdapter;
|
||||
|
||||
Data data{};
|
||||
Adapter createWriter() {
|
||||
return Adapter{data};
|
||||
}
|
||||
|
||||
bitsery::InputStreamAdapter getReader() {
|
||||
return bitsery::InputStreamAdapter{data};
|
||||
}
|
||||
};
|
||||
|
||||
using AdapterOutputTypes = ::testing::Types<
|
||||
OutBufferConfig<bitsery::OutputBufferAdapter>,
|
||||
OutStreamConfig<bitsery::OutputStreamAdapter>,
|
||||
OutStreamConfig<bitsery::OutputBufferedStreamAdapter>
|
||||
>;
|
||||
|
||||
template <typename TConfig>
|
||||
class OutputAll: public AdapterConfig<TConfig> {
|
||||
};
|
||||
|
||||
TYPED_TEST_SUITE(OutputAll, AdapterOutputTypes,);
|
||||
|
||||
TYPED_TEST(OutputAll, CanBeMoveConstructedAndMoveAssigned) {
|
||||
auto w = this->config.createWriter();
|
||||
uint8_t data{1};
|
||||
w.template writeBytes<1>(data);
|
||||
// move construct
|
||||
auto w1 = std::move(w);
|
||||
data = 2;
|
||||
w1.template writeBytes<1>(data);
|
||||
// move assignment
|
||||
w = std::move(w1);
|
||||
data = 3;
|
||||
w.template writeBytes<1>(data);
|
||||
w.flush();
|
||||
|
||||
auto r = this->config.getReader();
|
||||
r.template readBytes<1>(data);
|
||||
EXPECT_THAT(data, Eq(1));
|
||||
r.template readBytes<1>(data);
|
||||
EXPECT_THAT(data, Eq(2));
|
||||
r.template readBytes<1>(data);
|
||||
EXPECT_THAT(data, Eq(3));
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
class OutputStreamBuffered : public testing::Test {
|
||||
public:
|
||||
using Buffer = T;
|
||||
using Adapter = bitsery::BasicBufferedOutputStreamAdapter<char, bitsery::DefaultConfig, std::char_traits<char>, Buffer>;
|
||||
|
||||
static constexpr size_t InternalBufferSize = 128;
|
||||
|
||||
std::stringstream stream{};
|
||||
|
||||
Adapter writer{stream, 128};
|
||||
};
|
||||
|
||||
using BufferedAdapterInternalBufferTypes = ::testing::Types<
|
||||
std::vector<char>,
|
||||
std::array<char, 128>,
|
||||
std::string
|
||||
>;
|
||||
|
||||
TYPED_TEST_SUITE(OutputStreamBuffered, BufferedAdapterInternalBufferTypes,);
|
||||
|
||||
TYPED_TEST(OutputStreamBuffered, WhenInternalBufferIsFullThenWriteBufferToStream) {
|
||||
uint8_t x{};
|
||||
for (auto i = 0u; i < TestFixture::InternalBufferSize; ++i)
|
||||
this->writer.template writeBytes<1>(x);
|
||||
EXPECT_TRUE(this->stream.str().empty());
|
||||
this->writer.template writeBytes<1>(x);
|
||||
EXPECT_THAT(this->stream.str().size(), Eq(TestFixture::InternalBufferSize));
|
||||
}
|
||||
|
||||
TYPED_TEST(OutputStreamBuffered, WhenFlushThenWriteImmediately) {
|
||||
uint8_t x{};
|
||||
this->writer.template writeBytes<1>(x);
|
||||
EXPECT_THAT(this->stream.str().size(), Eq(0));
|
||||
this->writer.flush();
|
||||
EXPECT_THAT(this->stream.str().size(), Eq(1));
|
||||
this->writer.flush();
|
||||
EXPECT_THAT(this->stream.str().size(), Eq(1));
|
||||
}
|
||||
|
||||
TYPED_TEST(OutputStreamBuffered, WhenBufferIsStackAllocatedThenBufferSizeViaCtorHasNoEffect) {
|
||||
|
||||
//create writer with half the internal buffer size
|
||||
//for std::vector it should overflow, and for std::array it should have no effect
|
||||
typename TestFixture::Adapter w{this->stream, TestFixture::InternalBufferSize / 2};
|
||||
|
||||
uint8_t x{};
|
||||
for (auto i = 0u; i < TestFixture::InternalBufferSize; ++i)
|
||||
w.template writeBytes<1>(x);
|
||||
static constexpr bool ShouldWriteToStream = bitsery::traits::ContainerTraits<typename TestFixture::Buffer>::isResizable;
|
||||
EXPECT_THAT(this->stream.str().empty(), ::testing::Ne(ShouldWriteToStream));
|
||||
}
|
||||
|
||||
TEST(AdapterWriterMeasureSize, CorrectlyMeasuresWrittenBytesCountForSerialization) {
|
||||
bitsery::MeasureSize w{};
|
||||
EXPECT_THAT(w.writtenBytesCount(), Eq(0));
|
||||
w.writeBytes<8>(uint64_t{0});
|
||||
EXPECT_THAT(w.writtenBytesCount(), Eq(8));
|
||||
w.writeBuffer<8, uint64_t>(nullptr, 9);
|
||||
EXPECT_THAT(w.writtenBytesCount(), Eq(80));
|
||||
w.currentWritePos(10);
|
||||
w.writeBytes<4>(uint32_t{0});
|
||||
EXPECT_THAT(w.writtenBytesCount(), Eq(80));
|
||||
EXPECT_THAT(w.currentWritePos(), Eq(14));
|
||||
w.currentWritePos(80);
|
||||
EXPECT_THAT(w.writtenBytesCount(), Eq(80));
|
||||
w.writeBits(uint32_t{0}, 7u);
|
||||
EXPECT_THAT(w.writtenBytesCount(), Eq(80));
|
||||
w.align();
|
||||
EXPECT_THAT(w.writtenBytesCount(), Eq(81));
|
||||
w.writeBits(uint32_t{0}, 7u);
|
||||
w.flush();
|
||||
EXPECT_THAT(w.writtenBytesCount(), Eq(82));
|
||||
// doesn't compile on older compilers if I write bitsery::MeasureSize::BitPackingEnabled directly in EXPECT_THAT macro.
|
||||
constexpr bool bpEnabled = bitsery::MeasureSize::BitPackingEnabled;
|
||||
EXPECT_THAT(bpEnabled, Eq(true));
|
||||
}
|
||||
@@ -1,162 +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 <bitsery/adapter/stream.h>
|
||||
#include <bitsery/adapter_writer.h>
|
||||
#include <bitsery/adapter_reader.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
#include <bitsery/traits/array.h>
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <sstream>
|
||||
|
||||
//some helper types
|
||||
using Stream = std::stringstream;
|
||||
using OutputAdapter = bitsery::OutputStreamAdapter;
|
||||
using InputAdapter = bitsery::InputStreamAdapter ;
|
||||
using Writer = bitsery::AdapterWriter<bitsery::OutputStreamAdapter, bitsery::DefaultConfig>;
|
||||
using Reader = bitsery::AdapterReader<bitsery::InputStreamAdapter, bitsery::DefaultConfig>;
|
||||
|
||||
static constexpr size_t InternalBufferSize = 128;
|
||||
using BufferedAdapterInternalBuffer = std::array<char, InternalBufferSize>;
|
||||
using OutputBufferedAdapter = bitsery::BasicBufferedOutputStreamAdapter<char, std::char_traits<char>, BufferedAdapterInternalBuffer>;
|
||||
using BufferedWriter = bitsery::AdapterWriter<OutputBufferedAdapter, bitsery::DefaultConfig>;
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
TEST(AdapterIOStream, CorrectlyReturnsIsCompletedSuccessfully) {
|
||||
//setup data
|
||||
uint8_t t1 = 111;
|
||||
|
||||
Stream buf{};
|
||||
Writer w{{buf}};
|
||||
w.writeBytes<1>(t1);
|
||||
w.flush();
|
||||
|
||||
Reader r{{buf}};
|
||||
|
||||
uint8_t r1{};
|
||||
EXPECT_THAT(r.isCompletedSuccessfully(), Eq(false));
|
||||
r.readBytes<1>(r1);
|
||||
EXPECT_THAT(r.isCompletedSuccessfully(), Eq(true));
|
||||
EXPECT_THAT(r1, Eq(t1));
|
||||
}
|
||||
|
||||
TEST(AdapterIOStream, ReadingMoreThanAvailableReturnsZero) {
|
||||
//setup data
|
||||
uint8_t t1 = 111;
|
||||
|
||||
Stream buf{};
|
||||
Writer w{{buf}};
|
||||
w.writeBytes<1>(t1);
|
||||
w.flush();
|
||||
|
||||
Reader r{{buf}};
|
||||
|
||||
uint8_t r1{};
|
||||
r.readBytes<1>(r1);
|
||||
r.readBytes<1>(r1);
|
||||
EXPECT_THAT(r1, Eq(0));
|
||||
}
|
||||
|
||||
//this is strange, but probably stringstream doesnt use any of the base methods that sets io_base::iostate flags
|
||||
TEST(AdapterIOStream, WhenReadingMoreThanAvailableThenDataOverflow) {
|
||||
//setup data
|
||||
uint8_t t1 = 111;
|
||||
|
||||
Stream buf{};
|
||||
Writer w{{buf}};
|
||||
w.writeBytes<1>(t1);
|
||||
w.flush();
|
||||
|
||||
Reader r{{buf}};
|
||||
|
||||
uint8_t r1{};
|
||||
EXPECT_THAT(r.isCompletedSuccessfully(), Eq(false));
|
||||
EXPECT_THAT(r.error(), Eq(bitsery::ReaderError::NoError));
|
||||
r.readBytes<1>(r1);
|
||||
EXPECT_THAT(r.isCompletedSuccessfully(), Eq(true));
|
||||
EXPECT_THAT(r.error(), Eq(bitsery::ReaderError::NoError));
|
||||
EXPECT_THAT(r1, Eq(t1));
|
||||
r.readBytes<1>(r1);
|
||||
r.readBytes<1>(r1);
|
||||
EXPECT_THAT(r1, Eq(0));
|
||||
EXPECT_THAT(r.isCompletedSuccessfully(), Eq(false));
|
||||
EXPECT_THAT(r.error(), Eq(bitsery::ReaderError::DataOverflow));
|
||||
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
class AdapterBufferedOutputStream : public testing::Test {
|
||||
public:
|
||||
using Buffer = T;
|
||||
using Adapter = bitsery::BasicBufferedOutputStreamAdapter<char, std::char_traits<char>, Buffer>;
|
||||
using Writer = bitsery::AdapterWriter<Adapter, bitsery::DefaultConfig>;
|
||||
|
||||
static constexpr size_t InternalBufferSize = 128;
|
||||
|
||||
Stream stream{};
|
||||
|
||||
Writer writer{{stream, 128}};
|
||||
};
|
||||
|
||||
using BufferedAdapterInternalBufferTypes = ::testing::Types<
|
||||
std::vector<char>,
|
||||
std::array<char, 128>,
|
||||
std::string
|
||||
>;
|
||||
|
||||
TYPED_TEST_CASE(AdapterBufferedOutputStream, BufferedAdapterInternalBufferTypes);
|
||||
|
||||
TYPED_TEST(AdapterBufferedOutputStream, WhenBufferOverflowThenWriteBufferAndRemainingDataToStream) {
|
||||
uint8_t x{};
|
||||
for (auto i = 0u; i < TestFixture::InternalBufferSize; ++i)
|
||||
this->writer.template writeBytes<1>(x);
|
||||
EXPECT_TRUE(this->stream.str().empty());
|
||||
this->writer.template writeBytes<1>(x);
|
||||
EXPECT_THAT(this->stream.str().size(), Eq(TestFixture::InternalBufferSize + 1));
|
||||
}
|
||||
|
||||
TYPED_TEST(AdapterBufferedOutputStream, WhenFlushThenWriteImmediately) {
|
||||
uint8_t x{};
|
||||
this->writer.template writeBytes<1>(x);
|
||||
EXPECT_THAT(this->stream.str().size(), Eq(0));
|
||||
this->writer.flush();
|
||||
EXPECT_THAT(this->stream.str().size(), Eq(1));
|
||||
this->writer.flush();
|
||||
EXPECT_THAT(this->stream.str().size(), Eq(1));
|
||||
}
|
||||
|
||||
TYPED_TEST(AdapterBufferedOutputStream, WhenBufferIsStackAllocatedThenBufferSizeViaCtorHasNoEffect) {
|
||||
|
||||
//create writer with half the internal buffer size
|
||||
//for std::vector it should overflow, and for std::array it should have no effect
|
||||
typename TestFixture::Writer w{{this->stream, TestFixture::InternalBufferSize / 2}};
|
||||
|
||||
uint8_t x{};
|
||||
for (auto i = 0u; i < TestFixture::InternalBufferSize; ++i)
|
||||
w.template writeBytes<1>(x);
|
||||
static constexpr bool ShouldWriteToStream = bitsery::traits::ContainerTraits<typename TestFixture::Buffer>::isResizable;
|
||||
EXPECT_THAT(this->stream.str().empty(), ::testing::Ne(ShouldWriteToStream));
|
||||
}
|
||||
@@ -20,46 +20,49 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#include <bitsery/flexible.h>
|
||||
#include <bitsery/flexible/string.h>
|
||||
#include <bitsery/flexible/array.h>
|
||||
#include <bitsery/flexible/vector.h>
|
||||
#include <bitsery/flexible/list.h>
|
||||
#include <bitsery/flexible/forward_list.h>
|
||||
#include <bitsery/flexible/deque.h>
|
||||
#include <bitsery/flexible/queue.h>
|
||||
#include <bitsery/flexible/stack.h>
|
||||
#include <bitsery/flexible/map.h>
|
||||
#include <bitsery/flexible/unordered_map.h>
|
||||
#include <bitsery/flexible/set.h>
|
||||
#include <bitsery/flexible/unordered_set.h>
|
||||
#include <bitsery/flexible/memory.h>
|
||||
#include <bitsery/flexible/chrono.h>
|
||||
#include <bitsery/brief_syntax.h>
|
||||
#include <bitsery/brief_syntax/array.h>
|
||||
#include <bitsery/brief_syntax/atomic.h>
|
||||
#include <bitsery/brief_syntax/chrono.h>
|
||||
#include <bitsery/brief_syntax/deque.h>
|
||||
#include <bitsery/brief_syntax/forward_list.h>
|
||||
#include <bitsery/brief_syntax/list.h>
|
||||
#include <bitsery/brief_syntax/map.h>
|
||||
#include <bitsery/brief_syntax/memory.h>
|
||||
#include <bitsery/brief_syntax/queue.h>
|
||||
#include <bitsery/brief_syntax/set.h>
|
||||
#include <bitsery/brief_syntax/stack.h>
|
||||
#include <bitsery/brief_syntax/string.h>
|
||||
#include <bitsery/brief_syntax/unordered_map.h>
|
||||
#include <bitsery/brief_syntax/unordered_set.h>
|
||||
#include <bitsery/brief_syntax/vector.h>
|
||||
#if __cplusplus > 201402L
|
||||
#include <bitsery/flexible/tuple.h>
|
||||
#include <bitsery/flexible/variant.h>
|
||||
#include <bitsery/brief_syntax/tuple.h>
|
||||
#include <bitsery/brief_syntax/variant.h>
|
||||
#else
|
||||
#if defined(_MSC_VER)
|
||||
/*#if defined(_MSC_VER)
|
||||
#pragma message("tuple and variant only works with c++17")
|
||||
#else
|
||||
#warning "tuple and variant only works with c++17"
|
||||
#endif
|
||||
#endif*/
|
||||
#endif
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <utility>
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
TEST(FlexibleSyntax, FundamentalTypesAndBool) {
|
||||
TEST(BriefSyntax, FundamentalTypesAndBool) {
|
||||
int ti = 8745;
|
||||
MyEnumClass te = MyEnumClass::E4;
|
||||
float tf = 485.042f;
|
||||
double td = -454184.48445;
|
||||
bool tb = true;
|
||||
SerializationContext ctx{};
|
||||
ctx.createSerializer().archive(ti, te, tf, td, tb);
|
||||
ctx.createSerializer()(ti, te, tf, td, tb);
|
||||
|
||||
//result
|
||||
int ri{};
|
||||
@@ -67,7 +70,7 @@ TEST(FlexibleSyntax, FundamentalTypesAndBool) {
|
||||
float rf{};
|
||||
double rd{};
|
||||
bool rb{};
|
||||
ctx.createDeserializer().archive(ri, re, rf, rd, rb);
|
||||
ctx.createDeserializer()(ri, re, rf, rd, rb);
|
||||
|
||||
//test
|
||||
EXPECT_THAT(ri, Eq(ti));
|
||||
@@ -77,14 +80,14 @@ TEST(FlexibleSyntax, FundamentalTypesAndBool) {
|
||||
EXPECT_THAT(rb, Eq(tb));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, UseObjectFncInsteadOfValueN) {
|
||||
TEST(BriefSyntax, UseObjectFncInsteadOfValueN) {
|
||||
int ti = 8745;
|
||||
MyEnumClass te = MyEnumClass::E4;
|
||||
float tf = 485.042f;
|
||||
double td = -454184.48445;
|
||||
bool tb = true;
|
||||
SerializationContext ctx;
|
||||
auto &ser = ctx.createSerializer();
|
||||
auto& ser = ctx.createSerializer();
|
||||
ser.object(ti);
|
||||
ser.object(te);
|
||||
ser.object(tf);
|
||||
@@ -97,7 +100,7 @@ TEST(FlexibleSyntax, UseObjectFncInsteadOfValueN) {
|
||||
float rf{};
|
||||
double rd{};
|
||||
bool rb{};
|
||||
auto &des = ctx.createDeserializer();
|
||||
auto& des = ctx.createDeserializer();
|
||||
des.object(ri);
|
||||
des.object(re);
|
||||
des.object(rf);
|
||||
@@ -112,16 +115,16 @@ TEST(FlexibleSyntax, UseObjectFncInsteadOfValueN) {
|
||||
EXPECT_THAT(rb, Eq(tb));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, MixDifferentSyntax) {
|
||||
TEST(BriefSyntax, MixDifferentSyntax) {
|
||||
int ti = 8745;
|
||||
MyEnumClass te = MyEnumClass::E4;
|
||||
float tf = 485.042f;
|
||||
double td = -454184.48445;
|
||||
bool tb = true;
|
||||
SerializationContext ctx;
|
||||
auto &ser = ctx.createSerializer();
|
||||
auto& ser = ctx.createSerializer();
|
||||
ser.value<sizeof(ti)>(ti);
|
||||
ser.archive(te, tf, td);
|
||||
ser(te, tf, td);
|
||||
ser.object(tb);
|
||||
|
||||
//result
|
||||
@@ -130,8 +133,8 @@ TEST(FlexibleSyntax, MixDifferentSyntax) {
|
||||
float rf{};
|
||||
double rd{};
|
||||
bool rb{};
|
||||
auto &des = ctx.createDeserializer();
|
||||
des.archive(ri, re, rf);
|
||||
auto& des = ctx.createDeserializer();
|
||||
des(ri, re, rf);
|
||||
des.value8b(rd);
|
||||
des.object(rb);
|
||||
|
||||
@@ -144,130 +147,138 @@ TEST(FlexibleSyntax, MixDifferentSyntax) {
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T procArchive(const T& testData) {
|
||||
T procBriefSyntax(const T& testData) {
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().archive(testData);
|
||||
ctx.createSerializer()(testData);
|
||||
T res{};
|
||||
ctx.createDeserializer().archive(res);
|
||||
ctx.createDeserializer()(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T procArchiveWithMaxSize(const T& testData) {
|
||||
T&& procBriefSyntaxRvalue(T&& init_value, const T& testData) {
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().archive(bitsery::maxSize(testData, 100));
|
||||
ctx.createSerializer()(testData);
|
||||
ctx.createDeserializer()(init_value);
|
||||
return std::move(init_value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T procBriefSyntaxWithMaxSize(const T& testData) {
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer()(bitsery::maxSize(testData, 100));
|
||||
T res{};
|
||||
ctx.createDeserializer().archive(bitsery::maxSize(res, 100));
|
||||
ctx.createDeserializer()(bitsery::maxSize(res, 100));
|
||||
return res;
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, CStyleArrayForValueTypesAsContainer) {
|
||||
TEST(BriefSyntax, CStyleArrayForValueTypesAsContainer) {
|
||||
const int t1[3]{8748, -484, 45};
|
||||
int r1[3]{0, 0, 0};
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().archive(bitsery::asContainer(t1));
|
||||
ctx.createDeserializer().archive(bitsery::asContainer(r1));
|
||||
ctx.createSerializer()(bitsery::asContainer(t1));
|
||||
ctx.createDeserializer()(bitsery::asContainer(r1));
|
||||
|
||||
EXPECT_THAT(r1, ::testing::ContainerEq(t1));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, CStyleArrayForIntegralTypesAsText) {
|
||||
TEST(BriefSyntax, CStyleArrayForIntegralTypesAsText) {
|
||||
const char t1[3]{"hi"};
|
||||
char r1[3]{0, 0, 0};
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().archive(bitsery::asText(t1));
|
||||
ctx.createDeserializer().archive(bitsery::asText(r1));
|
||||
ctx.createSerializer()(bitsery::asText(t1));
|
||||
ctx.createDeserializer()(bitsery::asText(r1));
|
||||
|
||||
EXPECT_THAT(r1, ::testing::ContainerEq(t1));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, CStyleArray) {
|
||||
TEST(BriefSyntax, CStyleArray) {
|
||||
const MyEnumClass t1[3]{MyEnumClass::E1, MyEnumClass::E4, MyEnumClass::E2};
|
||||
MyEnumClass r1[3]{};
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().archive(t1);
|
||||
ctx.createDeserializer().archive(r1);
|
||||
ctx.createSerializer()(t1);
|
||||
ctx.createDeserializer()(r1);
|
||||
|
||||
EXPECT_THAT(r1, ::testing::ContainerEq(t1));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdString) {
|
||||
TEST(BriefSyntax, StdString) {
|
||||
std::string t1{"my nice string"};
|
||||
std::string t2{};
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchive(t2), Eq(t2));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t2), Eq(t2));
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntax(t2), Eq(t2));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t2), Eq(t2));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdArray) {
|
||||
TEST(BriefSyntax, StdArray) {
|
||||
std::array<int, 3> t1{8748, -484, 45};
|
||||
std::array<int, 0> t2{};
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchive(t2), Eq(t2));
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntax(t2), Eq(t2));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdVector) {
|
||||
TEST(BriefSyntax, StdVector) {
|
||||
std::vector<int> t1{8748, -484, 45};
|
||||
std::vector<float> t2{5.f, 0.198f};
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchive(t2), Eq(t2));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t2), Eq(t2));
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntax(t2), Eq(t2));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t2), Eq(t2));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdList) {
|
||||
TEST(BriefSyntax, StdList) {
|
||||
std::list<int> t1{8748, -484, 45};
|
||||
std::list<float> t2{5.f, 0.198f};
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchive(t2), Eq(t2));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t2), Eq(t2));
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntax(t2), Eq(t2));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t2), Eq(t2));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdForwardList) {
|
||||
TEST(BriefSyntax, StdForwardList) {
|
||||
std::forward_list<int> t1{8748, -484, 45};
|
||||
std::forward_list<float> t2{5.f, 0.198f};
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchive(t2), Eq(t2));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t2), Eq(t2));
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntax(t2), Eq(t2));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t2), Eq(t2));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdDeque) {
|
||||
TEST(BriefSyntax, StdDeque) {
|
||||
std::deque<int> t1{8748, -484, 45};
|
||||
std::deque<float> t2{5.f, 0.198f};
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchive(t2), Eq(t2));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t2), Eq(t2));
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntax(t2), Eq(t2));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t2), Eq(t2));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdQueue) {
|
||||
TEST(BriefSyntax, StdQueue) {
|
||||
std::queue<std::string> t1;
|
||||
t1.push("first");
|
||||
t1.push("second string");
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdPriorityQueue) {
|
||||
TEST(BriefSyntax, StdPriorityQueue) {
|
||||
std::priority_queue<std::string> t1;
|
||||
t1.push("first");
|
||||
t1.push("second string");
|
||||
t1.push("third");
|
||||
t1.push("fourth");
|
||||
auto r1 = procArchive(t1);
|
||||
auto r1 = procBriefSyntax(t1);
|
||||
//we cannot compare priority queue directly
|
||||
|
||||
EXPECT_THAT(r1.size(), Eq(t1.size()));
|
||||
@@ -278,50 +289,50 @@ TEST(FlexibleSyntax, StdPriorityQueue) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdStack) {
|
||||
TEST(BriefSyntax, StdStack) {
|
||||
std::stack<std::string> t1;
|
||||
t1.push("first");
|
||||
t1.push("second string");
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdUnorderedMap) {
|
||||
TEST(BriefSyntax, StdUnorderedMap) {
|
||||
std::unordered_map<int, int> t1;
|
||||
t1.emplace(3423, 624);
|
||||
t1.emplace(-5484, -845);
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdUnorderedMultiMap) {
|
||||
TEST(BriefSyntax, StdUnorderedMultiMap) {
|
||||
std::unordered_multimap<std::string, int> t1;
|
||||
t1.emplace("one", 624);
|
||||
t1.emplace("two", -845);
|
||||
t1.emplace("one", 897);
|
||||
|
||||
EXPECT_TRUE(procArchive(t1) == t1);
|
||||
EXPECT_TRUE(procArchiveWithMaxSize(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntaxWithMaxSize(t1) == t1);
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdMap) {
|
||||
TEST(BriefSyntax, StdMap) {
|
||||
std::map<int, int> t1;
|
||||
t1.emplace(3423, 624);
|
||||
t1.emplace(-5484, -845);
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdMultiMap) {
|
||||
TEST(BriefSyntax, StdMultiMap) {
|
||||
std::multimap<std::string, int> t1;
|
||||
t1.emplace("one", 624);
|
||||
t1.emplace("two", -845);
|
||||
t1.emplace("one", 897);
|
||||
|
||||
auto res = procArchive(t1);
|
||||
auto res = procBriefSyntax(t1);
|
||||
//same key values is not ordered, and operator == compares each element at same position
|
||||
//so we need to compare our selves
|
||||
EXPECT_THAT(res.size(), Eq(3));
|
||||
@@ -334,38 +345,38 @@ TEST(FlexibleSyntax, StdMultiMap) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdUnorderedSet) {
|
||||
TEST(BriefSyntax, StdUnorderedSet) {
|
||||
std::unordered_set<std::string> t1;
|
||||
t1.emplace("one");
|
||||
t1.emplace("two");
|
||||
t1.emplace("three");
|
||||
|
||||
EXPECT_TRUE(procArchive(t1) == t1);
|
||||
EXPECT_TRUE(procArchiveWithMaxSize(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntaxWithMaxSize(t1) == t1);
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdUnorderedMultiSet) {
|
||||
TEST(BriefSyntax, StdUnorderedMultiSet) {
|
||||
std::unordered_multiset<std::string> t1;
|
||||
t1.emplace("one");
|
||||
t1.emplace("two");
|
||||
t1.emplace("three");
|
||||
t1.emplace("one");
|
||||
|
||||
EXPECT_TRUE(procArchive(t1) == t1);
|
||||
EXPECT_TRUE(procArchiveWithMaxSize(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntaxWithMaxSize(t1) == t1);
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdSet) {
|
||||
TEST(BriefSyntax, StdSet) {
|
||||
std::set<std::string> t1;
|
||||
t1.emplace("one");
|
||||
t1.emplace("two");
|
||||
t1.emplace("three");
|
||||
|
||||
EXPECT_TRUE(procArchive(t1) == t1);
|
||||
EXPECT_TRUE(procArchiveWithMaxSize(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntaxWithMaxSize(t1) == t1);
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdMultiSet) {
|
||||
TEST(BriefSyntax, StdMultiSet) {
|
||||
std::multiset<std::string> t1;
|
||||
t1.emplace("one");
|
||||
t1.emplace("two");
|
||||
@@ -373,23 +384,23 @@ TEST(FlexibleSyntax, StdMultiSet) {
|
||||
t1.emplace("one");
|
||||
t1.emplace("two");
|
||||
|
||||
EXPECT_TRUE(procArchive(t1) == t1);
|
||||
EXPECT_TRUE(procArchiveWithMaxSize(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntaxWithMaxSize(t1) == t1);
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdSmartPtr) {
|
||||
TEST(BriefSyntax, StdSmartPtr) {
|
||||
std::shared_ptr<int> dataShared1(new int{4});
|
||||
std::weak_ptr<int> dataWeak1(dataShared1);
|
||||
std::unique_ptr<std::string> dataUnique1{new std::string{"hello world"}};
|
||||
|
||||
bitsery::ext::PointerLinkingContext plctx1{};
|
||||
BasicSerializationContext<bitsery::DefaultConfig, bitsery::ext::PointerLinkingContext> ctx;
|
||||
ctx.createSerializer(&plctx1).archive(dataShared1, dataWeak1, dataUnique1);
|
||||
BasicSerializationContext<bitsery::ext::PointerLinkingContext> ctx;
|
||||
ctx.createSerializer(plctx1)(dataShared1, dataWeak1, dataUnique1);
|
||||
|
||||
std::shared_ptr<int> resShared1{};
|
||||
std::weak_ptr<int> resWeak1{};
|
||||
std::unique_ptr<std::string> resUnique1{};
|
||||
ctx.createDeserializer(&plctx1).archive(resShared1, resWeak1, resUnique1);
|
||||
ctx.createDeserializer(plctx1)(resShared1, resWeak1, resUnique1);
|
||||
//clear shared state from pointer linking context
|
||||
plctx1.clearSharedState();
|
||||
|
||||
@@ -399,38 +410,53 @@ TEST(FlexibleSyntax, StdSmartPtr) {
|
||||
EXPECT_THAT(*resUnique1, Eq(*dataUnique1));
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdDuration) {
|
||||
TEST(BriefSyntax, StdDuration) {
|
||||
std::chrono::duration<int64_t, std::milli> t1{54654};
|
||||
EXPECT_TRUE(procArchive(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdTimePoint) {
|
||||
TEST(BriefSyntax, StdTimePoint) {
|
||||
using Duration = std::chrono::duration<double, std::milli>;
|
||||
using TP = std::chrono::time_point<std::chrono::system_clock, Duration>;
|
||||
|
||||
TP data{Duration{874656.4798}};
|
||||
EXPECT_TRUE(procArchive(data) == data);
|
||||
EXPECT_TRUE(procBriefSyntax(data) == data);
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdAtomic) {
|
||||
std::atomic<int32_t> atm0{54654};
|
||||
EXPECT_TRUE(procBriefSyntaxRvalue(std::atomic<int32_t>{}, atm0) == atm0);
|
||||
|
||||
std::atomic<bool> atm1{false};
|
||||
EXPECT_TRUE(procBriefSyntaxRvalue(std::atomic<bool>{}, atm1) == atm1);
|
||||
|
||||
std::atomic<bool> atm2{true};
|
||||
EXPECT_TRUE(procBriefSyntaxRvalue(std::atomic<bool>{}, atm2) == atm2);
|
||||
|
||||
std::atomic<uint16_t> atm3;
|
||||
atm3.store(0x1337);
|
||||
EXPECT_TRUE(procBriefSyntaxRvalue(std::atomic<uint16_t>{}, atm3).load() == 0x1337);
|
||||
}
|
||||
|
||||
#if __cplusplus > 201402L
|
||||
|
||||
TEST(FlexibleSyntax, StdTuple) {
|
||||
TEST(BriefSyntax, StdTuple) {
|
||||
std::tuple<int, std::string, std::vector<char>> t1{5,"hello hello", {'A','B','C'}};
|
||||
EXPECT_TRUE(procArchive(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdVariant) {
|
||||
TEST(BriefSyntax, StdVariant) {
|
||||
std::variant<float, std::string, std::chrono::milliseconds> t1{std::string("hello hello")};
|
||||
EXPECT_TRUE(procArchive(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST(FlexibleSyntax, NestedTypes) {
|
||||
TEST(BriefSyntax, NestedTypes) {
|
||||
std::unordered_map<std::string, std::vector<std::string>> t1;
|
||||
t1.emplace("my key", std::vector<std::string>{"very", "nice", "string"});
|
||||
t1.emplace("other key", std::vector<std::string>{"just a string"});
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
}
|
||||
@@ -20,10 +20,9 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#include <bitsery/adapter_writer.h>
|
||||
#include <bitsery/adapter_reader.h>
|
||||
#include <bitsery/ext/value_range.h>
|
||||
#include <bitsery/serializer.h>
|
||||
#include <bitsery/deserializer.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
@@ -38,8 +37,10 @@ constexpr EndiannessType getInverseEndianness(EndiannessType e) {
|
||||
: EndiannessType::LittleEndian;
|
||||
}
|
||||
|
||||
struct InverseEndiannessConfig:public DefaultConfig {
|
||||
static constexpr bitsery::EndiannessType NetworkEndianness = getInverseEndianness(DefaultConfig::NetworkEndianness);
|
||||
struct InverseEndiannessConfig {
|
||||
static constexpr bitsery::EndiannessType Endianness = getInverseEndianness(DefaultConfig::Endianness);
|
||||
static constexpr bool CheckDataErrors = true;
|
||||
static constexpr bool CheckAdapterErrors = true;
|
||||
};
|
||||
|
||||
struct IntegralTypes {
|
||||
@@ -50,7 +51,7 @@ struct IntegralTypes {
|
||||
int8_t e;
|
||||
};
|
||||
|
||||
using InverseReader = bitsery::AdapterReader<InputAdapter, InverseEndiannessConfig>;
|
||||
using InverseReader = bitsery::InputBufferAdapter<Buffer, InverseEndiannessConfig>;
|
||||
|
||||
|
||||
TEST(DataEndianness, WhenWriteBytesThenBytesAreSwapped) {
|
||||
@@ -80,7 +81,7 @@ TEST(DataEndianness, WhenWriteBytesThenBytesAreSwapped) {
|
||||
bw.writeBytes<1>(src.e);
|
||||
bw.flush();
|
||||
//read from buffer using inverse endianness config
|
||||
InverseReader br{InputAdapter{buf.begin(), bw.writtenBytesCount()}};
|
||||
InverseReader br{buf.begin(), bw.writtenBytesCount()};
|
||||
IntegralTypes res{};
|
||||
br.readBytes<8>(res.a);
|
||||
br.readBytes<4>(res.b);
|
||||
@@ -106,7 +107,7 @@ TEST(DataEndianness, WhenWrite1ByteValuesThenEndiannessIsIgnored) {
|
||||
bw.writeBuffer<1>(src, SIZE);
|
||||
bw.flush();
|
||||
//read from buffer using inverse endianness config
|
||||
InverseReader br{InputAdapter{buf.begin(), bw.writtenBytesCount()}};
|
||||
InverseReader br{buf.begin(), bw.writtenBytesCount()};
|
||||
br.readBuffer<1>(res, SIZE);
|
||||
//result is identical, because we write separate values, of size 1byte, that requires no swapping
|
||||
//check results
|
||||
@@ -125,7 +126,7 @@ TEST(DataEndianness, WhenWriteMoreThan1ByteValuesThenValuesAreSwapped) {
|
||||
bw.writeBuffer<2>(src, SIZE);
|
||||
bw.flush();
|
||||
//read from buffer using inverse endianness config
|
||||
InverseReader br{InputAdapter{buf.begin(), bw.writtenBytesCount()}};
|
||||
InverseReader br{buf.begin(), bw.writtenBytesCount()};
|
||||
br.readBuffer<2>(res, SIZE);
|
||||
//result is identical, because we write separate values, of size 1byte, that requires no swapping
|
||||
//check results
|
||||
@@ -161,15 +162,15 @@ TEST(DataEndianness, WhenValueTypeIs1ByteThenBitOperationsIsNotAffectedByEndiann
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
Writer bw{buf};
|
||||
bitsery::AdapterWriterBitPackingWrapper<Writer> bpw{bw};
|
||||
bitsery::details::OutputAdapterBitPackingWrapper<Writer> bpw{bw};
|
||||
bpw.writeBits(src.a, aBITS);
|
||||
bpw.writeBits(src.b, bBITS);
|
||||
bpw.writeBits(src.c, cBITS);
|
||||
bpw.writeBits(src.d, dBITS);
|
||||
bpw.flush();
|
||||
//read from buffer using inverse endianness config
|
||||
InverseReader br{InputAdapter{buf.begin(), bpw.writtenBytesCount()}};
|
||||
bitsery::AdapterReaderBitPackingWrapper<InverseReader> bpr{br};
|
||||
InverseReader br{buf.begin(), bpw.writtenBytesCount()};
|
||||
bitsery::details::InputAdapterBitPackingWrapper<InverseReader> bpr{br};
|
||||
IntegralUnsignedTypes res{};
|
||||
bpr.readBits(res.a, aBITS);
|
||||
bpr.readBits(res.b, bBITS);
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
|
||||
#include <bitsery/ext/value_range.h>
|
||||
#include <bitsery/serializer.h>
|
||||
#include <bitsery/deserializer.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
@@ -29,8 +31,8 @@
|
||||
using testing::Eq;
|
||||
using testing::ContainerEq;
|
||||
|
||||
using AdapterBitPackingWriter = bitsery::AdapterWriterBitPackingWrapper<Writer>;
|
||||
using AdapterBitPackingReader = bitsery::AdapterReaderBitPackingWrapper<Reader>;
|
||||
using AdapterBitPackingWriter = bitsery::details::OutputAdapterBitPackingWrapper<Writer>;
|
||||
using AdapterBitPackingReader = bitsery::details::InputAdapterBitPackingWrapper<Reader>;
|
||||
|
||||
|
||||
struct IntegralUnsignedTypes {
|
||||
@@ -58,7 +60,7 @@ TEST(DataBitsAndBytesOperations, WriteAndReadBitsMaxTypeValues) {
|
||||
bpw.writeBits(std::numeric_limits<uint8_t>::max(), 8);
|
||||
bpw.flush();
|
||||
|
||||
Reader br{InputAdapter{buf.begin(), bpw.writtenBytesCount()}};
|
||||
Reader br{buf.begin(), bpw.writtenBytesCount()};
|
||||
AdapterBitPackingReader bpr{br};
|
||||
uint64_t v64{};
|
||||
uint32_t v32{};
|
||||
@@ -106,7 +108,7 @@ TEST(DataBitsAndBytesOperations, WriteAndReadBits) {
|
||||
auto bytesCount = ((aBITS + bBITS + cBITS + dBITS + eBITS) / 8) +1 ;
|
||||
EXPECT_THAT(writtenSize, Eq(bytesCount));
|
||||
//read from buffer
|
||||
Reader br{InputAdapter{buf.begin(), writtenSize}};
|
||||
Reader br{buf.begin(), writtenSize};
|
||||
AdapterBitPackingReader bpr{br};
|
||||
|
||||
IntegralUnsignedTypes res{};
|
||||
@@ -139,7 +141,7 @@ TEST(DataBitsAndBytesOperations, WrittenSizeIsCountedPerByteNotPerBit) {
|
||||
EXPECT_THAT(writtenSize, Eq(1));
|
||||
|
||||
//read from buffer
|
||||
Reader br{InputAdapter{buf.begin(), writtenSize}};
|
||||
Reader br{buf.begin(), writtenSize};
|
||||
AdapterBitPackingReader bpr{br};
|
||||
uint16_t tmp;
|
||||
bpr.readBits(tmp,4);
|
||||
@@ -150,7 +152,7 @@ TEST(DataBitsAndBytesOperations, WrittenSizeIsCountedPerByteNotPerBit) {
|
||||
EXPECT_THAT(bpr.error(), Eq(bitsery::ReaderError::DataOverflow));//false
|
||||
|
||||
//part of next byte
|
||||
Reader br1{InputAdapter{buf.begin(), writtenSize}};
|
||||
Reader br1{buf.begin(), writtenSize};
|
||||
AdapterBitPackingReader bpr1{br1};
|
||||
bpr1.readBits(tmp,2);
|
||||
EXPECT_THAT(bpr1.error(), Eq(bitsery::ReaderError::NoError));
|
||||
@@ -158,7 +160,7 @@ TEST(DataBitsAndBytesOperations, WrittenSizeIsCountedPerByteNotPerBit) {
|
||||
EXPECT_THAT(bpr1.error(), Eq(bitsery::ReaderError::DataOverflow));//false
|
||||
|
||||
//bigger than byte
|
||||
Reader br2{InputAdapter{buf.begin(), writtenSize}};
|
||||
Reader br2{buf.begin(), writtenSize};
|
||||
AdapterBitPackingReader bpr2{br2};
|
||||
bpr2.readBits(tmp,9);
|
||||
EXPECT_THAT(bpr2.error(), Eq(bitsery::ReaderError::DataOverflow));//false
|
||||
@@ -181,7 +183,7 @@ TEST(DataBitsAndBytesOperations, ConsecutiveCallsToAlignHasNoEffect) {
|
||||
bpw.flush();
|
||||
|
||||
unsigned char tmp;
|
||||
Reader br{InputAdapter{buf.begin(), bpw.writtenBytesCount()}};
|
||||
Reader br{buf.begin(), bpw.writtenBytesCount()};
|
||||
AdapterBitPackingReader bpr{br};
|
||||
bpr.readBits(tmp,2);
|
||||
EXPECT_THAT(tmp, Eq(3u));
|
||||
@@ -214,14 +216,14 @@ TEST(DataBitsAndBytesOperations, AlignWritesZerosBits) {
|
||||
auto writtenSize = bpw.writtenBytesCount();
|
||||
EXPECT_THAT(writtenSize, Eq(1));
|
||||
unsigned char tmp;
|
||||
Reader br1{InputAdapter{buf.begin(), writtenSize}};
|
||||
Reader br1{buf.begin(), writtenSize};
|
||||
AdapterBitPackingReader bpr1{br1};
|
||||
bpr1.readBits(tmp,2);
|
||||
//read aligned bits
|
||||
bpr1.readBits(tmp,6);
|
||||
EXPECT_THAT(tmp, Eq(0));
|
||||
|
||||
Reader br2{InputAdapter{buf.begin(), writtenSize}};
|
||||
Reader br2{buf.begin(), writtenSize};
|
||||
AdapterBitPackingReader bpr2{br2};
|
||||
//read 2 bits
|
||||
bpr2.readBits(tmp,2);
|
||||
@@ -266,7 +268,7 @@ TEST(DataBitsAndBytesOperations, WriteAndReadBytes) {
|
||||
|
||||
EXPECT_THAT(writtenSize, Eq(18));
|
||||
//read from buffer
|
||||
Reader br{InputAdapter{buf.begin(), writtenSize}};
|
||||
Reader br{buf.begin(), writtenSize};
|
||||
IntegralTypes res{};
|
||||
br.readBytes<4>(res.b);
|
||||
br.readBytes<2>(res.c);
|
||||
@@ -312,7 +314,7 @@ TEST(DataBitsAndBytesOperations, WriteAndReadBytesWithBitPackingWrapper) {
|
||||
|
||||
EXPECT_THAT(writtenSize, Eq(18));
|
||||
//read from buffer
|
||||
Reader br{InputAdapter{buf.begin(), writtenSize}};
|
||||
Reader br{buf.begin(), writtenSize};
|
||||
AdapterBitPackingReader bpr{br};
|
||||
IntegralTypes res{};
|
||||
bpr.readBytes<4>(res.b);
|
||||
@@ -343,7 +345,7 @@ TEST(DataBitsAndBytesOperations, ReadWriteFncCanAcceptSignedData) {
|
||||
bw.writeBuffer<2>(src, DATA_SIZE);
|
||||
bw.flush();
|
||||
//read from buffer
|
||||
Reader br1{InputAdapter{buf.begin(), bw.writtenBytesCount()}};
|
||||
Reader br1{buf.begin(), bw.writtenBytesCount()};
|
||||
int16_t dst[DATA_SIZE]{};
|
||||
br1.readBuffer<2>(dst, DATA_SIZE);
|
||||
EXPECT_THAT(br1.error(), Eq(bitsery::ReaderError::NoError));
|
||||
@@ -366,7 +368,7 @@ TEST(DataBitsAndBytesOperations, ReadWriteCanWorkOnUnalignedData) {
|
||||
EXPECT_THAT(writtenSize, Eq(sizeof(src) + 1));
|
||||
|
||||
//read from buffer
|
||||
Reader br1{InputAdapter{buf.begin(), writtenSize}};
|
||||
Reader br1{buf.begin(), writtenSize};
|
||||
AdapterBitPackingReader bpr1{br1};
|
||||
int16_t dst[DATA_SIZE]{};
|
||||
uint8_t tmp{};
|
||||
@@ -394,7 +396,7 @@ TEST(DataBitsAndBytesOperations, RegressionTestReadBytesAfterReadBitsWithLotsOfZ
|
||||
bpw.flush();
|
||||
|
||||
//read from buffer
|
||||
Reader br{InputAdapter{buf.begin(), bpw.writtenBytesCount()}};
|
||||
Reader br{buf.begin(), bpw.writtenBytesCount()};
|
||||
AdapterBitPackingReader bpr{br};
|
||||
uint8_t tmp{};
|
||||
bpr.readBits(tmp, 2);
|
||||
|
||||
@@ -1,180 +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 <list>
|
||||
#include <bitset>
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
struct IntegralTypes {
|
||||
int64_t a;
|
||||
uint32_t b;
|
||||
int16_t c;
|
||||
uint8_t d;
|
||||
int8_t e;
|
||||
int8_t f[2];
|
||||
};
|
||||
|
||||
TEST(DataReading, WhenReadingMoreThanAvailableThenEmptyBufferError) {
|
||||
//setup data
|
||||
uint8_t a = 111;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
Writer bw{buf};
|
||||
|
||||
bw.writeBytes<1>(a);
|
||||
bw.writeBytes<1>(a);
|
||||
bw.writeBytes<1>(a);
|
||||
bw.flush();
|
||||
//read from buffer
|
||||
Reader br{InputAdapter{buf.begin(), bw.writtenBytesCount()}};
|
||||
int32_t c;
|
||||
br.readBytes<4>(c);
|
||||
EXPECT_THAT(br.error(), Eq(bitsery::ReaderError::DataOverflow));
|
||||
}
|
||||
|
||||
TEST(DataReading, WhenErrorOccursThenAllOtherOperationsFailsForSameError) {
|
||||
//setup data
|
||||
uint8_t a = 111;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
Writer bw{buf};
|
||||
|
||||
bw.writeBytes<1>(a);
|
||||
bw.writeBytes<1>(a);
|
||||
bw.writeBytes<1>(a);
|
||||
bw.flush();
|
||||
//read from buffer
|
||||
Reader br{InputAdapter{buf.begin(), bw.writtenBytesCount()}};
|
||||
int32_t c;
|
||||
br.readBytes<4>(c);
|
||||
EXPECT_THAT(br.error(), Eq(bitsery::ReaderError::DataOverflow));
|
||||
br.readBytes<1>(a);
|
||||
EXPECT_THAT(br.error(), Eq(bitsery::ReaderError::DataOverflow));
|
||||
}
|
||||
|
||||
|
||||
TEST(DataReading, ReadIsCompletedSuccessfullyWhenAllBytesAreReadWithoutErrors) {
|
||||
//setup data
|
||||
IntegralTypes data;
|
||||
data.b = 94545646;
|
||||
data.c = -8778;
|
||||
data.d = 200;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
Writer bw{buf};
|
||||
|
||||
bw.writeBytes<4>(data.b);
|
||||
bw.writeBytes<2>(data.c);
|
||||
bw.writeBytes<1>(data.d);
|
||||
bw.flush();
|
||||
//read from buffer
|
||||
Reader br{InputAdapter{buf.begin(), bw.writtenBytesCount()}};
|
||||
IntegralTypes res;
|
||||
br.readBytes<4>(res.b);
|
||||
EXPECT_THAT(br.error(), Eq(bitsery::ReaderError::NoError));
|
||||
br.readBytes<2>(res.c);
|
||||
EXPECT_THAT(br.error(), Eq(bitsery::ReaderError::NoError));
|
||||
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(false));
|
||||
br.readBytes<1>(res.d);
|
||||
EXPECT_THAT(br.error(), Eq(bitsery::ReaderError::NoError));
|
||||
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(true));
|
||||
br.readBytes<1>(res.d);
|
||||
EXPECT_THAT(br.error(), Eq(bitsery::ReaderError::DataOverflow));
|
||||
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(false));
|
||||
|
||||
Reader br1{InputAdapter{buf.begin(), bw.writtenBytesCount()}};
|
||||
br1.readBytes<4>(res.b);
|
||||
EXPECT_THAT(br1.error(), Eq(bitsery::ReaderError::NoError));
|
||||
br1.readBytes<2>(res.c);
|
||||
EXPECT_THAT(br1.error(), Eq(bitsery::ReaderError::NoError));
|
||||
EXPECT_THAT(br1.isCompletedSuccessfully(), Eq(false));
|
||||
br1.readBytes<2>(res.c);
|
||||
EXPECT_THAT(br1.error(), Eq(bitsery::ReaderError::DataOverflow));
|
||||
EXPECT_THAT(br1.isCompletedSuccessfully(), Eq(false));
|
||||
br1.readBytes<1>(res.d);
|
||||
EXPECT_THAT(br1.error(), Eq(bitsery::ReaderError::DataOverflow));
|
||||
EXPECT_THAT(br1.isCompletedSuccessfully(), Eq(false));
|
||||
}
|
||||
|
||||
TEST(DataReading, WhenReaderHasErrorsAllOperationsReadsReturnZero) {
|
||||
//setup data
|
||||
uint8_t a = 111;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
Writer bw{buf};
|
||||
|
||||
bw.writeBytes<1>(a);
|
||||
bw.writeBytes<1>(a);
|
||||
bw.writeBytes<1>(a);
|
||||
bw.flush();
|
||||
//read from buffer
|
||||
Reader br{InputAdapter{buf.begin(), bw.writtenBytesCount()}};
|
||||
bitsery::AdapterReaderBitPackingWrapper<Reader> bpr{br};
|
||||
int32_t c;
|
||||
bpr.readBytes<4>(c);
|
||||
EXPECT_THAT(br.error(), Eq(bitsery::ReaderError::DataOverflow));
|
||||
|
||||
int16_t r1= {-645};
|
||||
uint32_t r2[2] = {54898,87854};
|
||||
uint8_t r3 = 0xFF;
|
||||
|
||||
bpr.readBytes<2>(r1);
|
||||
bpr.readBuffer<4>(r2, 2);
|
||||
bpr.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));
|
||||
}
|
||||
|
||||
TEST(DataReading, ConstBufferAllAdapters) {
|
||||
//create and write to buffer
|
||||
uint16_t data = 7549;
|
||||
Buffer bufWrite{};
|
||||
Writer bw{bufWrite};
|
||||
bw.writeBytes<2>(data);
|
||||
bw.flush();
|
||||
const Buffer buf{bufWrite};
|
||||
|
||||
//read from buffer
|
||||
using Adapter1 = bitsery::InputBufferAdapter<const Buffer>;
|
||||
using Adapter2 = bitsery::UnsafeInputBufferAdapter<const Buffer>;
|
||||
|
||||
bitsery::AdapterReader<Adapter1, bitsery::DefaultConfig> r1{Adapter1{buf.begin(), buf.end()}};
|
||||
bitsery::AdapterReader<Adapter2, bitsery::DefaultConfig> r2{Adapter2{buf.begin(), buf.end()}};
|
||||
|
||||
uint16_t res1{};
|
||||
r1.readBytes<2>(res1);
|
||||
|
||||
uint16_t res2{};
|
||||
r2.readBytes<2>(res2);
|
||||
EXPECT_THAT(res1, Eq(data));
|
||||
EXPECT_THAT(res2, Eq(data));
|
||||
}
|
||||
@@ -1,137 +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 <bitsery/traits/string.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
|
||||
using testing::Eq;
|
||||
using SessionsEnabledWriter = bitsery::AdapterWriter<OutputAdapter, SessionsEnabledConfig>;
|
||||
using SessionsEnabledReader = bitsery::AdapterReader<InputAdapter, SessionsEnabledConfig>;
|
||||
|
||||
TEST(DataReadingErrors, WhenContainerOrTextSizeIsMoreThanMaxThenInvalidDataError) {
|
||||
SerializationContext ctx;
|
||||
std::string tmp = "larger text then allowed";
|
||||
ctx.createSerializer().text1b(tmp,100);
|
||||
ctx.createDeserializer().text1b(tmp, 10);
|
||||
EXPECT_THAT(ctx.br->error(), Eq(bitsery::ReaderError::InvalidData));
|
||||
}
|
||||
|
||||
TEST(DataReadingErrors, WhenReadingBoolByteReadsMoreThanOneThenInvalidBufferDataErrorAndResultIsFalse) {
|
||||
SerializationContext ctx;
|
||||
auto& ser = ctx.createSerializer();
|
||||
ser.value1b(uint8_t{1});
|
||||
ser.value1b(uint8_t{2});
|
||||
bool res{};
|
||||
auto& des = ctx.createDeserializer();
|
||||
des.boolValue(res);
|
||||
EXPECT_THAT(res, Eq(true));
|
||||
des.boolValue(res);
|
||||
EXPECT_THAT(res, Eq(false));
|
||||
EXPECT_THAT(ctx.br->error(), Eq(bitsery::ReaderError::InvalidData));
|
||||
}
|
||||
|
||||
TEST(DataReadingErrors, WhenReadingAlignHasNonZerosThenInvalidDataError) {
|
||||
Buffer buf{};
|
||||
Writer bw{buf};
|
||||
uint8_t tmp{0xFF};
|
||||
bw.writeBytes<1>(tmp);
|
||||
bw.flush();
|
||||
|
||||
Reader br{InputAdapter{buf.begin(), bw.writtenBytesCount()}};
|
||||
bitsery::AdapterReaderBitPackingWrapper<Reader> bpr{br};
|
||||
|
||||
bpr.readBits(tmp,3);
|
||||
bpr.align();
|
||||
EXPECT_THAT(bpr.error(), Eq(bitsery::ReaderError::InvalidData));
|
||||
}
|
||||
|
||||
TEST(DataReadingErrors, WhenReadingNewSessionInMiddleOfOldDataThenInvalidDataError) {
|
||||
uint8_t tmp{0xFF};
|
||||
Buffer buf{};
|
||||
SessionsEnabledWriter bw{buf};
|
||||
for (auto i = 0; i < 2; ++i) {
|
||||
bw.beginSession();
|
||||
bw.writeBytes<1>(tmp);
|
||||
bw.writeBytes<1>(tmp);
|
||||
bw.endSession();
|
||||
}
|
||||
bw.flush();
|
||||
SessionsEnabledReader br{InputAdapter{buf.begin(), bw.writtenBytesCount()}};
|
||||
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.error(), Eq(bitsery::ReaderError::InvalidData));
|
||||
}
|
||||
|
||||
|
||||
TEST(DataReadingErrors, WhenInitializingSessionsWhenNotEnoughDataThenInvalidData) {
|
||||
uint8_t tmp1{0xFF};
|
||||
Buffer buf1{};
|
||||
SessionsEnabledWriter bw1{buf1};
|
||||
bw1.writeBytes<1>(tmp1);
|
||||
bw1.flush();
|
||||
SessionsEnabledReader br1{InputAdapter{buf1.begin(), bw1.writtenBytesCount()}};
|
||||
br1.beginSession();
|
||||
EXPECT_THAT(br1.error(), Eq(bitsery::ReaderError::InvalidData));
|
||||
|
||||
Buffer buf2{};
|
||||
SessionsEnabledWriter bw2{buf2};
|
||||
uint16_t tmp2{0x8000};
|
||||
bw2.writeBytes<2>(tmp2);
|
||||
bw2.flush();
|
||||
SessionsEnabledReader br2{InputAdapter{buf2.begin(), bw2.writtenBytesCount()}};
|
||||
br2.beginSession();
|
||||
EXPECT_THAT(br2.error(), Eq(bitsery::ReaderError::InvalidData));
|
||||
}
|
||||
|
||||
TEST(DataReadingErrors, WhenInitializingSessionsWhereSessionsDataOffsetIsCorruptedThenInvalidData) {
|
||||
Buffer buf{};
|
||||
SessionsEnabledWriter bw{buf};
|
||||
bw.writeBytes<1>(uint8_t{1});
|
||||
bw.writeBytes<1>(uint8_t{1});
|
||||
bw.writeBytes<4>(uint32_t{10});
|
||||
SessionsEnabledReader br{InputAdapter{buf.begin(), bw.writtenBytesCount()}};
|
||||
br.beginSession();
|
||||
EXPECT_THAT(br.error(), Eq(bitsery::ReaderError::InvalidData));
|
||||
}
|
||||
|
||||
TEST(DataReadingErrors, WhenReadingNewSessionOutsideSessionThenInvalidData) {
|
||||
Buffer buf{};
|
||||
SessionsEnabledWriter bw{buf};
|
||||
bw.beginSession();
|
||||
bw.writeBytes<1>(uint8_t{1});
|
||||
bw.endSession();
|
||||
bw.flush();
|
||||
SessionsEnabledReader br{InputAdapter{buf.begin(), bw.writtenBytesCount()}};
|
||||
br.beginSession();
|
||||
br.endSession();
|
||||
br.beginSession();
|
||||
EXPECT_THAT(br.error(), Eq(bitsery::ReaderError::InvalidData));
|
||||
}
|
||||
@@ -33,7 +33,7 @@ using bitsery::EndiannessType;
|
||||
template <typename BufType>
|
||||
class DataWriting:public testing::Test {
|
||||
public:
|
||||
using TWriter = bitsery::AdapterWriter<bitsery::OutputBufferAdapter<BufType>, bitsery::DefaultConfig>;
|
||||
using TWriter = bitsery::OutputBufferAdapter<BufType>;
|
||||
using TBuffer = BufType;
|
||||
};
|
||||
|
||||
@@ -42,7 +42,7 @@ using FixedContainer = std::array<uint8_t, 100>;
|
||||
|
||||
using ContainerTypes = ::testing::Types<FixedContainer,NonFixedContainer>;
|
||||
|
||||
TYPED_TEST_CASE(DataWriting, ContainerTypes);
|
||||
TYPED_TEST_SUITE(DataWriting, ContainerTypes,);
|
||||
|
||||
static constexpr size_t DATA_SIZE = 14u;
|
||||
|
||||
@@ -74,7 +74,7 @@ TYPED_TEST(DataWriting, WhenWritingBitsThenMustFlushWriter) {
|
||||
using TBuffer = typename TestFixture::TBuffer;
|
||||
TBuffer buf{};
|
||||
TWriter bw{buf};
|
||||
bitsery::AdapterWriterBitPackingWrapper<TWriter> bpw{bw};
|
||||
bitsery::details::OutputAdapterBitPackingWrapper<TWriter> bpw{bw};
|
||||
bpw.writeBits(3u, 2);
|
||||
auto writtenSize1 = bpw.writtenBytesCount();
|
||||
bpw.flush();
|
||||
@@ -88,7 +88,7 @@ TYPED_TEST(DataWriting, WhenDataAlignedThenFlushHasNoEffect) {
|
||||
using TBuffer = typename TestFixture::TBuffer;
|
||||
TBuffer buf{};
|
||||
TWriter bw{buf};
|
||||
bitsery::AdapterWriterBitPackingWrapper<TWriter> bpw{bw};
|
||||
bitsery::details::OutputAdapterBitPackingWrapper<TWriter> bpw{bw};
|
||||
bpw.writeBits(3u, 2);
|
||||
bpw.align();
|
||||
auto writtenSize1 = bpw.writtenBytesCount();
|
||||
@@ -101,7 +101,7 @@ TYPED_TEST(DataWriting, WhenDataAlignedThenFlushHasNoEffect) {
|
||||
|
||||
TEST(DataWritingNonFixedBufferContainer, ContainerIsAlwaysResizedToCapacity) {
|
||||
NonFixedContainer buf{};
|
||||
bitsery::AdapterWriter<bitsery::OutputBufferAdapter<NonFixedContainer>, bitsery::DefaultConfig> bw{buf};
|
||||
bitsery::OutputBufferAdapter<NonFixedContainer> bw{buf};
|
||||
for (auto i = 0; i < 5; ++i) {
|
||||
uint32_t tmp{};
|
||||
bw.writeBytes<4>(tmp);
|
||||
|
||||
@@ -96,6 +96,7 @@ TEST(DeserializeNonDefaultConstructible, ResultContainerShouldShrink) {
|
||||
std::vector<NonDefaultConstructible> res{};
|
||||
res.emplace_back(2);
|
||||
res.emplace_back(3);
|
||||
res.emplace_back(4);
|
||||
|
||||
ctx.createSerializer().container(data, 10);
|
||||
ctx.createDeserializer().container(res, 10);
|
||||
@@ -183,12 +184,12 @@ TEST(DeserializeNonDefaultConstructible, StdMap) {
|
||||
data.emplace(NonDefaultConstructible{4}, NonDefaultConstructible{4});
|
||||
|
||||
auto& ser = ctx.createSerializer();
|
||||
ser.ext(data, bitsery::ext::StdMap{10},[&ser](NonDefaultConstructible& key, NonDefaultConstructible& value) {
|
||||
ser.ext(data, bitsery::ext::StdMap{10},[](decltype(ser)& ser, NonDefaultConstructible& key, NonDefaultConstructible& value) {
|
||||
ser.object(key);
|
||||
ser.object(value);
|
||||
});
|
||||
auto& des = ctx.createDeserializer();
|
||||
des.ext(res, bitsery::ext::StdMap{10},[&des](NonDefaultConstructible& key, NonDefaultConstructible& value) {
|
||||
des.ext(res, bitsery::ext::StdMap{10},[](decltype(des)& des, NonDefaultConstructible& key, NonDefaultConstructible& value) {
|
||||
des.object(key);
|
||||
des.object(value);
|
||||
});
|
||||
@@ -213,7 +214,7 @@ void serialize(S& s, NonPolymorphicPointers& o) {
|
||||
}
|
||||
|
||||
TEST(DeserializeNonDefaultConstructible, NonPolymorphicPointerAndSmartPointer) {
|
||||
using SerContext = BasicSerializationContext<bitsery::DefaultConfig, bitsery::ext::PointerLinkingContext>;
|
||||
using SerContext = BasicSerializationContext<bitsery::ext::PointerLinkingContext>;
|
||||
SerContext ctx{};
|
||||
NonPolymorphicPointers data{};
|
||||
data.pp = new NonDefaultConstructible{3};
|
||||
@@ -223,8 +224,8 @@ TEST(DeserializeNonDefaultConstructible, NonPolymorphicPointerAndSmartPointer) {
|
||||
|
||||
NonPolymorphicPointers res{};
|
||||
bitsery::ext::PointerLinkingContext plctx1{};
|
||||
ctx.createSerializer(&plctx1).object(data);
|
||||
ctx.createDeserializer(&plctx1).object(res);
|
||||
ctx.createSerializer(plctx1).object(data);
|
||||
ctx.createDeserializer(plctx1).object(res);
|
||||
|
||||
EXPECT_THAT(*res.pp, Eq(*data.pp));
|
||||
delete res.pp;
|
||||
@@ -306,7 +307,7 @@ void serialize(S& s, PolymorphicPointers& o) {
|
||||
|
||||
TEST(DeserializeNonDefaultConstructible, PolymorphicPointerAndSmartPointer) {
|
||||
using TContext = std::tuple<bitsery::ext::PointerLinkingContext, bitsery::ext::PolymorphicContext<bitsery::ext::StandardRTTI>>;
|
||||
using SerContext = BasicSerializationContext<bitsery::DefaultConfig, TContext>;
|
||||
using SerContext = BasicSerializationContext<TContext>;
|
||||
SerContext ctx{};
|
||||
PolymorphicPointers data{};
|
||||
data.pp = new PolymorphicNDC1{-4};
|
||||
@@ -315,13 +316,15 @@ TEST(DeserializeNonDefaultConstructible, PolymorphicPointerAndSmartPointer) {
|
||||
data.wp = data.sp;
|
||||
|
||||
PolymorphicPointers res{};
|
||||
|
||||
TContext serCtx{};
|
||||
std::get<1>(serCtx).registerBasesList<typename SerContext::TSerializer>(bitsery::ext::PolymorphicClassesList<PolymorphicNDCBase>{});
|
||||
TContext desCtx{};
|
||||
|
||||
std::get<1>(serCtx).registerBasesList<typename SerContext::TSerializer>(bitsery::ext::PolymorphicClassesList<PolymorphicNDCBase>{});
|
||||
std::get<1>(desCtx).registerBasesList<typename SerContext::TDeserializer>(bitsery::ext::PolymorphicClassesList<PolymorphicNDCBase>{});
|
||||
|
||||
ctx.createSerializer(&serCtx).object(data);
|
||||
ctx.createDeserializer(&desCtx).object(res);
|
||||
ctx.createSerializer(serCtx).object(data);
|
||||
ctx.createDeserializer(desCtx).object(res);
|
||||
auto respp = dynamic_cast<PolymorphicNDC1*>(res.pp);
|
||||
auto resup = dynamic_cast<PolymorphicNDC2*>(res.up.get());
|
||||
auto ressp = dynamic_cast<PolymorphicNDC1*>(res.sp.get());
|
||||
@@ -343,4 +346,6 @@ TEST(DeserializeNonDefaultConstructible, PolymorphicPointerAndSmartPointer) {
|
||||
EXPECT_THAT(*resup, Eq(*dataup));
|
||||
EXPECT_THAT(*ressp, Eq(*datasp));
|
||||
EXPECT_THAT(*reswp, Eq(*datawp));
|
||||
std::get<0>(serCtx).clearSharedState();
|
||||
std::get<0>(desCtx).clearSharedState();
|
||||
}
|
||||
|
||||
48
tests/serialization.cpp
Normal file
48
tests/serialization.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
//MIT License
|
||||
//
|
||||
//Copyright (c) 2019 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;
|
||||
|
||||
TEST(Serialization, AdapterCanBeMovedInAndOut) {
|
||||
Buffer buf{};
|
||||
bitsery::Serializer<Writer> ser1{buf};
|
||||
ser1.object(MyStruct1{1, 2});
|
||||
auto writeAdapter = std::move(ser1).adapter();
|
||||
bitsery::Serializer<Writer> ser2(std::move(writeAdapter));
|
||||
ser2.object(MyStruct1{3, 4});
|
||||
auto writtenBytesCount = ser2.adapter().writtenBytesCount();
|
||||
EXPECT_THAT(writtenBytesCount, Eq(MyStruct1::SIZE + MyStruct1::SIZE));
|
||||
|
||||
MyStruct1 res{};
|
||||
bitsery::Deserializer<Reader> des1{buf.begin(), writtenBytesCount};
|
||||
des1.object(res);
|
||||
EXPECT_THAT(res, Eq(MyStruct1{1, 2}));
|
||||
auto readerAdapter = std::move(des1).adapter();
|
||||
bitsery::Deserializer<Reader> des2(std::move(readerAdapter));
|
||||
des2.object(res);
|
||||
EXPECT_THAT(res, Eq(MyStruct1{3, 4}));
|
||||
EXPECT_TRUE(des2.adapter().isCompletedSuccessfully());
|
||||
}
|
||||
@@ -26,10 +26,6 @@
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
using Serializer = bitsery::BasicSerializer<bitsery::AdapterWriterBitPackingWrapper<Writer>>;
|
||||
|
||||
using Deserializer = bitsery::BasicDeserializer<bitsery::AdapterReaderBitPackingWrapper<Reader>>;
|
||||
|
||||
|
||||
TEST(SerializeBooleans, BoolAsBit) {
|
||||
|
||||
@@ -39,12 +35,12 @@ TEST(SerializeBooleans, BoolAsBit) {
|
||||
bool res1;
|
||||
bool res2;
|
||||
auto& ser = ctx.createSerializer();
|
||||
ser.enableBitPacking([&t1, &t2](Serializer& sbp) {
|
||||
ser.enableBitPacking([&t1, &t2](SerializationContext::TSerializerBPEnabled& sbp) {
|
||||
sbp.boolValue(t1);
|
||||
sbp.boolValue(t2);
|
||||
});
|
||||
auto& des = ctx.createDeserializer();
|
||||
des.enableBitPacking([&res1, &res2](Deserializer& sbp) {
|
||||
des.enableBitPacking([&res1, &res2](SerializationContext::TDeserializerBPEnabled& sbp) {
|
||||
sbp.boolValue(res1);
|
||||
sbp.boolValue(res2);
|
||||
});
|
||||
@@ -71,3 +67,17 @@ TEST(SerializeBooleans, BoolAsByte) {
|
||||
EXPECT_THAT(res2, Eq(t2));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(2));
|
||||
}
|
||||
|
||||
TEST(SerializeBooleans, WhenReadingBoolByteReadsMoreThanOneThenInvalidDataErrorAndResultIsFalse) {
|
||||
SerializationContext ctx;
|
||||
auto& ser = ctx.createSerializer();
|
||||
ser.value1b(uint8_t{1});
|
||||
ser.value1b(uint8_t{2});
|
||||
bool res{};
|
||||
auto& des = ctx.createDeserializer();
|
||||
des.boolValue(res);
|
||||
EXPECT_THAT(res, Eq(true));
|
||||
des.boolValue(res);
|
||||
EXPECT_THAT(res, Eq(false));
|
||||
EXPECT_THAT(ctx.des->adapter().error(), Eq(bitsery::ReaderError::InvalidData));
|
||||
}
|
||||
@@ -64,6 +64,13 @@ std::list<MyStruct2> getFilledContainer<std::list<MyStruct2>>() {
|
||||
};
|
||||
}
|
||||
|
||||
struct EmptyFtor {
|
||||
template <typename S, typename T>
|
||||
void operator() (S& , T& ) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* start testing session
|
||||
*/
|
||||
@@ -89,7 +96,7 @@ using SequenceContainersWithArthmeticTypes = ::testing::Types<
|
||||
std::forward_list<int>,
|
||||
std::deque<unsigned short>>;
|
||||
|
||||
TYPED_TEST_CASE(SerializeContainerDynamicSizeArthmeticTypes, SequenceContainersWithArthmeticTypes);
|
||||
TYPED_TEST_SUITE(SerializeContainerDynamicSizeArthmeticTypes, SequenceContainersWithArthmeticTypes,);
|
||||
|
||||
TYPED_TEST(SerializeContainerDynamicSizeArthmeticTypes, Values) {
|
||||
SerializationContext ctx{};
|
||||
@@ -107,18 +114,18 @@ TYPED_TEST(SerializeContainerDynamicSizeArthmeticTypes, CustomFunctionIncrements
|
||||
using TValue = typename TestFixture::TValue;
|
||||
|
||||
auto& ser = ctx.createSerializer();
|
||||
ser.container(this->src, 1000, [&ser](TValue& v) {
|
||||
ser.container(this->src, 1000, [](decltype(ser)& ser, TValue& v) {
|
||||
ser.template value<sizeof(v)>(v);
|
||||
});
|
||||
auto& des = ctx.createDeserializer();
|
||||
des.container(this->res, 1000, [&des](TValue &v) {
|
||||
des.container(this->res, 1000, [](decltype(des)& des, TValue &v) {
|
||||
des.template value<sizeof(v)>(v);
|
||||
//increment by 1 after reading
|
||||
v++;
|
||||
});
|
||||
//decrement result by 1, before comparing for eq
|
||||
for (auto &v:this->res)
|
||||
v -= 1;
|
||||
v = static_cast<TValue>(v-1);
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(this->getExpectedBufSize(ctx)));
|
||||
EXPECT_THAT(this->res, ContainerEq(this->src));
|
||||
@@ -144,7 +151,7 @@ using SerializeContainerDynamicSizeWithCompositeTypes = ::testing::Types<
|
||||
std::vector<MyStruct1>,
|
||||
std::list<MyStruct2>>;
|
||||
|
||||
TYPED_TEST_CASE(SerializeContainerDynamicSizeCompositeTypes, SerializeContainerDynamicSizeWithCompositeTypes);
|
||||
TYPED_TEST_SUITE(SerializeContainerDynamicSizeCompositeTypes, SerializeContainerDynamicSizeWithCompositeTypes,);
|
||||
|
||||
TYPED_TEST(SerializeContainerDynamicSizeCompositeTypes, DefaultSerializeFunction) {
|
||||
SerializationContext ctx{};
|
||||
@@ -161,9 +168,8 @@ TYPED_TEST(SerializeContainerDynamicSizeCompositeTypes, CustomFunctionThatDoNoth
|
||||
SerializationContext ctx{};
|
||||
using TValue = typename TestFixture::TValue;
|
||||
|
||||
auto emptyFnc = [](TValue &) {};
|
||||
ctx.createSerializer().container(this->src, 1000, emptyFnc);
|
||||
ctx.createDeserializer().container(this->res, 1000, emptyFnc);
|
||||
ctx.createSerializer().container(this->src, 1000, EmptyFtor{});
|
||||
ctx.createDeserializer().container(this->res, 1000, EmptyFtor{});
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(ctx.containerSizeSerializedBytesCount(this->src.size())));
|
||||
}
|
||||
@@ -183,7 +189,7 @@ using StaticContainersWithIntegralTypes = ::testing::Types<
|
||||
std::array<int16_t, 4>,
|
||||
int16_t[4]>;
|
||||
|
||||
TYPED_TEST_CASE(SerializeContainerFixedSizeArithmeticTypes, StaticContainersWithIntegralTypes);
|
||||
TYPED_TEST_SUITE(SerializeContainerFixedSizeArithmeticTypes, StaticContainersWithIntegralTypes,);
|
||||
|
||||
TYPED_TEST(SerializeContainerFixedSizeArithmeticTypes, ArithmeticValues) {
|
||||
using Container = typename TestFixture::TContainer;
|
||||
@@ -208,7 +214,7 @@ class SerializeContainerFixedSizeCompositeTypes : public SerializeContainerFixed
|
||||
using StaticContainersWithCompositeTypes = ::testing::Types<
|
||||
std::array<MyStruct1, 4>, MyStruct1[4]>;
|
||||
|
||||
TYPED_TEST_CASE(SerializeContainerFixedSizeCompositeTypes, StaticContainersWithCompositeTypes);
|
||||
TYPED_TEST_SUITE(SerializeContainerFixedSizeCompositeTypes, StaticContainersWithCompositeTypes,);
|
||||
|
||||
TYPED_TEST(SerializeContainerFixedSizeCompositeTypes, DefaultSerializationFunction) {
|
||||
using Container = typename TestFixture::TContainer;
|
||||
@@ -232,13 +238,13 @@ TYPED_TEST(SerializeContainerFixedSizeCompositeTypes, CustomFunctionThatSerializ
|
||||
|
||||
SerializationContext ctx{};
|
||||
auto& ser = ctx.createSerializer();
|
||||
ser.container(src, [&ser](TValue &v) {
|
||||
ser.container(src, [](decltype(ser)& ser, TValue &v) {
|
||||
char tmp{};
|
||||
ser.object(v);
|
||||
ser.value1b(tmp);
|
||||
});
|
||||
auto& des = ctx.createDeserializer();
|
||||
des.container(res, [&des](TValue &v) {
|
||||
des.container(res, [](decltype(des)& des, TValue &v) {
|
||||
char tmp{};
|
||||
des.object(v);
|
||||
des.value1b(tmp);
|
||||
@@ -253,17 +259,15 @@ class SerializeContainer : public ::testing::TestWithParam<size_t> {
|
||||
|
||||
TEST_P(SerializeContainer, SizeHasVariableLength) {
|
||||
SerializationContext ctx{};
|
||||
auto emptyFnc = [](uint8_t &) {};
|
||||
|
||||
std::vector<uint8_t > src(GetParam());
|
||||
std::vector<uint8_t > res{};
|
||||
ctx.createSerializer().container(src, std::numeric_limits<size_t>::max(), emptyFnc);
|
||||
ctx.createDeserializer().container(res, std::numeric_limits<size_t>::max(), emptyFnc);
|
||||
ctx.createSerializer().container(src, std::numeric_limits<size_t>::max(), EmptyFtor{});
|
||||
ctx.createDeserializer().container(res, std::numeric_limits<size_t>::max(), EmptyFtor{});
|
||||
|
||||
EXPECT_THAT(res.size(), Eq(src.size()));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(ctx.containerSizeSerializedBytesCount(src.size())));
|
||||
}
|
||||
|
||||
//last comma is to suppress error that otherwise can be suppressed by clang/gcc with -Wgnu-zero-variadic-macro-arguments
|
||||
INSTANTIATE_TEST_CASE_P(LargeContainerSize, SerializeContainer, ::testing::Values(0x01, 0x80, 0x4000),);
|
||||
INSTANTIATE_TEST_SUITE_P(LargeContainerSize, SerializeContainer, ::testing::Values(0x01, 0x80, 0x4000));
|
||||
|
||||
|
||||
@@ -26,108 +26,83 @@
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
template <typename ... Args>
|
||||
struct ConfigWithContext: bitsery::DefaultConfig {
|
||||
using InternalContext = std::tuple<Args...>;
|
||||
};
|
||||
|
||||
template <typename Context, typename ... Args>
|
||||
using SerializerConfigWithContext = bitsery::BasicSerializer<
|
||||
bitsery::AdapterWriter<bitsery::OutputBufferAdapter<Buffer>, ConfigWithContext<Args...>>, Context>;
|
||||
|
||||
template <typename Context, typename ... Args>
|
||||
using DeserializerConfigWithContext = bitsery::BasicDeserializer<
|
||||
bitsery::AdapterReader<bitsery::InputBufferAdapter<Buffer>, ConfigWithContext<Args...>>, Context>;
|
||||
|
||||
template <typename Context>
|
||||
using MySerializer = bitsery::BasicSerializer<Writer, Context>;
|
||||
|
||||
template <typename Context>
|
||||
using MyDeserializer = bitsery::BasicDeserializer<Reader, Context>;
|
||||
using bitsery::DefaultConfig;
|
||||
|
||||
using SingleTypeContext = int;
|
||||
using MultipleTypesContext = std::tuple<int, float, char>;
|
||||
|
||||
TEST(SerializationContext, WhenUsingContextThenReturnsUnderlyingPointerOrNull) {
|
||||
Buffer buf{};
|
||||
MySerializer<SingleTypeContext> ser1{buf, nullptr};
|
||||
EXPECT_THAT(ser1.context(), ::testing::IsNull());
|
||||
|
||||
MySerializer<MultipleTypesContext> ser2{buf, nullptr};
|
||||
EXPECT_THAT(ser2.context(), ::testing::IsNull());
|
||||
|
||||
SingleTypeContext sctx{};
|
||||
MyDeserializer<SingleTypeContext> des1{InputAdapter{buf.begin(), 1}, &sctx};
|
||||
EXPECT_THAT(des1.context(), Eq(&sctx));
|
||||
|
||||
MultipleTypesContext mctx{};
|
||||
MyDeserializer<MultipleTypesContext> des2{InputAdapter{buf.begin(), 1}, &mctx};
|
||||
EXPECT_THAT(des2.context(), Eq(&mctx));
|
||||
TEST(SerializationContext, WhenContextIsNotTupleThenReturnThisContext) {
|
||||
SingleTypeContext ctx{54};
|
||||
BasicSerializationContext<SingleTypeContext> c1;
|
||||
auto& ser1 = c1.createSerializer(ctx);
|
||||
|
||||
EXPECT_THAT(ser1.context<SingleTypeContext>(), Eq(ctx));
|
||||
}
|
||||
|
||||
TEST(SerializationContext, WhenContextIsTupleThenContextCastOverloadCastsToIndividualTupleTypes) {
|
||||
Buffer buf{};
|
||||
MySerializer<MultipleTypesContext> ser1{buf, nullptr};
|
||||
EXPECT_THAT(ser1.context<int>(), ::testing::IsNull());
|
||||
EXPECT_THAT(ser1.context<float>(), ::testing::IsNull());
|
||||
EXPECT_THAT(ser1.context<char>(), ::testing::IsNull());
|
||||
TEST(SerializationContext, WhenContextIsTupleThenReturnsTupleElements) {
|
||||
|
||||
MultipleTypesContext ctx{5, 798.654, 'F'};
|
||||
BasicSerializationContext<MultipleTypesContext> c1;
|
||||
auto& ser1 = c1.createSerializer(ctx);
|
||||
|
||||
EXPECT_THAT(ser1.context<int>(), std::get<0>(ctx));
|
||||
EXPECT_THAT(ser1.context<float>(), std::get<1>(ctx));
|
||||
EXPECT_THAT(ser1.context<char>(), std::get<2>(ctx));
|
||||
}
|
||||
|
||||
TEST(SerializationContext, WhenContextIsNotTupleThenContextCastOverloadReturnSameType) {
|
||||
Buffer buf{};
|
||||
SingleTypeContext ctx{};
|
||||
MySerializer<SingleTypeContext> ser1{buf, &ctx};
|
||||
EXPECT_THAT(ser1.context<SingleTypeContext>(), Eq(&ctx));
|
||||
}
|
||||
|
||||
TEST(SerializationContext, SerializerDeserializerCanHaveInternalContextViaConfig) {
|
||||
Buffer buf{};
|
||||
SerializerConfigWithContext<void, float, int> ser{buf};
|
||||
EXPECT_THAT(ser.context<int>(), ::testing::NotNull());
|
||||
EXPECT_THAT(*ser.context<int>(), Eq(0));
|
||||
*ser.context<int>() = 10;
|
||||
EXPECT_THAT(*ser.context<int>(), Eq(10));
|
||||
|
||||
DeserializerConfigWithContext<void, char> des{InputAdapter{buf.begin(), 1}};
|
||||
EXPECT_THAT(des.context<char>(), ::testing::NotNull());
|
||||
EXPECT_THAT(*des.context<char>(), Eq(0));
|
||||
*des.context<char>() = 10;
|
||||
EXPECT_THAT(*des.context<char>(), Eq(10));
|
||||
|
||||
//new instance has new context
|
||||
SerializerConfigWithContext<void, float, int> ser2{buf};
|
||||
EXPECT_THAT(ser2.context<int>(), ::testing::NotNull());
|
||||
EXPECT_THAT(*ser2.context<int>(), Eq(0));
|
||||
}
|
||||
|
||||
TEST(SerializationContext, WhenInternalAndExternalContextIsTheSamePriorityGoesToInternalContext) {
|
||||
Buffer buf{};
|
||||
int externalCtx = 5;
|
||||
|
||||
SerializerConfigWithContext<int, float, int> ser{buf, &externalCtx};
|
||||
EXPECT_THAT(ser.context<int>(), ::testing::NotNull());
|
||||
EXPECT_THAT(*ser.context<int>(), Eq(0));
|
||||
*ser.context<int>() = 2;
|
||||
|
||||
DeserializerConfigWithContext<int, int, char> des{InputAdapter{buf.begin(), 1}, &externalCtx};
|
||||
EXPECT_THAT(des.context<char>(), ::testing::NotNull());
|
||||
EXPECT_THAT(*des.context<char>(), Eq(0));
|
||||
*des.context<int>() = 3;
|
||||
|
||||
EXPECT_THAT(externalCtx, Eq(5));
|
||||
}
|
||||
|
||||
TEST(SerializationContext, ContextIfExistsReturnsNullWhenTypeDoesntExists) {
|
||||
Buffer buf{};
|
||||
std::tuple<double, short> extCtx1{};
|
||||
|
||||
SerializerConfigWithContext<std::tuple<double, short>, float, int> ser{buf, &extCtx1};
|
||||
EXPECT_THAT(ser.contextOrNull<int>(), ::testing::NotNull());
|
||||
TEST(SerializationContext, WhenContextDoesntExistsThenContextOrNullReturnsNull) {
|
||||
SingleTypeContext ctx1= 32;
|
||||
BasicSerializationContext<SingleTypeContext> c1;
|
||||
auto& ser = c1.createSerializer(ctx1);
|
||||
EXPECT_THAT(ser.contextOrNull<char>(), ::testing::IsNull());
|
||||
EXPECT_THAT(ser.contextOrNull<int>(), ::testing::NotNull());
|
||||
*ser.contextOrNull<int>() = 2;
|
||||
EXPECT_THAT(ctx1, Eq(2));
|
||||
|
||||
double extCtx2{};
|
||||
DeserializerConfigWithContext<double, int, char> des{InputAdapter{buf.begin(), 1}, &extCtx2};
|
||||
EXPECT_THAT(des.contextOrNull<double>(), ::testing::NotNull());
|
||||
EXPECT_THAT(des.contextOrNull<float>(), ::testing::IsNull());
|
||||
MultipleTypesContext ctx2{5, 798.654, 'F'};
|
||||
BasicSerializationContext<MultipleTypesContext> c2;
|
||||
auto& des = c2.createDeserializer(ctx2);
|
||||
EXPECT_THAT(des.contextOrNull<double>(), ::testing::IsNull());
|
||||
EXPECT_THAT(des.contextOrNull<int>(), ::testing::NotNull());
|
||||
EXPECT_THAT(*des.contextOrNull<char>(), Eq('F'));
|
||||
EXPECT_THAT(*des.contextOrNull<int>(), Eq(5));
|
||||
}
|
||||
|
||||
struct Base { int value{}; };
|
||||
struct Derived: Base{};
|
||||
|
||||
TEST(SerializationContext, ContextWillTryToConvertIfTypeIsConvertible) {
|
||||
Derived ctx1{};
|
||||
BasicSerializationContext<Derived> c1;
|
||||
auto& ser = c1.createSerializer(ctx1);
|
||||
EXPECT_THAT(ser.contextOrNull<Derived>(), ::testing::NotNull());
|
||||
EXPECT_THAT(ser.contextOrNull<Base>(), ::testing::NotNull());
|
||||
ser.context<Derived>();
|
||||
ser.context<Base>();
|
||||
}
|
||||
|
||||
TEST(SerializationContext, WhenMultipleConvertibleTypesExistsThenFirstMatchIsTaken) {
|
||||
{
|
||||
using CTX1 = std::tuple<Base, int, Derived>;
|
||||
CTX1 ctx1{};
|
||||
std::get<0>(ctx1).value = 1;
|
||||
std::get<2>(ctx1).value = 2;
|
||||
BasicSerializationContext<CTX1> c1;
|
||||
auto& ser = c1.createSerializer(ctx1);
|
||||
EXPECT_THAT(ser.context<Derived>().value, Eq(std::get<2>(ctx1).value));
|
||||
EXPECT_THAT(ser.context<Base>().value, Eq(std::get<0>(ctx1).value));
|
||||
}
|
||||
|
||||
{
|
||||
using CTX2 = std::tuple<float, Derived, Base>;
|
||||
CTX2 ctx2{};
|
||||
std::get<1>(ctx2).value = 1;
|
||||
std::get<2>(ctx2).value = 2;
|
||||
BasicSerializationContext<CTX2> c2;
|
||||
auto& des = c2.createSerializer(ctx2);
|
||||
|
||||
EXPECT_THAT(des.context<Derived>().value, Eq(std::get<1>(ctx2).value));
|
||||
//Base will not be accessable in this case, because Derived is first valid match
|
||||
EXPECT_THAT(des.context<Base>().value, Eq(std::get<1>(ctx2).value));
|
||||
}
|
||||
}
|
||||
@@ -37,35 +37,42 @@ using bitsery::EndiannessType;
|
||||
// helper function, that gets value filled with specified number of bits
|
||||
template <typename TValue>
|
||||
TValue getValue(bool isPositive, size_t significantBits) {
|
||||
TValue v = isPositive ? 0 : -1;
|
||||
TValue v = isPositive ? 0 : static_cast<TValue>(-1);
|
||||
if (significantBits == 0)
|
||||
return v;
|
||||
|
||||
using TUnsigned = typename std::make_unsigned<TValue>::type;
|
||||
TUnsigned mask = {};
|
||||
mask = ~mask; // invert shiftByBits
|
||||
mask = static_cast<TUnsigned>(~mask); // invert shiftByBits
|
||||
auto shiftBy = bitsery::details::BitsSize<TValue>::value - significantBits;
|
||||
mask >>= shiftBy;
|
||||
mask = static_cast<TUnsigned>(mask >> shiftBy);
|
||||
//cast to unsigned when applying mask
|
||||
return (TUnsigned)v ^ mask;
|
||||
return v ^ static_cast<TValue>(mask);
|
||||
}
|
||||
|
||||
// helper function, that serialize and return deserialized value
|
||||
template <typename TSerContext, typename TValue>
|
||||
template <typename TConfig, typename TValue>
|
||||
std::pair<TValue, size_t> serializeAndGetDeserialized(TValue data) {
|
||||
TSerContext ctx;
|
||||
TValue res{};
|
||||
ctx.createSerializer().template ext<sizeof(TValue)>(data, CompactValue{});
|
||||
ctx.createDeserializer().template ext<sizeof(TValue)>(res, CompactValue{});
|
||||
return {res, ctx.getBufferSize()};
|
||||
Buffer buf{};
|
||||
bitsery::Serializer<bitsery::OutputBufferAdapter<Buffer, TConfig>> ser{buf};
|
||||
ser.template ext<sizeof(TValue)>(data, CompactValue{});
|
||||
|
||||
bitsery::Deserializer<bitsery::InputBufferAdapter<Buffer, TConfig>> des{buf.begin(), ser.adapter().writtenBytesCount()};
|
||||
TValue res;
|
||||
des.template ext<sizeof(TValue)>(res, CompactValue{});
|
||||
return {res, ser.adapter().writtenBytesCount()};
|
||||
}
|
||||
|
||||
struct LittleEndianConfig: public bitsery::DefaultConfig {
|
||||
static constexpr EndiannessType NetworkEndianness = EndiannessType::LittleEndian;
|
||||
struct LittleEndianConfig {
|
||||
static constexpr EndiannessType Endianness = EndiannessType::LittleEndian;
|
||||
static constexpr bool CheckDataErrors = true;
|
||||
static constexpr bool CheckAdapterErrors = true;
|
||||
};
|
||||
|
||||
struct BigEndianConfig: public bitsery::DefaultConfig {
|
||||
static constexpr EndiannessType NetworkEndianness = EndiannessType::BigEndian;
|
||||
struct BigEndianConfig {
|
||||
static constexpr EndiannessType Endianness = EndiannessType::BigEndian;
|
||||
static constexpr bool CheckDataErrors = true;
|
||||
static constexpr bool CheckAdapterErrors = true;
|
||||
};
|
||||
|
||||
template <typename TValue, bool isPositiveNr, typename TConfig>
|
||||
@@ -111,7 +118,7 @@ using AllValueSizesTestCases = ::testing::Types<
|
||||
TC<int64_t, false, BigEndianConfig>
|
||||
>;
|
||||
|
||||
TYPED_TEST_CASE(SerializeExtensionCompactValueCorrectness, AllValueSizesTestCases);
|
||||
TYPED_TEST_SUITE(SerializeExtensionCompactValueCorrectness, AllValueSizesTestCases,);
|
||||
|
||||
TYPED_TEST(SerializeExtensionCompactValueCorrectness, TestDifferentSizeValues) {
|
||||
using TCase = typename TestFixture::TestCase;
|
||||
@@ -120,7 +127,7 @@ TYPED_TEST(SerializeExtensionCompactValueCorrectness, TestDifferentSizeValues) {
|
||||
|
||||
for (auto i = 0u; i < bitsery::details::BitsSize<TValue>::value + 1; ++i) {
|
||||
auto data = getValue<TValue>(tc.isPositive, i);
|
||||
auto res = serializeAndGetDeserialized<BasicSerializationContext<typename TCase::Config, void>>(data);
|
||||
auto res = serializeAndGetDeserialized<typename TCase::Config>(data);
|
||||
EXPECT_THAT(res.first, Eq(data));
|
||||
}
|
||||
}
|
||||
@@ -195,14 +202,14 @@ using RequiredBytesTestCases = ::testing::Types<
|
||||
SizeTC<int64_t, false, 63,10>
|
||||
>;
|
||||
|
||||
TYPED_TEST_CASE(SerializeExtensionCompactValueRequiredBytes, RequiredBytesTestCases);
|
||||
TYPED_TEST_SUITE(SerializeExtensionCompactValueRequiredBytes, RequiredBytesTestCases,);
|
||||
|
||||
TYPED_TEST(SerializeExtensionCompactValueRequiredBytes, Test) {
|
||||
using TCase = typename TestFixture::TestCase;
|
||||
using TValue = typename TCase::Value;
|
||||
TCase tc{};
|
||||
TValue data = getValue<TValue>(tc.isPositive, tc.fillBits);
|
||||
auto res = serializeAndGetDeserialized<SerializationContext>(data);
|
||||
auto res = serializeAndGetDeserialized<bitsery::DefaultConfig>(data);
|
||||
EXPECT_THAT(res.first, Eq(data));
|
||||
EXPECT_THAT(res.second, tc.bytesCount);
|
||||
}
|
||||
@@ -219,22 +226,19 @@ TEST(SerializeExtensionCompactValueEnum, TestEnums) {
|
||||
auto d1 = b1En::E;
|
||||
auto d2 = b8En::B;
|
||||
auto d3 = b8En::F;
|
||||
EXPECT_THAT(serializeAndGetDeserialized<SerializationContext>(d1).first, Eq(d1));
|
||||
EXPECT_THAT(serializeAndGetDeserialized<SerializationContext>(d2).first, Eq(d2));
|
||||
EXPECT_THAT(serializeAndGetDeserialized<SerializationContext>(d3).first, Eq(d3));
|
||||
EXPECT_THAT(serializeAndGetDeserialized<bitsery::DefaultConfig>(d1).first, Eq(d1));
|
||||
EXPECT_THAT(serializeAndGetDeserialized<bitsery::DefaultConfig>(d2).first, Eq(d2));
|
||||
EXPECT_THAT(serializeAndGetDeserialized<bitsery::DefaultConfig>(d3).first, Eq(d3));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionCompactValueAsObjectDeserializeOverflow, TestEnums) {
|
||||
SerializationContext ctx;
|
||||
auto data = getValue<uint32_t >(true, 17);
|
||||
uint16_t res{};
|
||||
auto& ser = ctx.createSerializer();
|
||||
ser.ext(data, CompactValueAsObject{});
|
||||
auto& des = ctx.createDeserializer();
|
||||
des.ext(res, CompactValueAsObject{});
|
||||
auto& rd = bitsery::AdapterAccess::getReader(des);
|
||||
ctx.createSerializer().ext(data, CompactValueAsObject{});
|
||||
ctx.createDeserializer().ext(res, CompactValueAsObject{});
|
||||
EXPECT_THAT(data, ::testing::Ne(res));
|
||||
EXPECT_THAT(rd.error(), Eq(bitsery::ReaderError::DataOverflow));
|
||||
EXPECT_THAT(ctx.des->adapter().error(), Eq(bitsery::ReaderError::InvalidData));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@ using namespace testing;
|
||||
|
||||
using bitsery::ext::Entropy;
|
||||
|
||||
using BPSer = bitsery::BasicSerializer<bitsery::AdapterWriterBitPackingWrapper<Writer>>;
|
||||
using BPDes = bitsery::BasicDeserializer<bitsery::AdapterReaderBitPackingWrapper<Reader>>;
|
||||
using BPSer = SerializationContext::TSerializerBPEnabled;
|
||||
using BPDes = SerializationContext::TDeserializerBPEnabled;
|
||||
|
||||
|
||||
TEST(SerializeExtensionEntropy, WhenEntropyEncodedThenOnlyWriteIndexUsingMinRequiredBits) {
|
||||
@@ -128,7 +128,7 @@ TEST(SerializeExtensionEntropy, CustomFunctionNotEntropyEncodedWithNoAlignBefore
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().enableBitPacking([&v, &values, &rangeForValue](BPSer& ser){
|
||||
//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& data) {
|
||||
auto serLambda = [&rangeForValue](BPSer& ser, MyStruct1& data) {
|
||||
ser.ext(data.i1, rangeForValue);
|
||||
ser.ext(data.i2, rangeForValue);
|
||||
};
|
||||
@@ -136,7 +136,7 @@ TEST(SerializeExtensionEntropy, CustomFunctionNotEntropyEncodedWithNoAlignBefore
|
||||
});
|
||||
|
||||
ctx.createDeserializer().enableBitPacking([&res, &values, &rangeForValue](BPDes& des) {
|
||||
auto desLambda = [&des, &rangeForValue](MyStruct1& data) {
|
||||
auto desLambda = [&rangeForValue](BPDes& des, MyStruct1& data) {
|
||||
des.ext(data.i1, rangeForValue);
|
||||
des.ext(data.i2, rangeForValue);
|
||||
};
|
||||
@@ -161,14 +161,14 @@ TEST(SerializeExtensionEntropy, CustomFunctionNotEntropyEncodedWithAlignBeforeDa
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().enableBitPacking([&v, &values, &rangeForValue](BPSer& ser){
|
||||
//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& data) {
|
||||
auto serLambda = [&rangeForValue](BPSer& ser, MyStruct1& data) {
|
||||
ser.ext(data.i1, rangeForValue);
|
||||
ser.ext(data.i2, rangeForValue);
|
||||
};
|
||||
ser.ext(v, Entropy<std::vector<MyStruct1>>(values, true), serLambda);
|
||||
});
|
||||
ctx.createDeserializer().enableBitPacking([&res, &values, &rangeForValue](BPDes& des) {
|
||||
auto desLambda = [&des, &rangeForValue](MyStruct1& data) {
|
||||
auto desLambda = [&rangeForValue](BPDes& des, MyStruct1& data) {
|
||||
des.ext(data.i1, rangeForValue);
|
||||
des.ext(data.i2, rangeForValue);
|
||||
};
|
||||
@@ -176,7 +176,7 @@ TEST(SerializeExtensionEntropy, CustomFunctionNotEntropyEncodedWithAlignBeforeDa
|
||||
});
|
||||
|
||||
EXPECT_THAT(res, Eq(v));
|
||||
auto bitsForIndex = 8; //because aligned
|
||||
auto bitsForIndex = 8u; //because aligned
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq((bitsForIndex + rangeForValue.getRequiredBits() * 2 - 1) / 8 + 1 ));
|
||||
}
|
||||
|
||||
@@ -189,10 +189,10 @@ TEST(SerializeExtensionEntropy, WhenEntropyEncodedThenCustomFunctionNotInvoked)
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().enableBitPacking([&v, &values](BPSer& ser) {
|
||||
ser.ext(v, Entropy<std::list<MyStruct1>>{values}, [](MyStruct1& ) {});
|
||||
ser.ext(v, Entropy<std::list<MyStruct1>>{values}, [](BPSer& ,MyStruct1& ) {});
|
||||
});
|
||||
ctx.createDeserializer().enableBitPacking([&res, &values](BPDes& des) {
|
||||
des.ext(res, Entropy<std::list<MyStruct1>>{values}, []( MyStruct1& ) {});
|
||||
des.ext(res, Entropy<std::list<MyStruct1>>{values}, [](BPDes& , MyStruct1& ) {});
|
||||
});
|
||||
|
||||
EXPECT_THAT(res, Eq(v));
|
||||
|
||||
@@ -32,395 +32,207 @@ struct DataV1 {
|
||||
int32_t v1;
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, DataV1& o) {
|
||||
s.value4b(o.v1);
|
||||
}
|
||||
|
||||
struct DataV2 {
|
||||
int32_t v1;
|
||||
int32_t v2;
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, DataV2& o) {
|
||||
s.value4b(o.v1);
|
||||
s.value4b(o.v2);
|
||||
}
|
||||
|
||||
struct DataV3 {
|
||||
int32_t v1;
|
||||
int32_t v2;
|
||||
int32_t v3;
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value4b(v1);
|
||||
s.value4b(v2);
|
||||
s.value4b(v3);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
TEST(SerializeExtensionGrowable, WriteSessionsDataAtBufferEndAfterFlush) {
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
TEST(SerializeExtensionGrowable, SessionsLengthIsStoredWith4BytesBeforeSessionDataStarts) {
|
||||
SerializationContext ctx;
|
||||
auto& ser = ctx.createSerializer();
|
||||
//session cannot be empty
|
||||
ser.ext(int8_t{}, Growable{}, [&ser] (int8_t& v) {
|
||||
ser.value2b(int16_t{1});
|
||||
ser.ext(int8_t{2}, Growable{}, [] (decltype(ser)& ser, int8_t& v) {
|
||||
ser.value1b(v);
|
||||
});
|
||||
ser.value1b(int8_t{3});
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(1u));
|
||||
ctx.bw->flush();
|
||||
EXPECT_THAT(ctx.getBufferSize(), Gt(1u));
|
||||
}
|
||||
|
||||
|
||||
TEST(SerializeExtensionGrowable, SessionDataConsistOfSessionsEndPosAnd4BytesSessionsDataOffset) {
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> 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(1+4 + 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
|
||||
uint32_t sessionsOffset{};//bufferEnd - sessionsOffset = dataEnd
|
||||
br.readBytes<4>(sessionsOffset);
|
||||
EXPECT_THAT(sessionsOffset, Eq(1+4));//1byte for session info, 4 bytes for session offset variable
|
||||
auto writtenSize = ctx.bw->writtenBytesCount();
|
||||
auto dSize = writtenSize - sessionsOffset;
|
||||
EXPECT_THAT(dSize, Eq(DATA_SIZE));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, WhenNestedSessionsThenStoreEachDepthAndSize) {
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> 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));
|
||||
auto& des = ctx.createDeserializer();
|
||||
uint8_t res1b{};
|
||||
uint16_t res2b{};
|
||||
uint32_t res4b{};
|
||||
des.value2b(res2b);
|
||||
EXPECT_THAT(res2b, Eq(1));
|
||||
des.value4b(res4b);
|
||||
EXPECT_THAT(res4b, Eq(1+4));//size + 4bytes
|
||||
des.value1b(res1b);
|
||||
EXPECT_THAT(res1b, Eq(2));
|
||||
des.value1b(res1b);
|
||||
EXPECT_THAT(res1b, Eq(3));
|
||||
EXPECT_THAT(ctx.ser->adapter().writtenBytesCount(), Eq(8));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleSessionsReadSameVersionData) {
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
SerializationContext ctx;
|
||||
DataV2 data{8454,987451};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
auto& ser = ctx.createSerializer();
|
||||
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
bw.beginSession();
|
||||
bw.writeBytes<4>(data.v1);
|
||||
bw.writeBytes<4>(data.v2);
|
||||
bw.endSession();
|
||||
ser.ext(data, Growable{});
|
||||
}
|
||||
//create more sessions that can fit in 2 bytes
|
||||
ctx.createDeserializer();//to flush data and create buffer reader
|
||||
ctx.createDeserializer();
|
||||
DataV2 res{};
|
||||
auto& br = (*ctx.br);
|
||||
auto& des = ctx.createDeserializer();
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
br.beginSession();
|
||||
br.readBytes<4>(res.v1);
|
||||
br.readBytes<4>(res.v2);
|
||||
br.endSession();
|
||||
des.ext(res, Growable{});
|
||||
EXPECT_THAT(res.v1, Eq(data.v1));
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
}
|
||||
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
|
||||
EXPECT_THAT(ctx.des->adapter().isCompletedSuccessfully(), Eq(true));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleSessionsReadNewerVersionData) {
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
DataV3 data{8454,987451,54};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
SerializationContext ctx;
|
||||
DataV3 data{8454,987451, 45612};
|
||||
auto& ser = ctx.createSerializer();
|
||||
|
||||
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();
|
||||
ser.ext(data, Growable{});
|
||||
}
|
||||
//create more sessions that can fit in 2 bytes
|
||||
ctx.createDeserializer();//to flush data and create buffer reader
|
||||
ctx.createDeserializer();
|
||||
DataV2 res{};
|
||||
auto& br = (*ctx.br);
|
||||
auto& des = ctx.createDeserializer();
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
br.beginSession();
|
||||
br.readBytes<4>(res.v1);
|
||||
br.readBytes<4>(res.v2);
|
||||
br.endSession();
|
||||
des.ext(res, Growable{});
|
||||
EXPECT_THAT(res.v1, Eq(data.v1));
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
}
|
||||
EXPECT_THAT(br.isCompletedSuccessfully(), Eq(true));
|
||||
EXPECT_THAT(ctx.des->adapter().isCompletedSuccessfully(), Eq(true));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleSessionsReadOlderVersionData) {
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
SerializationContext ctx;
|
||||
DataV2 data{8454,987451};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
auto& ser = ctx.createSerializer();
|
||||
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
bw.beginSession();
|
||||
bw.writeBytes<4>(data.v1);
|
||||
bw.writeBytes<4>(data.v2);
|
||||
bw.endSession();
|
||||
ser.ext(data, Growable{});
|
||||
}
|
||||
//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);
|
||||
ctx.createDeserializer();
|
||||
DataV3 res{};
|
||||
auto& des = ctx.createDeserializer();
|
||||
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();
|
||||
des.ext(res, Growable{});
|
||||
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));
|
||||
EXPECT_THAT(ctx.des->adapter().isCompletedSuccessfully(), Eq(true));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadSameVersionData) {
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
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();
|
||||
auto& ser = ctx.createSerializer();
|
||||
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
ser.ext(data, Growable{}, [](decltype(ser)& ser, DataV2& o) {
|
||||
ser.value4b(o.v1);
|
||||
ser.value4b(o.v2);
|
||||
ser.ext(o, Growable{});
|
||||
});
|
||||
}
|
||||
//create more sessions that can fit in 2 bytes
|
||||
ctx.createDeserializer();//to flush data and create buffer reader
|
||||
ctx.createDeserializer();
|
||||
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);
|
||||
auto& des = ctx.createDeserializer();
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
des.ext(res, Growable{}, [&res, &data](decltype(des)& des, DataV2& o) {
|
||||
des.value4b(o.v1);
|
||||
des.value4b(o.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();
|
||||
des.ext(o, Growable{});
|
||||
EXPECT_THAT(res.v1, Eq(data.v1));
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
});
|
||||
}
|
||||
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
|
||||
EXPECT_THAT(ctx.des->adapter().isCompletedSuccessfully(), Eq(true));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadNewerVersionData) {
|
||||
SerializationContext ctx;
|
||||
DataV3 data{8454,987451, 54124};
|
||||
auto& ser = ctx.createSerializer();
|
||||
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
ser.ext(data, Growable{}, [](decltype(ser)& ser, DataV3& o) {
|
||||
ser.value4b(o.v1);
|
||||
ser.value4b(o.v2);
|
||||
ser.ext(o, Growable{});
|
||||
//new fields can only be added at the end
|
||||
ser.value4b(o.v3);
|
||||
});
|
||||
}
|
||||
ctx.createDeserializer();
|
||||
DataV2 res{};
|
||||
auto& des = ctx.createDeserializer();
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
des.ext(res, Growable{}, [&res, &data](decltype(des)& des, DataV2& o) {
|
||||
des.value4b(o.v1);
|
||||
des.value4b(o.v2);
|
||||
EXPECT_THAT(res.v1, Eq(data.v1));
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
des.ext(o, Growable{});
|
||||
EXPECT_THAT(res.v1, Eq(data.v1));
|
||||
EXPECT_THAT(res.v2, Eq(data.v2));
|
||||
});
|
||||
}
|
||||
EXPECT_THAT(ctx.des->adapter().isCompletedSuccessfully(), Eq(true));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadOlderVersionData) {
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
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);
|
||||
}
|
||||
auto& ser = ctx.createSerializer();
|
||||
|
||||
//create more sessions that can fit in 2 bytes
|
||||
ctx.createDeserializer();//to flush data and create buffer reader
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
ser.ext(data, Growable{}, [](decltype(ser)& ser, DataV2& o) {
|
||||
ser.value4b(o.v1);
|
||||
ser.value4b(o.v2);
|
||||
ser.ext(o, Growable{});
|
||||
});
|
||||
}
|
||||
ctx.createDeserializer();
|
||||
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) {
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> 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);
|
||||
auto& des = ctx.createDeserializer();
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
des.ext(res, Growable{}, [&res, &data](decltype(des)& des, DataV3& o) {
|
||||
des.value4b(o.v1);
|
||||
des.value4b(o.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) {
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> 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);
|
||||
des.ext(o, Growable{});
|
||||
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(res.v3, Eq(0));
|
||||
//new fields can only be added at the end
|
||||
des.value4b(o.v3);
|
||||
EXPECT_THAT(res.v3, Eq(0));
|
||||
});
|
||||
}
|
||||
EXPECT_THAT(ctx.br->isCompletedSuccessfully(), Eq(true));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, SessionsStartsAtEndOfSerialization) {
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> 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));
|
||||
EXPECT_THAT(ctx.des->adapter().isCompletedSuccessfully(), Eq(true));
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user