mirror of
https://github.com/fraillt/bitsery.git
synced 2026-06-18 13:19:11 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d22b7c1527 | ||
|
|
8d6ffc9873 | ||
|
|
0e76e0608c | ||
|
|
501d60f67d | ||
|
|
04afd16fbd | ||
|
|
9621db1cd7 | ||
|
|
c555088aa3 | ||
|
|
c9619e3e3d | ||
|
|
01d56e00b8 | ||
|
|
04526ff0f4 | ||
|
|
b7d159bbfc | ||
|
|
f85dff7415 | ||
|
|
f35ae3f4dc | ||
|
|
ff40222124 | ||
|
|
a1785f3e15 | ||
|
|
105aa5f9e5 | ||
|
|
1822796f2e | ||
|
|
57dd028b7a | ||
|
|
03f2c3c8b5 | ||
|
|
aca3139600 | ||
|
|
c1ae593fb4 | ||
|
|
ddca8e4ad0 | ||
|
|
1fe2b398fc | ||
|
|
574ec69cca | ||
|
|
8e94596a6f | ||
|
|
fac2c8a7ce | ||
|
|
a6dad0885f | ||
|
|
65f90637df | ||
|
|
b10f86da00 | ||
|
|
6c3e1aee43 | ||
|
|
e5f8d5742f | ||
|
|
a2ecf8d7b0 | ||
|
|
670130397b | ||
|
|
4a0b3cae98 | ||
|
|
b3b32ab393 | ||
|
|
6ebdb9915b | ||
|
|
2e62bd08e3 | ||
|
|
54f69a5eea | ||
|
|
275c4138ee | ||
|
|
1ca45aab79 | ||
|
|
952635ff70 | ||
|
|
507b5ae01d |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
.idea/
|
||||
.vs/
|
||||
build/
|
||||
cmake-build-debug/
|
||||
cmake-build-*
|
||||
CTestConfig.cmake
|
||||
Testing/
|
||||
|
||||
77
.travis.yml
77
.travis.yml
@@ -1,23 +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
|
||||
install:
|
||||
- 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
|
||||
- cmake -DBUILD_SHARED_LIBS=ON .
|
||||
- make
|
||||
- sudo make install
|
||||
- cd ..
|
||||
|
||||
before_script:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake ..
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake -DBITSERY_BUILD_TESTS=ON -DCMAKE_CXX_STANDARD=$CXXSTD ..
|
||||
|
||||
script: make && make test
|
||||
|
||||
script:
|
||||
- make
|
||||
- cd tests
|
||||
- ctest
|
||||
206
CHANGELOG.md
206
CHANGELOG.md
@@ -1,3 +1,201 @@
|
||||
# [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
|
||||
* new extensions **StdTuple** and **StdVariant** for `std::tuple` and `std::variant`. These are the first extensions that requires C++17, or higher, standard enabled.
|
||||
Although `std::tuple` is C++11 type, but from usage perspective it has exactly the same requirements as `std::variant` and relies heavily on having class template argument deduction guides to make it convenient to use.
|
||||
You can easily use `std::tuple` without any extension at all, so the main motivation was to create convenient interface for **StdVariant** and use the same interface for **StdTuple** as well.
|
||||
* instead of providing custom lambda to overload each type in tuple or variant, there was added several helper callable objects.
|
||||
**OverloadValue** wrapper around `s.value<N>(o)`, **OverloadExtValue** wrapper around `s.ext<N>(o, Ext{})` and **OverloadExtObject** wrapper around `s.ext(o, Ext{})`.
|
||||
* new extensions **StdDuration** and **StdTimePoint** for `std::chrono::duration` and `std::chrono::time_point`.
|
||||
|
||||
### Improvements
|
||||
tests now uses `gtest_discover_tests` function, to automatically discover tests, which requires CMake 3.10.
|
||||
|
||||
# [4.5.1](https://github.com/fraillt/bitsery/compare/v4.5.0...v4.5.1) (2019-01-16)
|
||||
|
||||
### Improvements
|
||||
* template specializations, where possible, was changed to avoid using variadics, some Visual Studio compilers has [issues](https://developercommunity.visualstudio.com/content/problem/3437/error-with-c11-variadics.html) with variadic templates.
|
||||
* reduced compile warnings for VisualStudio:
|
||||
* added explicit casts
|
||||
* renamed `struct` to `class` where class is used as friend. e.g. `friend class bitsery::Access`, because it is more conventional usage.
|
||||
|
||||
# [4.5.0](https://github.com/fraillt/bitsery/compare/v4.4.0...v4.5.0) (2019-01-10)
|
||||
|
||||
### Features
|
||||
* ability to create non default constructible objects, by defining private default constructor and making `friend class bitsery::Access;` to access it.
|
||||
It is not necessary to enforce class invariant immediately, because internal object representation will be overriden anyway.
|
||||
|
||||
### Improvements
|
||||
* `StdSmartPtr` supports `std::unique_ptr` with custom deleter.
|
||||
* `*InputBufferAdapter`(all) can also accept const buffer;
|
||||
|
||||
### Bug fixes
|
||||
* fixed deserialization in `bitsery/ext/std_map{set}` when target container is not empty.
|
||||
* added missing template parameters for specializations on `std` containers in multiple files in `bitsery/ext/*`.
|
||||
|
||||
# [4.4.0](https://github.com/fraillt/bitsery/compare/v4.3.0...v4.4.0) (2019-01-08)
|
||||
|
||||
### Features
|
||||
* new extensions **CompactValue** and **CompactValueAsObject**, stores integral values in less space if possible. This is useful when you're working with mostly small values, that in rare cases can be large.
|
||||
E.g. `int64_t money = 8000;` will only use 2 bytes, instead of 8. **CompactValueAsObject** allows to use `ext()` overload, without specifying size of underlying type and sets BUFFER_OVERFLOW error if value doesn't fit in underlying type during deserialization.
|
||||
|
||||
### Improvements
|
||||
* improved **PolymorphicContext**, allows to extend already registered hierarchy in one translation unit, using different type other than `PolymorphicBaseClass` to avoid symbol collision between translation units or libraries.
|
||||
`registerBasesList` was modified, so that it could accept user defined type (instead of `PolymorphicBaseClass`) that is used to declare hierarchy, by default it is `PolymorphicBaseClass`.
|
||||
This introduced breaking change, for those who used this syntax (`registerBasesList<MySerializer, Shape>({})`) during registration.
|
||||
It is encouraged to define helper type, that could be used for registering hierarchy for serialization and deserialization [example](examples/smart_pointers_with_polymorphism.cpp).
|
||||
`This is only relevant then you want to use **PolymorphicContext** between different translation units or libraries`.
|
||||
```cpp
|
||||
//libA
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
template<>
|
||||
struct PolymorphicBaseClass<Shape> : PolymorphicDerivedClasses<Circle, Rectangle> {};
|
||||
}
|
||||
}
|
||||
using MyPolymorphicClassesForRegistering = bitsery::ext::PolymorphicClassesList<Shape>;
|
||||
...
|
||||
ctx.registerBasesList<MySerializer>(MyPolymorphicClassesForRegistering{}).
|
||||
|
||||
//otherLib
|
||||
struct MySquare: Shape {...}
|
||||
//now it must define different type (exactly the same as PolymorphicBaseClass) to declare hierarchy
|
||||
template<typename TBase>
|
||||
struct MyHierarchy {
|
||||
using Childs = PolymorphicClassesList<>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct MyHierarchy<Shape>: bitsery::ext::bitsery::ext::PolymorphicClassesList<MySquare> {};
|
||||
...
|
||||
//notice that we pass MyHierarchy as second argument
|
||||
ctx.registerBasesList<MySerializer, MyHierarchy>(MyPolymorphicClassesForRegistering{}).
|
||||
```
|
||||
* **PolymorphicContext** also get optional method `registerSingleBaseBranch`, that allows manually register hierarchies, this might be more convenient when using you need to register in different translation units (or libraries), but it is error-prone.
|
||||
|
||||
# [4.3.0](https://github.com/fraillt/bitsery/compare/v4.2.1...v4.3.0) (2018-08-23)
|
||||
|
||||
### Features
|
||||
|
||||
* added runtime polymorphism support for pointer like types (raw and smart pointers).
|
||||
In order to enable polymorphism new **PolymorphicContext** was created. It provides capability to register classes with serializer/deserializer.
|
||||
* runtime polymorphism can be customized, by replacing **StandardRTTI** from <bitsery/ext/utils/rtti_utils.h> header.
|
||||
* added smart pointers support for std::unique_ptr, std::shared_ptr and std::weak_ptr via **StdSmartPtr** extension.
|
||||
* new **UnsafeInputBufferAdapter** doesn't check for buffer size on deserialization, on some compilers can improve deserialization performance up to ~40%.
|
||||
|
||||
### Improvements
|
||||
* creatly improved interface for extending/implementing support for pointer like types. Now all pointer like types extends from **PointerObjectExtensionBase** and implements/configures required details.
|
||||
* reimplemented **PointerOwner**, **PointerObserver**, **ReferencedByPointer**.
|
||||
* reimplemented **PointerLinkingContext** to properly support shared objects and runtime polymorphism, pointer ownership for shared objects now has two states: SharedOwner e.g. std::shared_ptr and SharedObserver std::weak_ptr.
|
||||
|
||||
### Other notes
|
||||
There is one *minor?* issue/limitation for pointer like types that uses virtual inheritance. When several pointers points to same object through different static type. it will not work correctly e.g.:
|
||||
```cpp
|
||||
struct Derived: virtual Base {...};
|
||||
struct MyData {
|
||||
std::shared_ptr<Derived> sptr;
|
||||
std::weak_ptr<Base> wptddr;
|
||||
}
|
||||
```
|
||||
In this example wptr and sptr have different static type, and *Derived* is virtually inherited from *Base*, so I get different pointer address for different types.
|
||||
|
||||
# [4.2.1](https://github.com/fraillt/bitsery/compare/v4.2.0...v4.2.1) (2018-03-09)
|
||||
|
||||
### Improvements
|
||||
* changed CMake structure, to follow **Modern CMake** principles.
|
||||
* bitsery now has *install* target and **find_package(Bitsery)** exports **Bitsery::bitsery** target.
|
||||
* *GTest* no longer downloads as external application, but tries to find via *find_package*.
|
||||
* removed *ext* folder, and instead added *scripts* folder that contains few helper scripts for development, currently tested on Ubuntu.
|
||||
* fixed/added few tests cases.
|
||||
|
||||
### Other notes
|
||||
* some work was done on polymorphism support, but current solution, although working, but has many design and performance issues, and interfaces for extensibility might change drastically.
|
||||
|
||||
# [4.2.0](https://github.com/fraillt/bitsery/compare/v4.1.0...v4.2.0) (2017-11-12)
|
||||
|
||||
### Features
|
||||
@@ -94,7 +292,7 @@ Be careful when using deserializing untrusted data and make sure to enforce fund
|
||||
### Features
|
||||
|
||||
* refactored interface, now works with C++11 compiler.
|
||||
* new new extension **Growable**, that allows to have forward/backward compatability within this functions serialization flow. It only allows to append new data at the end of to existing flow without breaking old consumers.
|
||||
* new extension **Growable**, that allows to have forward/backward compatability within this functions serialization flow. It only allows to append new data at the end of to existing flow without breaking old consumers.
|
||||
* old consumer: correctly read old interfce and ignore new data.
|
||||
* new consumer: get defaults (zero values) for new fields, when reading old data.
|
||||
* added new extension for associative *map* containers **ContainerMap**.
|
||||
@@ -146,9 +344,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.
|
||||
@@ -156,8 +351,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,18 +1,57 @@
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
project(bitsery
|
||||
LANGUAGES CXX
|
||||
VERSION 5.0.1)
|
||||
|
||||
project(bitsery)
|
||||
#======== build options ===================================
|
||||
option(BITSERY_BUILD_EXAMPLES "Build examples" OFF)
|
||||
option(BITSERY_BUILD_TESTS "Build tests" OFF)
|
||||
|
||||
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
|
||||
set(COMPILE_FLAGS "")
|
||||
#============= setup target ======================
|
||||
add_library(bitsery INTERFACE)
|
||||
# create alias, so that user could always write target_link_libraries(... Bitsery::bitsery)
|
||||
# despite of bitsery target is imported or not
|
||||
add_library(Bitsery::bitsery ALIAS bitsery)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
target_include_directories(bitsery INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>)
|
||||
target_compile_features(bitsery INTERFACE
|
||||
cxx_auto_type
|
||||
cxx_constexpr
|
||||
cxx_lambdas
|
||||
cxx_nullptr
|
||||
cxx_variadic_templates)
|
||||
|
||||
#=============== setup installation =======================
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/BitseryConfigVersion.cmake
|
||||
COMPATIBILITY SameMajorVersion)
|
||||
install(TARGETS bitsery
|
||||
EXPORT bitseryTargets
|
||||
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(EXPORT bitseryTargets
|
||||
FILE "BitseryConfig.cmake"
|
||||
NAMESPACE Bitsery::
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/bitsery)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/BitseryConfigVersion.cmake
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/bitsery)
|
||||
install(DIRECTORY include/bitsery
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
|
||||
#================ handle sub-projects =====================
|
||||
|
||||
if (BITSERY_BUILD_EXAMPLES)
|
||||
message("build bitsery examples")
|
||||
add_subdirectory(examples)
|
||||
else()
|
||||
set(COMPILE_FLAGS "-Wall -Wextra -Wno-missing-braces -Wpedantic -Weffc++")
|
||||
message("skip bitsery examples")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMPILE_FLAGS}")
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_subdirectory(examples)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
if (BITSERY_BUILD_TESTS)
|
||||
message("build bitsery tests")
|
||||
add_subdirectory(tests)
|
||||
else()
|
||||
message("skip bitsery tests")
|
||||
endif()
|
||||
|
||||
@@ -8,20 +8,34 @@ you contribute:
|
||||
1. Fork the repository.
|
||||
2. Create new branch based on the *master* branch (`git checkout -b your_branch master`). If your contribution is a bug fix, you should name your branch `bugfix/xxx`; for a feature, it should be `feature/xxx`. Otherwise, just use your good judgment. Consistent naming of branches is appreciated since it makes the output of `git branch` easier to understand with a single glance.
|
||||
3. Do your modifications on that branch. Except for special cases, your contribution should include proper unit tests and documentation.
|
||||
4. Make sure your modifications did not break anything by building, running tests and checking code coverage (test coverage should not be less than 100%):
|
||||
4. Make sure your modifications did not break anything by building, running tests:
|
||||
```shell
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake -DBITSERY_BUILD_TESTS=ON ..
|
||||
make
|
||||
ctest
|
||||
make tests_coverage
|
||||
x-www-browser ./coverage/index.html
|
||||
(cd tests; ctest)
|
||||
```
|
||||
or run CTest scripts and view code coverage (scripts tested on ubuntu, requires lcov for coverage):
|
||||
```shell
|
||||
cd scripts
|
||||
ctest -S build.bitsery.cmake
|
||||
./show_coverage.sh build
|
||||
```
|
||||
5. Commit your changes, and push to your fork (`git push origin your_branch`). Commit message should be one line short description. When applicable, please squash adjacent *wip* commits into a single *logical* commit.
|
||||
6. Open a pull request against Bitsery *master* branch. Currently ongoing development is on *master*. At some point an integration branch will be set-up, and pull-requests should target that, but for now its all against master. You may see feature branches come and go, too.
|
||||
|
||||
|
||||
If you're working with visual studio, there is how to build and run all tests from command line
|
||||
```shell
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DBITSERY_BUILD_TESTS=ON -DGTEST_ROOT="<PATH to GTEST>" -DCMAKE_CXX_FLAGS_RELEASE=/MT ..
|
||||
cmake --build . --config Release
|
||||
(cd tests && ctest -C Release && cd ..)
|
||||
```
|
||||
/MT option might be optional, depending on how gtest was built.
|
||||
|
||||
## Style guide
|
||||
|
||||
Just use your own judgment and stick to the style of the surrounding code.
|
||||
Just use your own judgment and stick to the style of the surrounding code.
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Mindaugas Vinkelis
|
||||
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
|
||||
|
||||
35
README.md
35
README.md
@@ -15,31 +15,33 @@ 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.
|
||||
* raw pointers (no polymorphism yet).
|
||||
* smart and raw pointers with customizable runtime polymorphism support.
|
||||
* fine-grained bit-level serialization control.
|
||||
* Easily extendable for any type.
|
||||
* Allows flexible or/and verbose syntax for better serialization control.
|
||||
* Configurable endianess support.
|
||||
* Allows brief or/and verbose syntax for better serialization control.
|
||||
* Configurable endianness support.
|
||||
* No macros.
|
||||
|
||||
## Why to 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 |
|
||||
| | data size | serialize | deserialize |
|
||||
|------------------|-----------|-----------|-------------|
|
||||
| bitsery | 6913B | 1252ms | 1170ms |
|
||||
| bitsery_compress | 4213B | 1445ms | 1325ms |
|
||||
| boost | 11037B | 9952ms | 8767ms |
|
||||
| cereal | 10413B | 6497ms | 5470ms |
|
||||
| flatbuffers | 14924B | 6762ms | 2173ms |
|
||||
| yas | 10463B | 1352ms | 1109ms |
|
||||
| yas_compress | 7315B | 1673ms | 1598ms |
|
||||
|
||||
*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,7 +63,7 @@ void serialize(S& s, MyStruct& o) {
|
||||
s.value4b(o.i);
|
||||
s.value2b(o.e);
|
||||
s.container4b(o.fs, 10);
|
||||
};
|
||||
}
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
@@ -76,7 +78,6 @@ int main() {
|
||||
Buffer buffer;
|
||||
|
||||
auto writtenSize = quickSerialization<OutputAdapter>(buffer, data);
|
||||
|
||||
auto state = quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
@@ -96,6 +97,8 @@ This documentation comprises these parts:
|
||||
|
||||
Works with C++11 compiler, no additional dependencies, include `<bitsery/bitsery.h>` and you're done.
|
||||
|
||||
> some **bitsery** extensions might require higher C++ standard (e.g. `StdVariant`)
|
||||
|
||||
## Platforms
|
||||
|
||||
This library was tested on
|
||||
@@ -105,4 +108,4 @@ This library was tested on
|
||||
|
||||
## License
|
||||
|
||||
**bitsery** is licensed under the [MIT license](LICENSE).
|
||||
**bitsery** is licensed under the [MIT license](LICENSE).
|
||||
|
||||
@@ -4,68 +4,74 @@ 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`
|
||||
* `boolValue`
|
||||
* `container`
|
||||
* `ext`
|
||||
* `context`
|
||||
* `context<T>`
|
||||
* `contextOrNull<T>`
|
||||
* `object`
|
||||
* `text`
|
||||
* `value`
|
||||
* `operator()` (4.6.1) (when brief syntax is enabled)
|
||||
* `adapter` (5.0.0)
|
||||
* `boolValue` (4.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)
|
||||
|
||||
Serializer/Deserializer extensions via `ext` method (alphabetical order):
|
||||
* `BaseClass`
|
||||
* `Entropy`
|
||||
* `Growable`
|
||||
* `PointerOwner`
|
||||
* `PointerObserver`
|
||||
* `ReferencedByPointer`
|
||||
* `StdMap`
|
||||
* `StdOptional`
|
||||
* `StdQueue`
|
||||
* `StdSet`
|
||||
* `StdStack`
|
||||
* `ValueRange`
|
||||
* `VirtualBaseClass`
|
||||
|
||||
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)`
|
||||
* `BaseClass` (4.2.0)
|
||||
* `CompactValue` (4.4.0)
|
||||
* `CompactValueAsObject` (4.4.0)
|
||||
* `Entropy` (3.0.0)
|
||||
* `Growable` (3.0.0)
|
||||
* `PointerOwner` (4.1.0)
|
||||
* `PointerObserver` (4.1.0)
|
||||
* `ReferencedByPointer` (4.1.0)
|
||||
* `StdDuration` (4.6.0)
|
||||
* `StdMap` (3.0.0)
|
||||
* `StdOptional` (2.0.0)
|
||||
* `StdQueue` (4.0.0)
|
||||
* `StdSet` (4.0.0)
|
||||
* `StdSmartPrt` (4.3.0)
|
||||
* `StdStack` (4.0.0)
|
||||
* `StdTimePoint` (4.6.0)
|
||||
* `StdTuple` (4.6.0) (requires c++17)
|
||||
* `StdVariant` (4.6.0) (requires c++17)
|
||||
* `ValueRange` (3.0.0)
|
||||
* `VirtualBaseClass` (4.2.0)
|
||||
|
||||
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:
|
||||
* if you're getting static assert "please define 'serialize' function", most likely it is because your **serialize** function is not defined in same namespace as object.
|
||||
* if you're getting static assert "please define 'serialize' function", please define **serialize** function in same namespace as object, or in **bitsery** namespace, for more info [ADL](https://en.cppreference.com/w/cpp/language/adl).
|
||||
|
||||
Other:
|
||||
* [Contributing](../CONTRIBUTING.md)
|
||||
|
||||
@@ -33,18 +33,18 @@ Now let's review features in more detail.
|
||||
|
||||
* **Cross-platform compatible.** if same code compiles on Android, PS3 console, and your PC either x64 or x86 architecture, you are 100% sure it works.
|
||||
To achieve this, bitsery specifically defines size of underlying data, hence syntax is *value\<2\>* (alias function *value2b*) instead or *value*, or *container2b* for element type of 16bits, eg int16_t.
|
||||
Bitsery also applies endianess transformation if nessesarry.
|
||||
* **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/).
|
||||
But do it on your own risk, and static assert using *assertFundamentalTypeSizes* function if you're planing to use it accross 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.
|
||||
It also doesn't serialize any type information, all information needed is writen in your code!
|
||||
Bitsery also applies endianness transformation if necessary.
|
||||
* **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 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.
|
||||
@@ -65,5 +65,7 @@ Bitsery allows to use bit-level operations and has two extensions that use them:
|
||||
You want to support your custom container, its fine there is *ContainerTraits* for this, only few methods required to implement.
|
||||
To use same container for buffer writing/reading add specialization to *BufferAdapterTraits*.
|
||||
You want to customize serialization flow - use extensions, only two methods to define, and *ExtensionTraits* to further customize usage.
|
||||
* **Configurable endianess support.** default is *Little Endian*, but if your primary target is PowerPC architecture, eg. PlayStation3, just change your configuration to be *Big Endian*.
|
||||
* **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.
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
This is a quick guide to get **bitsery** up and running in a matter of minutes.
|
||||
The only prerequisite for running bitsery is a modern C++11 compliant compiler, such as GCC 4.9.4, clang 3.4, MSVC 2015, or newer.
|
||||
Older versions might work, but it is not tested.
|
||||
Older versions might work, but they have not been tested.
|
||||
|
||||
## Get bitsery
|
||||
|
||||
bitsery can be directly included in your project or installed anywhere you can access header files.
|
||||
**bitsery** can be directly included in your project or installed anywhere you can access header files.
|
||||
Grab the latest version, and include directory `bitsery_base_dir/include/` to your project.
|
||||
There's nothing to build or make - **bitsery** is header only.
|
||||
|
||||
@@ -27,11 +27,14 @@ using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
```
|
||||
|
||||
**bitsery** is very lightweight, so we need to explicitly include what we need.
|
||||
* `<bitsery/bitsery.h>` is a core header, that includes our Serializer and Deserializer
|
||||
* `<bitsery/adapter/buffer.h>` in order to write/read data we need specific adapter, depending on what underlying buffer will be. In this example we'll be using std::vector as our buffer, so we include buffer adapter.
|
||||
* <bitsery/traits/...> traits tells library how efficiently serialize particular container.
|
||||
|
||||
create alias types for *InputAdapter* and *OutputAdapter* using our vector as buffer.
|
||||
Include | Description
|
||||
--|--
|
||||
`<bitsery/bitsery.h>` | This is a core header, that includes our Serializer and Deserializer.
|
||||
`<bitsery/adapter/buffer.h>` | In order to write/read data, we need a specific adapter, depending on what underlying buffer will be. In this example, we'll be using `std::vector` as our buffer, so we include the buffer adapter.
|
||||
`<bitsery/traits/...>` | Traits tell the library how to efficiently serialize a particular container. Many common STL containers are supported out of the box.
|
||||
|
||||
Create alias types for *InputAdapter* and *OutputAdapter* using our vector as buffer.
|
||||
|
||||
## Add serialization method for your type
|
||||
|
||||
@@ -55,7 +58,7 @@ 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.
|
||||
@@ -73,9 +76,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 = quickSerialization(OutputAdapter{buffer}, data);
|
||||
auto state = quickDeserialization(InputAdapter{buffer.begin(), writtenSize}, res);
|
||||
```
|
||||
|
||||
These helper functions use default configuration *bitsery::DefaultConfig*
|
||||
@@ -98,30 +100,29 @@ using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = 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 = quickSerialization(OutputAdapter{buffer}, data);
|
||||
auto state = 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 == ReaderError::NoError && state.second);
|
||||
assert(data.fs == res.fs && data.i == res.i && std::strcmp(data.str, res.str) == 0);
|
||||
}
|
||||
```
|
||||
|
||||
**currently documentation and tutorial is progress, but for more usage examples see examples folder**
|
||||
**currently documentation and tutorial is progress, but for more usage examples see examples folder**
|
||||
|
||||
@@ -20,22 +20,21 @@
|
||||
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
#SOFTWARE.
|
||||
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
project(bitsery_examples CXX)
|
||||
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
|
||||
include_directories(${CMAKE_SOURCE_DIR}/include)
|
||||
if (NOT TARGET Bitsery::bitsery)
|
||||
message(FATAL_ERROR "Bitsery::bitsery alias not set. Please generate CMake from bitsery root directory.")
|
||||
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})
|
||||
foreach(ExampleFile ${ExampleFiles})
|
||||
get_filename_component(ExampleName ${ExampleFile} NAME_WE)
|
||||
add_executable(${ExampleName} ${ExampleFile})
|
||||
|
||||
ENDFOREACH()
|
||||
add_executable(bitsery.example.${ExampleName} ${ExampleFile})
|
||||
target_link_libraries(bitsery.example.${ExampleName} PRIVATE Bitsery::bitsery)
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
||||
target_compile_options(bitsery.example.${ExampleName} PRIVATE -Wextra -Wno-missing-braces -Wpedantic -Weffc++)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,13 +17,12 @@ 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
|
||||
115
examples/composite_types.cpp
Normal file
115
examples/composite_types.cpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
// include extensions to work with tuples and variants
|
||||
// these extesions only work with C++17
|
||||
|
||||
#if __cplusplus > 201402L
|
||||
#include <bitsery/ext/std_tuple.h>
|
||||
#include <bitsery/ext/std_variant.h>
|
||||
// let's include this extension to make it more interesting :)
|
||||
#include <bitsery/ext/compact_value.h>
|
||||
|
||||
struct MyStruct {
|
||||
std::vector<int32_t> v{};
|
||||
float f{};
|
||||
|
||||
bool operator==(const MyStruct& rhs) const {
|
||||
return v == rhs.v && f == rhs.f;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename S>
|
||||
void serialize(S& s, MyStruct& o) {
|
||||
s.container4b(o.v, 1000);
|
||||
s.value4b(o.f);
|
||||
}
|
||||
|
||||
// this will be the type that we want to serialize/deserialize
|
||||
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{
|
||||
// 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{
|
||||
// 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>{},
|
||||
// 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>{},
|
||||
// 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)
|
||||
// ext::OverloadExtObject<MyTuple, ext::StdTuple<ext::OverloadValue<float, 4>>>{},
|
||||
|
||||
// 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& s, int32_t& v) {
|
||||
s.ext4b(v, ext::CompactValue{});
|
||||
});
|
||||
},
|
||||
// NOTE.
|
||||
// it is possible to provide "auto" as type parameter
|
||||
// this will allow you to override all default 'serialize' functions
|
||||
// but in this case it will not be called, because we have explicitly provided overloads for all variant types
|
||||
// also note, that first parameter (serializer) is also "auto", this is required, so that it would be least specialized case
|
||||
// otherwise it will not compile if you any ext::Overload* helper defined, because it will have ambiguous definitions
|
||||
// (ext::OverLoad* defines (templated_type& s, concrete_type& o) and lambda would be (concrete_type& s, templated_type& o))
|
||||
[](auto& , auto& ) {
|
||||
assert(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
|
||||
//set some random data
|
||||
MyVariant data{MyTuple{-7549, {{-451, 2, 968, 75, 4, 156, 49}, 874.4f}}};
|
||||
// MyVariant data{MyStruct{{-451, 2, 968, 75, 4, 156, 49}, 874.4f}};
|
||||
MyVariant res{};
|
||||
|
||||
//create buffer to store data
|
||||
Buffer buffer;
|
||||
//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);
|
||||
|
||||
//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);
|
||||
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
assert(data == res);
|
||||
}
|
||||
#else
|
||||
#if defined(_MSC_VER)
|
||||
#pragma message("example works only on c++17")
|
||||
#else
|
||||
#warning "example works only on c++17"
|
||||
#endif
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -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,23 +53,25 @@ 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>();
|
||||
//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>;
|
||||
using namespace bitsery;
|
||||
// define adapter types,
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
|
||||
MyTypes::GameState data{};
|
||||
@@ -88,18 +90,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 = 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 = quickDeserialization(ctx, InputAdapter{buffer.begin(), writtenSize}, res);
|
||||
|
||||
assert(r.error() == ReaderError::NoError && r.isCompletedSuccessfully());
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
}
|
||||
|
||||
@@ -21,9 +21,6 @@ void serialize(S& s, MyStruct& o) {
|
||||
|
||||
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 +28,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};
|
||||
ser.object(data);
|
||||
//flush to writer
|
||||
AdapterAccess::getWriter(ser).flush();
|
||||
ser.adapter().flush();
|
||||
s.close();
|
||||
//reopen for reading
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -75,11 +75,6 @@ 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;
|
||||
};
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
MyTypes::Monster data{};
|
||||
@@ -89,17 +84,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 = 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 = quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
//this example coverls all the corner cases that can happen using inherintace
|
||||
//this example covers all the corner cases that can happen using inheritance
|
||||
//in reality virtual inherintance is usually avoided, so your code would look much simpler.
|
||||
//
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
//this header contains two extensions, that specifies inheritance type of base class
|
||||
// BaseClass - normal inheritance
|
||||
// VirtualBaseClass - when virtual inheritance is used
|
||||
//in order for virtual inheritance to work, InheritanceContext is required.
|
||||
//it can be created either internally (via configuration) or externally (pointer to context).
|
||||
//in order for virtual inheritance to work, InheritanceContext is required. for normal inheritance it is not required
|
||||
#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);
|
||||
}
|
||||
@@ -81,18 +80,11 @@ namespace bitsery {
|
||||
|
||||
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 = OutputBufferAdapter<Buffer>;
|
||||
using Reader = InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
|
||||
@@ -103,15 +95,13 @@ int main() {
|
||||
|
||||
Buffer buf{};
|
||||
|
||||
BasicSerializer<Writer> ser{OutputBufferAdapter<Buffer>{buf}, nullptr};
|
||||
ser.object(data);
|
||||
auto writtenSize = AdapterAccess::getWriter(ser).writtenBytesCount();
|
||||
ext::InheritanceContext ctx1;
|
||||
auto writtenSize = 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());
|
||||
|
||||
ext::InheritanceContext ctx2;
|
||||
auto state = quickDeserialization(ctx2, Reader{buf.begin(), writtenSize}, res);
|
||||
assert(state.first == 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
|
||||
}
|
||||
|
||||
64
examples/non_default_constructible.cpp
Normal file
64
examples/non_default_constructible.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// example of how to deserialize non default constructible objects
|
||||
//
|
||||
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
|
||||
class MyData {
|
||||
//define your private data
|
||||
float _x{0};
|
||||
float _y{0};
|
||||
//make bitsery:Access friend
|
||||
friend class bitsery::Access;
|
||||
//create default constructor, don't worry about class invariant, it will be restored in deserialization
|
||||
MyData() = default;
|
||||
//define serialize function
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value4b(_x);
|
||||
s.value4b(_y);
|
||||
}
|
||||
public:
|
||||
//define non default public constructor
|
||||
MyData(float x, float y):_x{x}, _y{y} {}
|
||||
//this is for convenience
|
||||
bool operator ==(const MyData&rhs) const {
|
||||
return _x == rhs._x && _y == rhs._y;
|
||||
}
|
||||
};
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using Writer = OutputBufferAdapter<Buffer>;
|
||||
using Reader = InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
|
||||
//initialize our data
|
||||
std::vector<MyData> data{};
|
||||
data.emplace_back(145.4f, 84.48f);
|
||||
std::vector<MyData> res{};
|
||||
|
||||
//create buffer
|
||||
Buffer buffer{};
|
||||
|
||||
//we cant use quick (de)serialization helper methods, because we ant to serialize container directly
|
||||
//create writer and serialize container
|
||||
Serializer<Writer> ser{buffer};
|
||||
ser.container(data, 10);
|
||||
ser.adapter().flush();
|
||||
|
||||
//create reader and deserialize container
|
||||
Deserializer<Reader> des{buffer.begin(), ser.adapter().writtenBytesCount()};
|
||||
des.container(res, 10);
|
||||
|
||||
//check if everything went ok
|
||||
assert(des.adapter().error() == 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
|
||||
@@ -82,17 +82,14 @@ using namespace bitsery;
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
using Writer = OutputBufferAdapter<Buffer>;
|
||||
using Reader = 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 +112,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();
|
||||
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,
|
||||
@@ -135,13 +126,9 @@ 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);
|
||||
auto state = quickDeserialization(ctx, Reader{buffer.begin(), writtenSize}, res);
|
||||
//check if everything went find
|
||||
assert(r.error() == ReaderError::NoError && r.isCompletedSuccessfully());
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
//also check for dangling pointers, after deserialization
|
||||
assert(ctx.isValid());
|
||||
}
|
||||
|
||||
271
examples/smart_pointers_with_polymorphism.cpp
Normal file
271
examples/smart_pointers_with_polymorphism.cpp
Normal file
@@ -0,0 +1,271 @@
|
||||
//
|
||||
// Created by fraillt on 18.4.26.
|
||||
//
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/ext/pointer.h>
|
||||
#include <bitsery/ext/inheritance.h>
|
||||
#include <bitsery/ext/std_smart_ptr.h>
|
||||
|
||||
//in order to work with polymorphic types, we need to describe few steps:
|
||||
// 1) describe relationships between base and derived types
|
||||
// this will allow to know what are possible types reachable from base class
|
||||
// 2) bind serializer to base class
|
||||
// this will allow to iterate through all types, and add serialization functions,
|
||||
// without this step compiler would simply remove functions that are not bound at compile-time even it we use type at runtime.
|
||||
|
||||
using bitsery::ext::BaseClass;
|
||||
|
||||
using bitsery::ext::PointerObserver;
|
||||
using bitsery::ext::StdSmartPtr;
|
||||
|
||||
//define our data structures
|
||||
struct Color {
|
||||
float r{}, g{}, b{};
|
||||
bool operator == (const Color& o) const {
|
||||
return std::tie(r, g, b) ==
|
||||
std::tie(o.r, o.g, o.b);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct Shape {
|
||||
Color clr{};
|
||||
virtual ~Shape() = 0;
|
||||
};
|
||||
|
||||
Shape::~Shape() = default;
|
||||
|
||||
struct Circle : Shape {
|
||||
int32_t radius{};
|
||||
bool operator == (const Circle& o) const {
|
||||
return std::tie(radius, clr) ==
|
||||
std::tie(o.radius, o.clr);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
struct Rectangle : Shape {
|
||||
int32_t width{};
|
||||
int32_t height{};
|
||||
bool operator == (const Rectangle& o) const {
|
||||
return std::tie(width, height, clr) ==
|
||||
std::tie(o.width, o.height, o.clr);
|
||||
}
|
||||
};
|
||||
|
||||
struct RoundedRectangle : Rectangle {
|
||||
int32_t radius{};
|
||||
bool operator == (const RoundedRectangle& o) const {
|
||||
return std::tie(radius, static_cast<const Rectangle&>(*this)) ==
|
||||
std::tie(o.radius, static_cast<const Rectangle&>(o));
|
||||
}
|
||||
};
|
||||
|
||||
//define serialization functions
|
||||
template<typename S>
|
||||
void serialize(S &s, Color &o) {
|
||||
//in real world scenario, it might be possible to serialize this using ValueRange, to map values in smaller space
|
||||
//but for the sake of this example keep it simple
|
||||
s.value4b(o.r);
|
||||
s.value4b(o.g);
|
||||
s.value4b(o.b);
|
||||
}
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, Shape &o) {
|
||||
s.object(o.clr);
|
||||
}
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, Circle &o) {
|
||||
s.ext(o, bitsery::ext::BaseClass<Shape>{});
|
||||
s.value4b(o.radius);
|
||||
}
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, Rectangle &o) {
|
||||
s.ext(o, bitsery::ext::BaseClass<Shape>{});
|
||||
s.value4b(o.width);
|
||||
s.value4b(o.height);
|
||||
}
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, RoundedRectangle &o) {
|
||||
s.ext(o, bitsery::ext::BaseClass<Rectangle>{});
|
||||
s.value4b(o.radius);
|
||||
}
|
||||
|
||||
//define our test structure
|
||||
struct SomeShapes {
|
||||
std::vector<std::shared_ptr<Shape>> sharedList;
|
||||
std::unique_ptr<Shape> uniquePtr;
|
||||
//weak ptr and refPtr will point to sharedList
|
||||
std::weak_ptr<Shape> weakPtr;
|
||||
Shape* refPtr;
|
||||
};
|
||||
|
||||
//creates object, and populates some data
|
||||
SomeShapes createData() {
|
||||
SomeShapes data{};
|
||||
{
|
||||
auto tmp = new RoundedRectangle{};
|
||||
tmp->height = 151572;
|
||||
tmp->width = 488795;
|
||||
tmp->radius = 898;
|
||||
tmp->clr.r = 0.5f;
|
||||
tmp->clr.g = 1.0f;
|
||||
tmp->clr.b = 1.0f;
|
||||
data.uniquePtr.reset(tmp);
|
||||
}
|
||||
{
|
||||
auto tmp = new Circle{};
|
||||
tmp->radius = 75987;
|
||||
tmp->clr.r = 0.5f;
|
||||
tmp->clr.g = 0.0f;
|
||||
tmp->clr.b = 1.0f;
|
||||
data.sharedList.emplace_back(tmp);
|
||||
}
|
||||
{
|
||||
auto tmp = new Rectangle{};
|
||||
tmp->height = 15157;
|
||||
tmp->width = 48879;
|
||||
tmp->clr.r = 1.0f;
|
||||
tmp->clr.g = 0.0f;
|
||||
tmp->clr.b = 0.0f;
|
||||
data.sharedList.emplace_back(tmp);
|
||||
}
|
||||
data.weakPtr = data.sharedList[0];
|
||||
data.refPtr = data.sharedList[1].get();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, SomeShapes &o) {
|
||||
s.ext(o.uniquePtr, StdSmartPtr{});
|
||||
// to make things more interesting first serialize weakPtr and refPtr,
|
||||
// even though objects that weakPtr and refPtr is serialized later,
|
||||
// bitsery will work regardless
|
||||
s.ext(o.weakPtr, StdSmartPtr{});
|
||||
s.ext(o.refPtr, PointerObserver{});
|
||||
s.container(o.sharedList, 100, [](S& s, std::shared_ptr<Shape> &item) {
|
||||
s.ext(item, StdSmartPtr{});
|
||||
});
|
||||
}
|
||||
|
||||
// STEP 1
|
||||
// define relationships between base and derived classes
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
//for each base class define DIRECTLY derived classes
|
||||
//e.g. PolymorphicBaseClass<Shape> : PolymorphicDerivedClasses<Circle, Rectangle, RoundedRectangle>
|
||||
// is incorrect, because RoundedRectangle does not directly derive from Shape
|
||||
template<>
|
||||
struct PolymorphicBaseClass<Shape> : PolymorphicDerivedClasses<Circle, Rectangle> {
|
||||
};
|
||||
|
||||
template<>
|
||||
struct PolymorphicBaseClass<Rectangle> : PolymorphicDerivedClasses<RoundedRectangle> {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// convenient type that stores all our types, so that we could easily register and
|
||||
// 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 Writer = OutputBufferAdapter<Buffer>;
|
||||
using Reader = 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>>;
|
||||
//NOTE:
|
||||
// RTTI can be customizable, if you can't use dynamic_cast and typeid, and have 'custom' solution
|
||||
using MySerializer = Serializer<Writer, TContext>;
|
||||
using MyDeserializer = Deserializer<Reader, TContext>;
|
||||
|
||||
//checks if deserialized data is equal
|
||||
void assertSameShapes(const SomeShapes &data, const SomeShapes &res) {
|
||||
{
|
||||
auto d = dynamic_cast<RoundedRectangle *>(data.uniquePtr.get());
|
||||
auto r = dynamic_cast<RoundedRectangle *>(res.uniquePtr.get());
|
||||
assert(r != nullptr);
|
||||
assert(*d == *r);
|
||||
}
|
||||
{
|
||||
auto d = dynamic_cast<Circle *>(data.sharedList[0].get());
|
||||
auto r = dynamic_cast<Circle *>(res.sharedList[0].get());
|
||||
assert(r != nullptr);
|
||||
assert(*d == *r);
|
||||
}
|
||||
{
|
||||
auto d = dynamic_cast<Rectangle *>(data.sharedList[1].get());
|
||||
auto r = dynamic_cast<Rectangle *>(res.sharedList[1].get());
|
||||
assert(r != nullptr);
|
||||
assert(*d == *r);
|
||||
}
|
||||
assert(res.weakPtr.lock().get() == res.sharedList[0].get());
|
||||
assert(res.refPtr == res.sharedList[1].get());
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
||||
auto data = createData();
|
||||
|
||||
//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
|
||||
// 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{});
|
||||
//create writer and serialize
|
||||
MySerializer ser{ctx, buffer};
|
||||
ser.object(data);
|
||||
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,
|
||||
//so we can successfully reconstruct pointers after deserialization
|
||||
assert(std::get<0>(ctx).isValid());
|
||||
}
|
||||
SomeShapes res{};
|
||||
{
|
||||
TContext ctx{};
|
||||
std::get<1>(ctx).registerBasesList<MyDeserializer>(MyPolymorphicClassesForRegistering{});
|
||||
//deserialize our data
|
||||
MyDeserializer des{ctx, buffer.begin(), writtenSize};
|
||||
des.object(res);
|
||||
assert(des.adapter().error() == 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,
|
||||
// it is only required if there are any pointers that manage shared state, e.g. std::shared_ptr
|
||||
assert(res.weakPtr.use_count() == 2);//one in sharedList and one in pointer linking context
|
||||
std::get<0>(ctx).clearSharedState();
|
||||
assert(res.weakPtr.use_count() == 1);
|
||||
}
|
||||
assertSameShapes(data, res);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
# Copyright (c) 2012 - 2015, Lars Bilke
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice, this
|
||||
# list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software without
|
||||
# specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
#
|
||||
#
|
||||
# 2012-01-31, Lars Bilke
|
||||
# - Enable Code Coverage
|
||||
#
|
||||
# 2013-09-17, Joakim Söderberg
|
||||
# - Added support for Clang.
|
||||
# - Some additional usage instructions.
|
||||
#
|
||||
# USAGE:
|
||||
|
||||
# 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here:
|
||||
# http://stackoverflow.com/a/22404544/80480
|
||||
#
|
||||
# 1. Copy this file into your cmake modules path.
|
||||
#
|
||||
# 2. Add the following line to your CMakeLists.txt:
|
||||
# INCLUDE(CodeCoverage)
|
||||
#
|
||||
# 3. Set compiler flags to turn off optimization and enable coverage:
|
||||
# SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage")
|
||||
# SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage")
|
||||
#
|
||||
# 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target
|
||||
# which runs your test executable and produces a lcov code coverage report:
|
||||
# Example:
|
||||
# SETUP_TARGET_FOR_COVERAGE(
|
||||
# my_coverage_target # Name for custom target.
|
||||
# test_driver # Name of the test driver executable that runs the tests.
|
||||
# # NOTE! This should always have a ZERO as exit code
|
||||
# # otherwise the coverage generation will not complete.
|
||||
# coverage # Name of output directory.
|
||||
# )
|
||||
#
|
||||
# 4. Build a Debug build:
|
||||
# cmake -DCMAKE_BUILD_TYPE=Debug ..
|
||||
# make
|
||||
# make my_coverage_target
|
||||
#
|
||||
#
|
||||
|
||||
# Check prereqs
|
||||
FIND_PROGRAM( GCOV_PATH gcov )
|
||||
FIND_PROGRAM( LCOV_PATH lcov )
|
||||
FIND_PROGRAM( GENHTML_PATH genhtml )
|
||||
FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests)
|
||||
|
||||
IF(NOT GCOV_PATH)
|
||||
MESSAGE(FATAL_ERROR "gcov not found! Aborting...")
|
||||
ENDIF() # NOT GCOV_PATH
|
||||
|
||||
IF("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang")
|
||||
IF("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3)
|
||||
MESSAGE(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...")
|
||||
ENDIF()
|
||||
ELSEIF(NOT CMAKE_COMPILER_IS_GNUCXX)
|
||||
MESSAGE(FATAL_ERROR "Compiler is not GNU gcc! Aborting...")
|
||||
ENDIF() # CHECK VALID COMPILER
|
||||
|
||||
SET(CMAKE_CXX_FLAGS_COVERAGE
|
||||
"-g -O0 --coverage -fprofile-arcs -ftest-coverage"
|
||||
CACHE STRING "Flags used by the C++ compiler during coverage builds."
|
||||
FORCE )
|
||||
SET(CMAKE_C_FLAGS_COVERAGE
|
||||
"-g -O0 --coverage -fprofile-arcs -ftest-coverage"
|
||||
CACHE STRING "Flags used by the C compiler during coverage builds."
|
||||
FORCE )
|
||||
SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE
|
||||
""
|
||||
CACHE STRING "Flags used for linking binaries during coverage builds."
|
||||
FORCE )
|
||||
SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
|
||||
""
|
||||
CACHE STRING "Flags used by the shared libraries linker during coverage builds."
|
||||
FORCE )
|
||||
MARK_AS_ADVANCED(
|
||||
CMAKE_CXX_FLAGS_COVERAGE
|
||||
CMAKE_C_FLAGS_COVERAGE
|
||||
CMAKE_EXE_LINKER_FLAGS_COVERAGE
|
||||
CMAKE_SHARED_LINKER_FLAGS_COVERAGE )
|
||||
|
||||
IF ( NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "Coverage"))
|
||||
MESSAGE( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" )
|
||||
ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug"
|
||||
|
||||
|
||||
# Param _targetname The name of new the custom make target
|
||||
# Param _testrunner The name of the target which runs the tests.
|
||||
# MUST return ZERO always, even on errors.
|
||||
# If not, no coverage report will be created!
|
||||
# Param _outputname lcov output is generated as _outputname.info
|
||||
# HTML report is generated in _outputname/index.html
|
||||
# Optional fourth parameter is passed as arguments to _testrunner
|
||||
# Pass them in list form, e.g.: "-j;2" for -j 2
|
||||
FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname)
|
||||
|
||||
IF(NOT LCOV_PATH)
|
||||
MESSAGE(FATAL_ERROR "lcov not found! Aborting...")
|
||||
ENDIF() # NOT LCOV_PATH
|
||||
|
||||
IF(NOT GENHTML_PATH)
|
||||
MESSAGE(FATAL_ERROR "genhtml not found! Aborting...")
|
||||
ENDIF() # NOT GENHTML_PATH
|
||||
|
||||
SET(coverage_info "${CMAKE_BINARY_DIR}/${_outputname}.info")
|
||||
SET(coverage_cleaned "${coverage_info}.cleaned")
|
||||
|
||||
SEPARATE_ARGUMENTS(test_command UNIX_COMMAND "${_testrunner}")
|
||||
|
||||
# Setup target
|
||||
ADD_CUSTOM_TARGET(${_targetname}
|
||||
|
||||
# Cleanup lcov
|
||||
${LCOV_PATH} --directory . --zerocounters
|
||||
|
||||
# Run tests
|
||||
COMMAND ${test_command} ${ARGV3}
|
||||
|
||||
# Capturing lcov counters and generating report
|
||||
COMMAND ${LCOV_PATH} --directory . --capture --output-file ${coverage_info}
|
||||
#extract only /include/bitsery directory
|
||||
COMMAND ${LCOV_PATH} --extract ${coverage_info} '*include/bitsery*' --output-file ${coverage_cleaned}
|
||||
COMMAND ${GENHTML_PATH} -o ${_outputname} ${coverage_cleaned}
|
||||
COMMAND ${CMAKE_COMMAND} -E remove ${coverage_info} ${coverage_cleaned}
|
||||
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report."
|
||||
)
|
||||
|
||||
# Show info where to find the report
|
||||
ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD
|
||||
COMMAND ;
|
||||
COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report."
|
||||
)
|
||||
|
||||
ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE
|
||||
|
||||
# Param _targetname The name of new the custom make target
|
||||
# Param _testrunner The name of the target which runs the tests
|
||||
# Param _outputname cobertura output is generated as _outputname.xml
|
||||
# Optional fourth parameter is passed as arguments to _testrunner
|
||||
# Pass them in list form, e.g.: "-j;2" for -j 2
|
||||
FUNCTION(SETUP_TARGET_FOR_COVERAGE_COBERTURA _targetname _testrunner _outputname)
|
||||
|
||||
IF(NOT PYTHON_EXECUTABLE)
|
||||
MESSAGE(FATAL_ERROR "Python not found! Aborting...")
|
||||
ENDIF() # NOT PYTHON_EXECUTABLE
|
||||
|
||||
IF(NOT GCOVR_PATH)
|
||||
MESSAGE(FATAL_ERROR "gcovr not found! Aborting...")
|
||||
ENDIF() # NOT GCOVR_PATH
|
||||
|
||||
ADD_CUSTOM_TARGET(${_targetname}
|
||||
|
||||
# Run tests
|
||||
${_testrunner} ${ARGV3}
|
||||
|
||||
# Running gcovr
|
||||
COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} -e '${CMAKE_SOURCE_DIR}/tests/' -o ${_outputname}.xml
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
COMMENT "Running gcovr to produce Cobertura code coverage report."
|
||||
)
|
||||
|
||||
# Show info where to find the report
|
||||
ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD
|
||||
COMMAND ;
|
||||
COMMENT "Cobertura code coverage report saved in ${_outputname}.xml."
|
||||
)
|
||||
|
||||
ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE_COBERTURA
|
||||
@@ -1,17 +0,0 @@
|
||||
function(LinkTestLib TargetName)
|
||||
|
||||
add_dependencies(${TargetName} googletest)
|
||||
if(NOT WIN32 OR MINGW)
|
||||
FOREACH(LibName ${GTestLinkLibNames})
|
||||
target_link_libraries(${TargetName} ${GTestLibsDir}/lib${LibName}.a )
|
||||
ENDFOREACH()
|
||||
else()
|
||||
FOREACH(LibName ${GTestLinkLibNames})
|
||||
target_link_libraries(${TargetName}
|
||||
debug ${GTestLibsDir}/DebugLibs/${CMAKE_FIND_LIBRARY_PREFIXES}${LibName}d${CMAKE_FIND_LIBRARY_SUFFIXES}
|
||||
optimized ${GTestLibsDir}/ReleaseLibs/${CMAKE_FIND_LIBRARY_PREFIXES}${LibName}${CMAKE_FIND_LIBRARY_SUFFIXES})
|
||||
ENDFOREACH()
|
||||
endif()
|
||||
|
||||
target_link_libraries(${TargetName} ${CMAKE_THREAD_LIBS_INIT})
|
||||
endfunction(LinkTestLib)
|
||||
@@ -1,79 +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.
|
||||
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
project(gtest_builder C CXX)
|
||||
include(ExternalProject)
|
||||
|
||||
set(ForceSharedCrt ON)
|
||||
set(DisablePThreads OFF)
|
||||
|
||||
if(MINGW)
|
||||
set(DisablePThreads ON)
|
||||
endif()
|
||||
|
||||
if (${UseGMock})
|
||||
message("use gmock")
|
||||
set(BuildArgs -DBUILD_GTEST=OFF -DBUILD_GMOCK=ON)
|
||||
else ()
|
||||
message("use gtest only")
|
||||
set(BuildArgs -DBUILD_GTEST=ON -DBUILD_GMOCK=OFF)
|
||||
endif()
|
||||
|
||||
if (WIN32 AND NOT MINGW)
|
||||
set(BuildArgs ${BuildArgs}
|
||||
-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG:PATH=DebugLibs
|
||||
-DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE:PATH=ReleaseLibs)
|
||||
endif()
|
||||
|
||||
ExternalProject_Add(googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
CMAKE_ARGS ${BuildArgs}
|
||||
-Dgtest_force_shared_crt=${ForceSharedCrt}
|
||||
-Dgtest_disable_pthreads=${DisablePThreads}
|
||||
PREFIX "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
# disable update command
|
||||
UPDATE_COMMAND ""
|
||||
# disable install step
|
||||
INSTALL_COMMAND ""
|
||||
)
|
||||
|
||||
#export variables
|
||||
ExternalProject_Get_Property(googletest source_dir)
|
||||
ExternalProject_Get_Property(googletest binary_dir)
|
||||
if (${UseGMock})
|
||||
# need to include both googletest and googlemock
|
||||
set(GTestIncludeDirs ${source_dir}/googlemock/include ${source_dir}/googletest/include PARENT_SCOPE)
|
||||
set(GTestLibsDir ${binary_dir}/googlemock PARENT_SCOPE)
|
||||
set(GTestLibName gmock PARENT_SCOPE)
|
||||
set(GTestMainLibName gmock_main PARENT_SCOPE)
|
||||
set(GTestLinkLibNames gmock_main PARENT_SCOPE)
|
||||
else()
|
||||
set(GTestIncludeDirs ${source_dir}/googletest/include PARENT_SCOPE)
|
||||
set(GTestLibsDir ${binary_dir}/googletest PARENT_SCOPE)
|
||||
set(GTestLibName gtest PARENT_SCOPE)
|
||||
set(GTestMainLibName gtest_main PARENT_SCOPE)
|
||||
# need to include both libs gtest and gtest_main
|
||||
set(GTestLinkLibNames gtest gtest_main PARENT_SCOPE)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_ADAPTER_BUFFER_H
|
||||
#define BITSERY_ADAPTER_BUFFER_H
|
||||
|
||||
@@ -29,100 +28,191 @@
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
//base class that stores container iterators, and is required for session support (for reading sessions data)
|
||||
template <typename Buffer>
|
||||
class BufferIterators {
|
||||
protected:
|
||||
using TIterator = typename traits::BufferAdapterTraits<Buffer>::TIterator;
|
||||
|
||||
template<typename Buffer>
|
||||
class BufferIterators {
|
||||
static constexpr bool isConstBuffer = std::is_const<Buffer>::value;
|
||||
using BuffNonConst = typename std::remove_const<Buffer>::type;
|
||||
public:
|
||||
BufferIterators(const BufferIterators&) = delete;
|
||||
BufferIterators& operator=(const BufferIterators&) = delete;
|
||||
BufferIterators(BufferIterators&&) = default;
|
||||
BufferIterators& operator=(BufferIterators&&) = default;
|
||||
virtual ~BufferIterators() = default;
|
||||
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}
|
||||
{
|
||||
: beginIt{begin},
|
||||
posIt{begin},
|
||||
endIt{end} {
|
||||
}
|
||||
|
||||
friend details::SessionAccess;
|
||||
|
||||
TIterator beginIt;
|
||||
TIterator posIt;
|
||||
TIterator endIt;
|
||||
};
|
||||
|
||||
|
||||
template <typename Buffer>
|
||||
class InputBufferAdapter: public BufferIterators<Buffer> {
|
||||
template<typename Buffer, typename Config = DefaultConfig>
|
||||
class InputBufferAdapter: public BufferIterators<Buffer>,
|
||||
public details::InputAdapterBaseCRTP<InputBufferAdapter<Buffer,Config>> {
|
||||
public:
|
||||
|
||||
friend details::InputAdapterBaseCRTP<InputBufferAdapter<Buffer,Config>>;
|
||||
using TConfig = Config;
|
||||
using TIterator = typename BufferIterators<Buffer>::TIterator;
|
||||
using TValue = typename traits::BufferAdapterTraits<Buffer>::TValue;
|
||||
static_assert(details::IsDefined<TValue>::value, "Please define BufferAdapterTraits or include from <bitsery/traits/...>");
|
||||
static_assert(traits::ContainerTraits<Buffer>::isContiguous, "BufferAdapter only works with contiguous containers");
|
||||
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),
|
||||
_endReadPos{endIt} {
|
||||
|
||||
InputBufferAdapter(TIterator begin, TIterator end): BufferIterators<Buffer>(begin, end)
|
||||
{
|
||||
}
|
||||
|
||||
InputBufferAdapter(TIterator begin, size_t size)
|
||||
:InputBufferAdapter(begin, std::next(begin, size))
|
||||
{
|
||||
: BufferIterators<Buffer>(begin, std::next(begin, size)),
|
||||
_endReadPos{std::next(begin, size)} {
|
||||
}
|
||||
|
||||
void read(TValue* data, size_t 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>{});
|
||||
}
|
||||
|
||||
size_t currentReadPos() const {
|
||||
return static_cast<size_t>(std::distance(this->beginIt, this->posIt));
|
||||
}
|
||||
|
||||
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.");
|
||||
const auto buffSize = static_cast<size_t>(std::distance(this->beginIt, this->endIt));
|
||||
if (buffSize >= pos) {
|
||||
_overflowOnReadEndPos = pos == 0;
|
||||
if (pos == 0)
|
||||
pos = buffSize;
|
||||
_endReadPos = std::next(this->beginIt, pos);
|
||||
} else {
|
||||
error(ReaderError::DataOverflow);
|
||||
}
|
||||
}
|
||||
|
||||
size_t currentReadEndPos() const {
|
||||
if (_overflowOnReadEndPos)
|
||||
return 0;
|
||||
return static_cast<size_t>(std::distance(this->beginIt, _endReadPos));
|
||||
}
|
||||
|
||||
ReaderError error() const {
|
||||
return _err;
|
||||
}
|
||||
|
||||
void error(ReaderError error) {
|
||||
if (_err == ReaderError::NoError) {
|
||||
_err = error;
|
||||
_endReadPos = this->endIt;
|
||||
this->beginIt = this->endIt;
|
||||
this->posIt = this->endIt;
|
||||
}
|
||||
}
|
||||
|
||||
bool isCompletedSuccessfully() const {
|
||||
return this->posIt == this->endIt && _err == ReaderError::NoError;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void readChecked(TValue *data, size_t size, std::false_type) {
|
||||
//for optimization
|
||||
auto tmp = this->posIt;
|
||||
this->posIt += size;
|
||||
if (std::distance(this->posIt, this->endIt) >= 0) {
|
||||
assert(std::distance(this->posIt, this->endIt) >= 0);
|
||||
std::memcpy(data, std::addressof(*tmp), size);
|
||||
}
|
||||
|
||||
void readChecked(TValue *data, size_t size, std::true_type) {
|
||||
//for optimization
|
||||
auto tmp = this->posIt;
|
||||
this->posIt += size;
|
||||
if (std::distance(this->posIt, _endReadPos) >= 0) {
|
||||
std::memcpy(data, std::addressof(*tmp), size);
|
||||
} else {
|
||||
this->posIt -= size;
|
||||
//set everything to zeros
|
||||
std::memset(data, 0, size);
|
||||
|
||||
if (error() == ReaderError::NoError)
|
||||
setError(ReaderError::DataOverflow);
|
||||
if (_overflowOnReadEndPos)
|
||||
error(ReaderError::DataOverflow);
|
||||
}
|
||||
}
|
||||
|
||||
ReaderError error() const {
|
||||
auto res = std::distance(this->endIt, this->posIt);
|
||||
if (res > 0) {
|
||||
auto err = static_cast<ReaderError>(res);
|
||||
return err;
|
||||
void readInternal(TValue *data, size_t size) {
|
||||
readChecked(data, size, std::integral_constant<bool, Config::CheckAdapterErrors>{});
|
||||
}
|
||||
|
||||
void currentReadPosChecked(size_t pos, std::true_type) {
|
||||
if (static_cast<size_t>(std::distance(this->beginIt, this->endIt)) >= pos) {
|
||||
this->posIt = std::next(this->beginIt, pos);
|
||||
} else {
|
||||
error(ReaderError::DataOverflow);
|
||||
}
|
||||
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));
|
||||
void currentReadPosChecked(size_t pos, std::false_type) {
|
||||
this->posIt = std::next(this->beginIt, pos);
|
||||
}
|
||||
|
||||
bool isCompletedSuccessfully() const {
|
||||
return this->posIt == this->endIt;
|
||||
}
|
||||
TIterator _endReadPos;
|
||||
ReaderError _err = ReaderError::NoError;
|
||||
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;
|
||||
|
||||
static_assert(details::IsDefined<TValue>::value, "Please define BufferAdapterTraits or include from <bitsery/traits/...>");
|
||||
static_assert(traits::ContainerTraits<Buffer>::isContiguous, "BufferAdapter only works with contiguous containers");
|
||||
static_assert(details::IsDefined<TValue>::value,
|
||||
"Please define BufferAdapterTraits or include from <bitsery/traits/...>");
|
||||
static_assert(traits::ContainerTraits<Buffer>::isContiguous,
|
||||
"BufferAdapter only works with contiguous containers");
|
||||
|
||||
OutputBufferAdapter(Buffer &buffer)
|
||||
: _buffer{std::addressof(buffer)}
|
||||
{
|
||||
: _buffer{std::addressof(buffer)} {
|
||||
|
||||
init(TResizable{});
|
||||
}
|
||||
|
||||
OutputBufferAdapter(const OutputBufferAdapter&) = delete;
|
||||
OutputBufferAdapter& operator=(const OutputBufferAdapter&) = delete;
|
||||
OutputBufferAdapter(OutputBufferAdapter&&) = default;
|
||||
OutputBufferAdapter& operator = (OutputBufferAdapter&&) = default;
|
||||
|
||||
void write(const TValue *data, size_t size) {
|
||||
writeInternal(data, size, TResizable{});
|
||||
void currentWritePos(size_t pos) {
|
||||
const auto currPos =static_cast<size_t>(std::distance(std::begin(*_buffer), _outIt));
|
||||
const auto maxPos = currPos > pos ? currPos : pos;
|
||||
if (maxPos > _biggestCurrentPos) {
|
||||
_biggestCurrentPos = maxPos;
|
||||
}
|
||||
setCurrentWritePos(pos, TResizable{});
|
||||
}
|
||||
|
||||
size_t currentWritePos() const {
|
||||
return static_cast<size_t> (std::distance(std::begin(*_buffer), _outIt));
|
||||
}
|
||||
|
||||
void flush() {
|
||||
@@ -130,15 +220,21 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
size_t writtenBytesCount() const {
|
||||
return static_cast<size_t>(std::distance(std::begin(*_buffer), _outIt));
|
||||
const auto pos =static_cast<size_t>(std::distance(std::begin(*_buffer), _outIt));
|
||||
return pos > _biggestCurrentPos ? pos : _biggestCurrentPos;
|
||||
}
|
||||
|
||||
private:
|
||||
using TResizable = std::integral_constant<bool, traits::ContainerTraits<Buffer>::isResizable>;
|
||||
|
||||
void writeInternal(const TValue *data, size_t size) {
|
||||
writeInternalImpl(data, size, TResizable{});
|
||||
}
|
||||
|
||||
Buffer* _buffer;
|
||||
TIterator _outIt{};
|
||||
TIterator _end{};
|
||||
size_t _biggestCurrentPos{};
|
||||
|
||||
/*
|
||||
* resizable buffer
|
||||
@@ -153,7 +249,7 @@ namespace bitsery {
|
||||
_outIt = std::begin(*_buffer);
|
||||
}
|
||||
|
||||
void writeInternal(const TValue *data, const size_t size, std::true_type) {
|
||||
void writeInternalImpl(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;
|
||||
@@ -163,7 +259,7 @@ namespace bitsery {
|
||||
#else
|
||||
auto tmp = _outIt;
|
||||
_outIt += size;
|
||||
if (std::distance(_outIt , _end) >= 0) {
|
||||
if (std::distance(_outIt, _end) >= 0) {
|
||||
std::memcpy(std::addressof(*tmp), data, size);
|
||||
#endif
|
||||
} else {
|
||||
@@ -180,7 +276,17 @@ namespace bitsery {
|
||||
_end = std::end(*_buffer);
|
||||
_outIt = std::next(std::begin(*_buffer), pos);
|
||||
|
||||
writeInternal(data, size, std::true_type{});
|
||||
writeInternalImpl(data, size, std::true_type{});
|
||||
}
|
||||
}
|
||||
|
||||
void setCurrentWritePos(size_t pos, std::true_type) {
|
||||
const auto begin = std::begin(*_buffer);
|
||||
if (static_cast<size_t>(std::distance(begin, std::end(*_buffer))) >= pos) {
|
||||
_outIt = std::next(begin, pos);
|
||||
} else {
|
||||
traits::BufferAdapterTraits<Buffer>::increaseBufferSize(*_buffer);
|
||||
setCurrentWritePos(pos, std::true_type{});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,15 +298,20 @@ namespace bitsery {
|
||||
_end = std::end(*_buffer);
|
||||
}
|
||||
|
||||
void writeInternal(const TValue *data, size_t size, std::false_type) {
|
||||
void writeInternalImpl(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);
|
||||
}
|
||||
};
|
||||
|
||||
void setCurrentWritePos(size_t pos, std::false_type) {
|
||||
const auto begin = std::begin(*_buffer);
|
||||
assert(static_cast<size_t>(std::distance(begin, std::end(*_buffer))) >= pos);
|
||||
_outIt = std::next(begin, pos);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
@@ -20,7 +20,6 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_ADAPTER_STREAM_H
|
||||
#define BITSERY_ADAPTER_STREAM_H
|
||||
|
||||
@@ -28,61 +27,103 @@
|
||||
#include "../traits/array.h"
|
||||
#include <ios>
|
||||
|
||||
|
||||
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:
|
||||
|
||||
void readInternal(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, 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 , 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)} {}
|
||||
|
||||
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() {
|
||||
@@ -96,40 +137,43 @@ namespace bitsery {
|
||||
return 0u;
|
||||
}
|
||||
|
||||
//this method is only for stream writing
|
||||
bool isValidState() const {
|
||||
return !_ios->bad();
|
||||
private:
|
||||
|
||||
void writeInternal(const TValue* data, size_t size) {
|
||||
//for optimization
|
||||
_ios->rdbuf()->sputn( data , size );
|
||||
}
|
||||
|
||||
private:
|
||||
std::basic_ios<TChar, CharTraits>* _ios;
|
||||
};
|
||||
|
||||
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),
|
||||
:_ios(std::addressof(ostream)),
|
||||
_buf{},
|
||||
_outIt{}
|
||||
{
|
||||
init(bufferSize, TResizable{});
|
||||
}
|
||||
|
||||
//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)},
|
||||
: _ios{rhs._ios},
|
||||
_buf{},
|
||||
_outIt{}
|
||||
{
|
||||
@@ -139,7 +183,7 @@ namespace bitsery {
|
||||
};
|
||||
|
||||
BasicBufferedOutputStreamAdapter& operator = (BasicBufferedOutputStreamAdapter&& rhs) {
|
||||
_adapter = std::move(rhs._adapter);
|
||||
_ios = rhs._ios;
|
||||
//get current written size, before move
|
||||
auto size = std::distance(std::begin(rhs._buf), rhs._outIt);
|
||||
_buf = std::move(rhs._buf);
|
||||
@@ -147,9 +191,33 @@ namespace bitsery {
|
||||
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) {
|
||||
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);
|
||||
writeToStream(std::addressof(*begin), static_cast<size_t>(std::distance(begin, _outIt)));
|
||||
_outIt = begin;
|
||||
if (auto ostream = dynamic_cast<std::basic_ostream<TChar, CharTraits>*>(_ios))
|
||||
ostream->flush();
|
||||
}
|
||||
|
||||
size_t writtenBytesCount() const {
|
||||
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>;
|
||||
|
||||
void writeInternal(const TValue* data, size_t size) {
|
||||
auto tmp = _outIt;
|
||||
|
||||
#if defined(_MSC_VER) && (_ITERATOR_DEBUG_LEVEL > 0)
|
||||
@@ -157,38 +225,25 @@ namespace bitsery {
|
||||
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 {
|
||||
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);
|
||||
writeToStream(std::addressof(*_outIt), static_cast<size_t>(std::distance(_outIt, tmp)));
|
||||
writeToStream(data, size);
|
||||
}
|
||||
}
|
||||
|
||||
void flush() {
|
||||
auto begin = std::begin(_buf);
|
||||
_adapter.write(std::addressof(*begin), static_cast<size_t>(std::distance(begin, _outIt)));
|
||||
_outIt = begin;
|
||||
_adapter.flush();
|
||||
void writeToStream(const TValue* data, size_t size) {
|
||||
_ios->rdbuf()->sputn( data , size );
|
||||
}
|
||||
|
||||
size_t writtenBytesCount() const {
|
||||
return _adapter.writtenBytesCount();
|
||||
}
|
||||
|
||||
//this method is only for stream writing
|
||||
bool isValidState() const {
|
||||
return _adapter.isValidState();
|
||||
}
|
||||
|
||||
private:
|
||||
using TResizable = std::integral_constant<bool, traits::ContainerTraits<TBuffer>::isResizable>;
|
||||
|
||||
void init (size_t bufferSize, std::true_type) {
|
||||
_buf.resize(bufferSize);
|
||||
_outIt = std::begin(_buf);
|
||||
@@ -197,31 +252,30 @@ namespace bitsery {
|
||||
_outIt = std::begin(_buf);
|
||||
}
|
||||
|
||||
BasicOutputStreamAdapter<TChar, CharTraits> _adapter;
|
||||
std::basic_ios<TChar, CharTraits>* _ios;
|
||||
TBuffer _buf;
|
||||
BufferIt _outIt;
|
||||
};
|
||||
|
||||
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} {
|
||||
: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,272 +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();
|
||||
}
|
||||
}
|
||||
|
||||
const InputAdapter& adapter() const {
|
||||
return _inputAdapter;
|
||||
}
|
||||
|
||||
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 nessesarry
|
||||
_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,343 +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);
|
||||
}
|
||||
|
||||
const OutputAdapter& adapter() const {
|
||||
return _outputAdapter;
|
||||
}
|
||||
|
||||
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,9 +24,9 @@
|
||||
#ifndef BITSERY_BITSERY_H
|
||||
#define BITSERY_BITSERY_H
|
||||
|
||||
#define BITSERY_MAJOR_VERSION 4
|
||||
#define BITSERY_MINOR_VERSION 2
|
||||
#define BITSERY_PATCH_VERSION 0
|
||||
#define BITSERY_MAJOR_VERSION 5
|
||||
#define BITSERY_MINOR_VERSION 0
|
||||
#define BITSERY_PATCH_VERSION 1
|
||||
|
||||
#define BITSERY_QUOTE_MACRO(name) #name
|
||||
#define BITSERY_BUILD_VERSION_STR(major,minor, patch) \
|
||||
|
||||
@@ -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 "bitsery/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 "bitsery/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
|
||||
40
include/bitsery/brief_syntax/chrono.h
Normal file
40
include/bitsery/brief_syntax/chrono.h
Normal file
@@ -0,0 +1,40 @@
|
||||
//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_BRIEF_SYNTAX_TYPE_STD_CHRONO_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_CHRONO_H
|
||||
|
||||
#include "../ext/std_chrono.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename T, typename P>
|
||||
void serialize(S &s, std::chrono::duration<T, P> &obj) {
|
||||
s.template ext<sizeof(T)>(obj, ext::StdDuration{});
|
||||
}
|
||||
|
||||
template<typename S, typename C, typename T, typename P>
|
||||
void serialize(S &s, std::chrono::time_point<C, std::chrono::duration<T, P>> &obj) {
|
||||
s.template ext<sizeof(T)>(obj, ext::StdTimePoint{});
|
||||
}
|
||||
}
|
||||
|
||||
#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 "bitsery/details/brief_syntax_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename ... TArgs>
|
||||
void serialize(S &s, std::deque<TArgs... > &obj) {
|
||||
flexible::processContainer(s, obj);
|
||||
template<typename S, typename T, typename Allocator>
|
||||
void serialize(S &s, std::deque<T, Allocator> &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 "bitsery/details/brief_syntax_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename ... TArgs>
|
||||
void serialize(S &s, std::forward_list<TArgs... > &obj) {
|
||||
flexible::processContainer(s, obj);
|
||||
template<typename S, typename T, typename Allocator>
|
||||
void serialize(S &s, std::forward_list<T, Allocator> &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 "bitsery/details/brief_syntax_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename ... TArgs>
|
||||
void serialize(S &s, std::list<TArgs... > &obj) {
|
||||
flexible::processContainer(s, obj);
|
||||
template<typename S, typename T, typename Allocator>
|
||||
void serialize(S &s, std::list<T, Allocator> &obj) {
|
||||
brief_syntax::processContainer(s, obj);
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_LIST_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_LIST_H
|
||||
@@ -21,34 +21,30 @@
|
||||
//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"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename ... TArgs>
|
||||
void serialize(S &s, std::map<TArgs ... > &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
using TKey = typename std::map<TArgs...>::key_type;
|
||||
using TValue = typename std::map<TArgs...>::mapped_type;
|
||||
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](TKey& key, TValue& value) {
|
||||
[](S& s, Key& key, T& value) {
|
||||
s.object(key);
|
||||
s.object(value);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename S, typename ... TArgs>
|
||||
void serialize(S &s, std::multimap<TArgs ... > &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
using TKey = typename std::multimap<TArgs...>::key_type;
|
||||
using TValue = typename std::multimap<TArgs...>::mapped_type;
|
||||
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](TKey& key, TValue& 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
|
||||
45
include/bitsery/brief_syntax/memory.h
Normal file
45
include/bitsery/brief_syntax/memory.h
Normal file
@@ -0,0 +1,45 @@
|
||||
//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_BRIEF_SYNTAX_TYPE_STD_MEMORY_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_MEMORY_H
|
||||
|
||||
#include "../ext/std_smart_ptr.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename T, typename D>
|
||||
void serialize(S &s, std::unique_ptr<T, D> &obj) {
|
||||
s.ext(obj, ext::StdSmartPtr{});
|
||||
}
|
||||
|
||||
template<typename S, typename T>
|
||||
void serialize(S &s, std::shared_ptr<T> &obj) {
|
||||
s.ext(obj, ext::StdSmartPtr{});
|
||||
}
|
||||
|
||||
template<typename S, typename T>
|
||||
void serialize(S &s, std::weak_ptr<T> &obj) {
|
||||
s.ext(obj, ext::StdSmartPtr{});
|
||||
}
|
||||
}
|
||||
|
||||
#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,23 +21,23 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_UNORDERED_SET_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_UNORDERED_SET_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_SET_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_SET_H
|
||||
|
||||
#include <unordered_set>
|
||||
#include <set>
|
||||
#include "../ext/std_set.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename ... TArgs>
|
||||
void serialize(S &s, std::unordered_set<TArgs...> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
template<typename S, typename Key, typename Compare, typename Allocator>
|
||||
void serialize(S &s, std::set<Key, Compare, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
s.ext(obj, ext::StdSet{maxSize});
|
||||
}
|
||||
|
||||
template<typename S, typename ... TArgs>
|
||||
void serialize(S &s, std::unordered_multiset<TArgs...> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
template<typename S, typename Key, typename Compare, typename Allocator>
|
||||
void serialize(S &s, std::multiset<Key, Compare, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
s.ext(obj, ext::StdSet{maxSize});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_UNORDERED_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 "bitsery/details/brief_syntax_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename T, typename ... TArgs>
|
||||
void serialize(S &s, std::basic_string<T, TArgs...> &str) {
|
||||
flexible::processContainer(s, str);
|
||||
template<typename S, typename CharT, typename Traits, typename Allocator>
|
||||
void serialize(S &s, std::basic_string<CharT, Traits, Allocator> &str) {
|
||||
brief_syntax::processContainer(s, str);
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_STRING_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_STRING_H
|
||||
35
include/bitsery/brief_syntax/tuple.h
Normal file
35
include/bitsery/brief_syntax/tuple.h
Normal file
@@ -0,0 +1,35 @@
|
||||
//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_BRIEF_SYNTAX_TYPE_STD_TUPLE_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_TUPLE_H
|
||||
|
||||
#include "../ext/std_tuple.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename ...Ts>
|
||||
void serialize(S &s, std::tuple<Ts...> &obj) {
|
||||
s.ext(obj, ext::StdTuple{});
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_TUPLE_H
|
||||
@@ -21,30 +21,26 @@
|
||||
//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"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename ... TArgs>
|
||||
void serialize(S &s, std::unordered_map<TArgs ... > &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
using TKey = typename std::unordered_map<TArgs...>::key_type;
|
||||
using TValue = typename std::unordered_map<TArgs...>::mapped_type;
|
||||
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](TKey& key, TValue& value) {
|
||||
[](S& s, Key& key, T& value) {
|
||||
s.object(key);
|
||||
s.object(value);
|
||||
});
|
||||
}
|
||||
|
||||
template<typename S, typename ... TArgs>
|
||||
void serialize(S &s, std::unordered_multimap<TArgs ... > &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
using TKey = typename std::unordered_multimap<TArgs...>::key_type;
|
||||
using TValue = typename std::unordered_multimap<TArgs...>::mapped_type;
|
||||
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](TKey& key, TValue& value) {
|
||||
[](S& s, Key& key, T& value) {
|
||||
s.object(key);
|
||||
s.object(value);
|
||||
});
|
||||
@@ -52,4 +48,4 @@ namespace bitsery {
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_UNORDERED_MAP_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_UNORDERED_MAP_H
|
||||
@@ -21,23 +21,23 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_FLEXIBLE_TYPE_STD_SET_H
|
||||
#define BITSERY_FLEXIBLE_TYPE_STD_SET_H
|
||||
#ifndef BITSERY_BRIEF_SYNTAX_TYPE_STD_UNORDERED_SET_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_UNORDERED_SET_H
|
||||
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include "../ext/std_set.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename ... TArgs>
|
||||
void serialize(S &s, std::set<TArgs...> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
template<typename S, typename Key, typename Hash, typename KeyEqual, typename Allocator>
|
||||
void serialize(S &s, std::unordered_set<Key, Hash, KeyEqual, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
s.ext(obj, ext::StdSet{maxSize});
|
||||
}
|
||||
|
||||
template<typename S, typename ... TArgs>
|
||||
void serialize(S &s, std::multiset<TArgs...> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
template<typename S, typename Key, typename Hash, typename KeyEqual, typename Allocator>
|
||||
void serialize(S &s, std::unordered_multiset<Key, Hash, KeyEqual, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
|
||||
s.ext(obj, ext::StdSet{maxSize});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_SET_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_UNORDERED_SET_H
|
||||
35
include/bitsery/brief_syntax/variant.h
Normal file
35
include/bitsery/brief_syntax/variant.h
Normal file
@@ -0,0 +1,35 @@
|
||||
//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_BRIEF_SYNTAX_TYPE_STD_VARIANT_H
|
||||
#define BITSERY_BRIEF_SYNTAX_TYPE_STD_VARIANT_H
|
||||
|
||||
#include "../ext/std_variant.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename ...Ts>
|
||||
void serialize(S &s, std::variant<Ts...> &obj) {
|
||||
s.ext(obj, ext::StdVariant{});
|
||||
}
|
||||
}
|
||||
|
||||
#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 "bitsery/details/brief_syntax_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
template<typename S, typename ... TArgs>
|
||||
void serialize(S &s, std::vector<TArgs... > &obj) {
|
||||
flexible::processContainer(s, obj);
|
||||
template<typename S, typename T, typename Allocator>
|
||||
void serialize(S &s, std::vector<T, Allocator> &obj) {
|
||||
brief_syntax::processContainer(s, obj);
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_FLEXIBLE_TYPE_STD_VECTOR_H
|
||||
#endif //BITSERY_BRIEF_SYNTAX_TYPE_STD_VECTOR_H
|
||||
@@ -29,24 +29,22 @@
|
||||
namespace bitsery {
|
||||
|
||||
/*
|
||||
* endianess
|
||||
* endianness
|
||||
*/
|
||||
enum class EndiannessType {
|
||||
LittleEndian,
|
||||
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,152 @@
|
||||
#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;
|
||||
T 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 |= shiftedRes;
|
||||
m_scratch >>= bits;
|
||||
m_scratchBits -= bits;
|
||||
bitsLeft -= bits;
|
||||
}
|
||||
v = 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 +178,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 +212,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 +224,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 +234,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 +244,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 +267,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 +279,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 +296,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 +308,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 +320,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 +354,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 +423,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
|
||||
@@ -352,14 +444,14 @@ namespace bitsery {
|
||||
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));
|
||||
this->_adapter.template readBuffer<VSIZE>(reinterpret_cast<TIntegral*>(&(*first)), std::distance(first, last));
|
||||
}
|
||||
|
||||
//process by calling functions
|
||||
template<typename It, typename Fnc>
|
||||
void procContainer(It first, It last, Fnc fnc) {
|
||||
for (; first != last; ++first)
|
||||
fnc(*first);
|
||||
fnc(*this, *first);
|
||||
}
|
||||
|
||||
//process object types
|
||||
@@ -381,20 +473,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 +502,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 +526,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 +541,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()};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,19 +29,79 @@
|
||||
#include <vector>
|
||||
#include <stack>
|
||||
#include <cstring>
|
||||
#include "adapter_utils.h"
|
||||
#include <climits>
|
||||
#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> {
|
||||
/**
|
||||
* 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 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* swap utils
|
||||
*/
|
||||
|
||||
//add swap functions to class, to avoid compilation warning about unused functions
|
||||
struct SwapImpl {
|
||||
@@ -82,6 +142,9 @@ namespace bitsery {
|
||||
return SwapImpl::exec(static_cast<UT>(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* endianness utils
|
||||
*/
|
||||
//add test data in separate struct, because some compilers only support constexpr functions with return-only body
|
||||
struct EndiannessTestData {
|
||||
static constexpr uint32_t _sample4Bytes = 0x01020304;
|
||||
@@ -95,6 +158,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 {
|
||||
@@ -106,18 +179,121 @@ namespace bitsery {
|
||||
using type = uint16_t;
|
||||
};
|
||||
|
||||
/*
|
||||
* class used by session reader, to access underlying iterators of buffer
|
||||
/**
|
||||
* 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, "");
|
||||
writeSwapped(&v, 1, 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, "");
|
||||
writeSwapped(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 writeSwapped(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)->writeInternal(reinterpret_cast<const typename Adapter::TValue *>(&res), sizeof(T));
|
||||
});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void writeSwapped(const T *v, size_t count, std::false_type) {
|
||||
static_cast<Adapter*>(this)->writeInternal(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, "");
|
||||
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() {
|
||||
|
||||
}
|
||||
|
||||
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 directRead(T *v, size_t count) {
|
||||
static_assert(!std::is_const<T>::value, "");
|
||||
static_cast<Base*>(this)->readInternal(reinterpret_cast<typename Base::TValue *>(v), sizeof(T) * count);
|
||||
//swap each byte if necessary
|
||||
_swapDataBits(v, count, ShouldSwap<typename Base::TConfig>{});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void _swapDataBits(T *v, size_t count, std::true_type) {
|
||||
std::for_each(v, std::next(v, 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
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -35,7 +35,7 @@ namespace bitsery {
|
||||
NotDefinedType(T&& ...){}
|
||||
NotDefinedType() = default;
|
||||
//define operators so that we also swallow deeper errors, to reduce error stack
|
||||
//this time will be used as iterator, so define all operators nessesarry to work with iterators
|
||||
//this time will be used as iterator, so define all operators necessary to work with iterators
|
||||
friend bool operator == (const NotDefinedType&, const NotDefinedType&) {
|
||||
return true;
|
||||
}
|
||||
@@ -56,7 +56,7 @@ namespace bitsery {
|
||||
int& operator*() {
|
||||
return data;
|
||||
}
|
||||
int data;
|
||||
int data{};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
|
||||
@@ -26,23 +26,67 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <tuple>
|
||||
#include "adapter_utils.h"
|
||||
#include "adapter_common.h"
|
||||
#include "../traits/core/traits.h"
|
||||
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
//this allows to call private serialize method for the class
|
||||
//just make friend it to that class
|
||||
struct Access {
|
||||
//this allows to call private serialize method, and construct instance (if no default constructor is provided) for your type
|
||||
//just make friend it in your class
|
||||
class Access {
|
||||
public:
|
||||
template<typename S, typename T>
|
||||
static auto serialize(S &s, T &obj) -> decltype(obj.serialize(s)) {
|
||||
obj.serialize(s);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T create() {
|
||||
//if you get an error here, please create default constructor
|
||||
return T{};
|
||||
}
|
||||
template <typename 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
|
||||
//specialize this class by inheriting from either UseNonMemberFnc or UseMemberFnc
|
||||
//e.g.
|
||||
//template <> struct SelectSerializeFnc<MyDerivedClass>:UseMemberFnc {};
|
||||
template<typename T>
|
||||
@@ -55,23 +99,6 @@ namespace bitsery {
|
||||
struct UseMemberFnc : std::integral_constant<int, 2> {
|
||||
};
|
||||
|
||||
|
||||
//serializer/deserializer, does not 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
|
||||
@@ -116,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>
|
||||
@@ -162,23 +189,23 @@ 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
|
||||
|
||||
|
||||
//used for extensions, when extension TValue = void
|
||||
//used for extensions when extension TValue = void
|
||||
struct DummyType {
|
||||
};
|
||||
|
||||
/*
|
||||
* this includes all integral types floats and enums(except bool)
|
||||
* this includes all integral types, floats and enums(except bool)
|
||||
*/
|
||||
template<typename T>
|
||||
struct IsFundamentalType : std::integral_constant<bool,
|
||||
@@ -230,6 +257,10 @@ namespace bitsery {
|
||||
selectSerializeFnc(s, v, SelectSerializeFnc<TDecayed>{});
|
||||
}
|
||||
|
||||
static constexpr bool isDefined() {
|
||||
return HasSerializeFunction<S, T>::value || HasSerializeMethod<S, T>::value;
|
||||
}
|
||||
|
||||
private:
|
||||
static void selectSerializeFnc(S &s, T &v, std::integral_constant<int, 0>) {
|
||||
static_assert(!(HasSerializeFunction<S, T>::value && HasSerializeMethod<S, T>::value),
|
||||
@@ -255,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));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -269,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 {
|
||||
};
|
||||
@@ -277,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. Type doesn't exists.");
|
||||
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. Type doesn't exists.");
|
||||
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,228 +0,0 @@
|
||||
//
|
||||
// Created by fraillt on 17.10.5.
|
||||
//
|
||||
|
||||
#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
|
||||
186
include/bitsery/ext/compact_value.h
Normal file
186
include/bitsery/ext/compact_value.h
Normal file
@@ -0,0 +1,186 @@
|
||||
//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_COMPACT_VALUE_H
|
||||
#define BITSERY_EXT_COMPACT_VALUE_H
|
||||
|
||||
#include "../details/serialization_common.h"
|
||||
#include "../details/adapter_common.h"
|
||||
#include <cassert>
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
namespace details {
|
||||
|
||||
template <bool CheckOverflow>
|
||||
class CompactValueImpl {
|
||||
public:
|
||||
|
||||
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.adapter(), reinterpret_cast<const TValue&>(v), std::integral_constant<bool, sizeof(T) != 1>{});
|
||||
}
|
||||
|
||||
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.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 Writer, typename T>
|
||||
void serializeImpl(Writer &writer, const T &v, std::false_type) const {
|
||||
writer.template writeBytes<1>(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 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 Reader, typename T>
|
||||
void deserializeImpl(Reader &reader, T &v, std::true_type) const {
|
||||
using TUnsigned = SameSizeUnsigned<T>;
|
||||
TUnsigned res{};
|
||||
readBytes<Reader::TConfig::CheckDataErrors>(reader, res);
|
||||
v = zigZagDecode<T>(res, std::is_signed<typename IntegralFromFundamental<T>::TValue>{});
|
||||
}
|
||||
|
||||
// zigzag encode signed types
|
||||
template<typename T>
|
||||
const SameSizeUnsigned<T> &zigZagEncode(const T &v, std::false_type) const {
|
||||
return v;
|
||||
}
|
||||
|
||||
template<typename TResult, typename TUnsigned>
|
||||
const TResult &zigZagDecode(const TUnsigned &v, std::false_type) const{
|
||||
return v;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
SameSizeUnsigned<T> zigZagEncode(const T &v, std::true_type) const {
|
||||
return (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
|
||||
}
|
||||
|
||||
// write/read bytes one by one
|
||||
template<typename Writer, typename T>
|
||||
void writeBytes(Writer &w, const T &v) const {
|
||||
auto val = v;
|
||||
while(val > 0x7Fu) {
|
||||
w.template writeBytes<1>(static_cast<uint8_t>(val | 0x80u));
|
||||
val >>=7u;
|
||||
}
|
||||
w.template writeBytes<1>(static_cast<uint8_t>(val));
|
||||
}
|
||||
|
||||
template<bool CheckErrors, typename Reader, typename T>
|
||||
void readBytes(Reader &r, T &v) const {
|
||||
constexpr auto TBITS = sizeof(T)*8;
|
||||
uint8_t b1{0x80u};
|
||||
auto i = 0u;
|
||||
for (;i < TBITS && b1 > 0x7Fu; i +=7u) {
|
||||
r.template readBytes<1>(b1);
|
||||
v += static_cast<T>(b1 & 0x7Fu) << i;
|
||||
}
|
||||
handleReadOverflow<Reader, T>(r, i, b1,
|
||||
std::integral_constant<bool, CheckOverflow && CheckErrors>{});
|
||||
}
|
||||
template <typename Reader, typename T>
|
||||
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.error(bitsery::ReaderError::InvalidData);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Reader, typename T>
|
||||
void handleReadOverflow(Reader &, unsigned , uint8_t , std::false_type) const {
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace ext {
|
||||
|
||||
// this type will use value overload, and do not check if type is sufficiently large during deserialization
|
||||
class CompactValue: public details::CompactValueImpl<false> {};
|
||||
|
||||
// this type will enable object overload, and set DataOverflow if value doesn't fit in type, during deserialization
|
||||
class CompactValueAsObject: public details::CompactValueImpl<true> {};
|
||||
|
||||
}
|
||||
|
||||
namespace traits {
|
||||
|
||||
template<typename T>
|
||||
struct ExtensionTraits<ext::CompactValue, T> {
|
||||
using TValue = T;
|
||||
static constexpr bool SupportValueOverload = true;
|
||||
// disable object overload, because we don't have implemented serialization function for fundamental types
|
||||
static constexpr bool SupportObjectOverload = false;
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct ExtensionTraits<ext::CompactValueAsObject, T> {
|
||||
// use dummy implemenations for value and object overload
|
||||
using TValue = void;
|
||||
// only enable object overload
|
||||
static constexpr bool SupportValueOverload = false;
|
||||
static constexpr bool SupportObjectOverload = true;
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
};
|
||||
|
||||
template<typename T, bool Check>
|
||||
struct ExtensionTraits<details::CompactValueImpl<Check>, T> {
|
||||
using TValue = T;
|
||||
static constexpr bool SupportValueOverload = !Check;
|
||||
static constexpr bool SupportObjectOverload = Check;
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif //BITSERY_EXT_COMPACT_VALUE_H
|
||||
@@ -56,28 +56,28 @@ 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();
|
||||
d.adapter().align();
|
||||
if (index)
|
||||
obj = *std::next(std::begin(_values), 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 "bitsery/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();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -23,380 +23,185 @@
|
||||
#ifndef BITSERY_EXT_POINTER_H
|
||||
#define BITSERY_EXT_POINTER_H
|
||||
|
||||
#include <cassert>
|
||||
#include "../traits/core/traits.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include "utils/pointer_utils.h"
|
||||
#include "utils/polymorphism_utils.h"
|
||||
#include "utils/rtti_utils.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
namespace ext {
|
||||
|
||||
//forward declare
|
||||
class PointerLinkingContext;
|
||||
namespace pointer_details {
|
||||
|
||||
enum class PointerType {
|
||||
Nullable,
|
||||
NotNull
|
||||
};
|
||||
template<typename T>
|
||||
struct PtrOwnerManager {
|
||||
static_assert(std::is_pointer<T>::value, "");
|
||||
|
||||
enum class PointerOwnershipType:uint8_t {
|
||||
//is not responsible for pointer lifetime management.
|
||||
Observer,
|
||||
//only ONE owner is responsible for this pointers creation/destruction
|
||||
Owner,
|
||||
//MANY shared owners is responsible for pointer creation/destruction
|
||||
//requires additional context to manage shared owners themselves.
|
||||
Shared
|
||||
};
|
||||
using TElement = typename std::remove_pointer<T>::type;
|
||||
|
||||
namespace details_pointer {
|
||||
|
||||
//helper functions that creates or destroys pointers
|
||||
//useful, because can be specialized
|
||||
template <typename T>
|
||||
void destroyObject(T &ptr) {
|
||||
if (ptr) {
|
||||
delete ptr;
|
||||
}
|
||||
ptr = nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void createObject(T &ptr) {
|
||||
using TNonPtr = typename std::remove_pointer<T>::type;
|
||||
if (ptr == nullptr)
|
||||
ptr = new TNonPtr{};
|
||||
}
|
||||
|
||||
|
||||
template <typename S, typename TPtr>
|
||||
void serializeObject(S &s, const TPtr &obj) {
|
||||
using TNonPtr = typename std::remove_pointer<TPtr>::type;
|
||||
static_assert(!std::is_polymorphic<TNonPtr>::value, "Polymorphic types are not supported");
|
||||
serializeImpl(s, *obj, details::IsFundamentalType<TNonPtr>{});
|
||||
}
|
||||
|
||||
template <typename S, typename T>
|
||||
void serializeImpl(S& s, const T& obj, std::true_type) {
|
||||
s.template value<sizeof(T)>(obj);
|
||||
}
|
||||
|
||||
template <typename S, typename T>
|
||||
void serializeImpl(S& s, const T& obj, std::false_type) {
|
||||
s.object(obj);
|
||||
}
|
||||
|
||||
template <typename S, typename TPtr>
|
||||
void deserializeObject(S &s, TPtr &obj) {
|
||||
using TNonPtr = typename std::remove_pointer<TPtr>::type;
|
||||
static_assert(!std::is_polymorphic<TNonPtr>::value, "Polymorphic types are not supported");
|
||||
deserializeImpl(s, *obj, details::IsFundamentalType<TNonPtr>{});
|
||||
}
|
||||
|
||||
template <typename S, typename T>
|
||||
void deserializeImpl(S& s, T& obj, std::true_type) {
|
||||
s.template value<sizeof(T)>(obj);
|
||||
}
|
||||
|
||||
template <typename S, typename T>
|
||||
void deserializeImpl(S& s, T& obj, std::false_type) {
|
||||
s.object(obj);
|
||||
}
|
||||
|
||||
class RawPointerManager {
|
||||
public:
|
||||
template <typename Writer, typename Ser, typename T>
|
||||
static void serializeObject(Writer& , Ser& ser, const T& obj) {
|
||||
|
||||
details_pointer::serializeObject(ser, obj);
|
||||
static TElement* getPtr(T& obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
template <typename Reader, typename Des, typename T>
|
||||
static void deserializeObject(Reader& , Des& des, T& obj) {
|
||||
details_pointer::createObject(obj);
|
||||
details_pointer::deserializeObject(des, obj);
|
||||
static constexpr PointerOwnershipType getOwnership() {
|
||||
return PointerOwnershipType::Owner;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void destroyObject(T& obj) {
|
||||
details_pointer::destroyObject(obj);
|
||||
}
|
||||
};
|
||||
|
||||
class PointerLinkingContextSerialization {
|
||||
public:
|
||||
explicit PointerLinkingContextSerialization()
|
||||
:_currId{0},
|
||||
_ptrMap{}
|
||||
{}
|
||||
|
||||
PointerLinkingContextSerialization(const PointerLinkingContextSerialization&) = delete;
|
||||
PointerLinkingContextSerialization& operator = (const PointerLinkingContextSerialization&) = delete;
|
||||
|
||||
PointerLinkingContextSerialization(PointerLinkingContextSerialization&&) = default;
|
||||
PointerLinkingContextSerialization& operator = (PointerLinkingContextSerialization&&) = default;
|
||||
~PointerLinkingContextSerialization() = default;
|
||||
|
||||
struct PointerInfo {
|
||||
PointerInfo(size_t id_, PointerOwnershipType ownershipType_)
|
||||
:id{id_},
|
||||
ownershipType{ownershipType_},
|
||||
sharedCount{0}
|
||||
{};
|
||||
size_t id;
|
||||
PointerOwnershipType ownershipType;
|
||||
size_t sharedCount;
|
||||
};
|
||||
|
||||
const PointerInfo& getInfoByPtr(const void *ptr, PointerOwnershipType ptrType) {
|
||||
auto res = _ptrMap.emplace(ptr, PointerInfo{_currId + 1u, ptrType});
|
||||
auto& ptrInfo = res.first->second;
|
||||
if (res.second) {
|
||||
++_currId;
|
||||
return ptrInfo;
|
||||
}
|
||||
//ptr already exists
|
||||
//for observer return success
|
||||
if (ptrType == PointerOwnershipType::Observer)
|
||||
return ptrInfo;
|
||||
//set owner and return success
|
||||
if (ptrInfo.ownershipType == PointerOwnershipType::Observer) {
|
||||
ptrInfo.ownershipType = ptrType;
|
||||
return ptrInfo;
|
||||
}
|
||||
//only shared ownership can get here multiple times
|
||||
assert(ptrType == PointerOwnershipType::Shared);
|
||||
ptrInfo.sharedCount++;
|
||||
return ptrInfo;
|
||||
static void create(T& obj, pointer_utils::PolyAllocWithTypeId alloc, size_t typeId) {
|
||||
obj = alloc.newObject<TElement>(typeId);
|
||||
}
|
||||
|
||||
//valid, when all pointers have owners.
|
||||
//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*, PointerInfo>& p) {
|
||||
return p.second.ownershipType != PointerOwnershipType::Observer;
|
||||
});
|
||||
static void createPolymorphic(T& obj, pointer_utils::PolyAllocWithTypeId alloc,
|
||||
const std::shared_ptr<PolymorphicHandlerBase>& handler) {
|
||||
obj = static_cast<TElement*>(handler->create(alloc));
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _currId;
|
||||
std::unordered_map<const void*, PointerInfo> _ptrMap;
|
||||
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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//this class is used to store context for shared ptr owners
|
||||
struct SharedContextBase {
|
||||
virtual ~SharedContextBase() = default;
|
||||
};
|
||||
template<typename T>
|
||||
struct PtrObserverManager {
|
||||
static_assert(std::is_pointer<T>::value, "");
|
||||
|
||||
class PointerLinkingContextDeserialization {
|
||||
public:
|
||||
explicit PointerLinkingContextDeserialization()
|
||||
: _idMap{}
|
||||
{}
|
||||
using TElement = typename std::remove_pointer<T>::type;
|
||||
|
||||
PointerLinkingContextDeserialization(const PointerLinkingContextDeserialization&) = delete;
|
||||
PointerLinkingContextDeserialization& operator = (const PointerLinkingContextDeserialization&) = delete;
|
||||
|
||||
PointerLinkingContextDeserialization(PointerLinkingContextDeserialization&&) = default;
|
||||
PointerLinkingContextDeserialization& operator = (PointerLinkingContextDeserialization&&) = default;
|
||||
~PointerLinkingContextDeserialization() = default;
|
||||
|
||||
|
||||
struct PointerInfo {
|
||||
PointerInfo(size_t id_, void* ptr, PointerOwnershipType ownershipType_)
|
||||
:id{id_},
|
||||
ownershipType{ownershipType_},
|
||||
ownerPtr{ptr},
|
||||
observersList{},
|
||||
sharedContext{}
|
||||
{};
|
||||
PointerInfo(const PointerInfo&) = delete;
|
||||
PointerInfo& operator = (const PointerInfo&) = delete;
|
||||
PointerInfo(PointerInfo&&) = default;
|
||||
PointerInfo&operator = (PointerInfo&&) = default;
|
||||
~PointerInfo() = default;
|
||||
|
||||
void processOwner(void* ptr) {
|
||||
ownerPtr = ptr;
|
||||
assert(ownershipType != PointerOwnershipType::Observer);
|
||||
for (auto& o:observersList)
|
||||
o.get() = ptr;
|
||||
observersList.clear();
|
||||
observersList.shrink_to_fit();
|
||||
}
|
||||
void processObserver(void* (&ptr)) {
|
||||
if (ownerPtr) {
|
||||
ptr = ownerPtr;
|
||||
} else {
|
||||
observersList.push_back(ptr);
|
||||
}
|
||||
}
|
||||
size_t id;
|
||||
PointerOwnershipType ownershipType;
|
||||
void* ownerPtr;
|
||||
std::vector<std::reference_wrapper<void*>> observersList;
|
||||
std::unique_ptr<SharedContextBase> sharedContext;
|
||||
};
|
||||
|
||||
PointerInfo& getInfoById(size_t id, PointerOwnershipType ptrType) {
|
||||
auto res = _idMap.emplace(id, PointerInfo{id, nullptr, ptrType});
|
||||
auto& ptrInfo = res.first->second;
|
||||
if (!res.second) {
|
||||
assert(ptrType != PointerOwnershipType::Owner || ptrInfo.ownershipType == PointerOwnershipType::Observer);
|
||||
if (ptrInfo.ownershipType == PointerOwnershipType::Observer)
|
||||
ptrInfo.ownershipType = ptrType;
|
||||
}
|
||||
return ptrInfo;
|
||||
static TElement* getPtr(T& obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
//valid, when all pointers has owners
|
||||
bool isPointerDeserializationValid() const {
|
||||
return std::all_of(_idMap.begin(), _idMap.end(), [](const std::pair<const size_t, PointerInfo>& p) {
|
||||
return p.second.ownershipType != PointerOwnershipType::Observer;
|
||||
});
|
||||
static constexpr PointerOwnershipType getOwnership() {
|
||||
return PointerOwnershipType::Observer;
|
||||
}
|
||||
|
||||
//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 destroy(T& obj, MemResourceBase* , size_t ) {
|
||||
obj = nullptr;
|
||||
}
|
||||
|
||||
static void destroyPolymorphic(T& obj, MemResourceBase* , PolymorphicHandlerBase& ) {
|
||||
obj = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<size_t, PointerInfo> _idMap;
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
PointerLinkingContext& getLinkingContext(S& s) {
|
||||
auto res = s.template context<PointerLinkingContext>();
|
||||
assert(res != nullptr);
|
||||
return *res;
|
||||
}
|
||||
template<typename T>
|
||||
struct NonPtrManager {
|
||||
|
||||
static_assert(!std::is_pointer<T>::value, "");
|
||||
|
||||
using TElement = T;
|
||||
|
||||
static TElement* getPtr(T& obj) {
|
||||
return &obj;
|
||||
}
|
||||
|
||||
static constexpr PointerOwnershipType getOwnership() {
|
||||
return PointerOwnershipType::Owner;
|
||||
}
|
||||
|
||||
// this code is unreachable for reference type, but is necessary to compile
|
||||
// LCOV_EXCL_START
|
||||
|
||||
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
|
||||
|
||||
};
|
||||
|
||||
// this class is used by NonPtrManager
|
||||
struct NoRTTI {
|
||||
template<typename TBase>
|
||||
static size_t get(TBase&) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename TBase>
|
||||
static constexpr size_t get() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
template<typename TBase, typename TDerived>
|
||||
static constexpr TDerived* cast(TBase* obj) {
|
||||
static_assert(!std::is_pointer<TDerived>::value, "");
|
||||
return dynamic_cast<TDerived*>(obj);
|
||||
}
|
||||
|
||||
template<typename TBase>
|
||||
static constexpr bool isPolymorphic() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//this class is for convenience
|
||||
class PointerLinkingContext:
|
||||
public details_pointer::PointerLinkingContextSerialization,
|
||||
public details_pointer::PointerLinkingContextDeserialization {
|
||||
template<typename RTTI>
|
||||
using PointerOwnerBase = pointer_utils::PointerObjectExtensionBase<
|
||||
pointer_details::PtrOwnerManager, PolymorphicContext, RTTI>;
|
||||
|
||||
using PointerOwner = PointerOwnerBase<StandardRTTI>;
|
||||
|
||||
using PointerObserver = pointer_utils::PointerObjectExtensionBase<
|
||||
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> {
|
||||
public:
|
||||
bool isValid() {
|
||||
return isPointerSerializationValid() && isPointerDeserializationValid();
|
||||
}
|
||||
};
|
||||
|
||||
class PointerOwner {
|
||||
public:
|
||||
explicit PointerOwner(PointerType ptrType = PointerType::Nullable):_ptrType{ptrType} {}
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &w, const T &obj, Fnc &&) const {
|
||||
auto& ctx = details_pointer::getLinkingContext(ser);
|
||||
if (obj) {
|
||||
auto& ptrInfo = ctx.getInfoByPtr(obj, PointerOwnershipType::Owner);
|
||||
details::writeSize(w, ptrInfo.id);
|
||||
details_pointer::RawPointerManager::serializeObject(w, ser, obj);
|
||||
} else {
|
||||
assert(_ptrType == PointerType::Nullable);
|
||||
details::writeSize(w, 0);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &des, Reader &r, T &obj, Fnc &&) const {
|
||||
details_pointer::getLinkingContext(des);
|
||||
size_t id{};
|
||||
details::readSize(r, id, std::numeric_limits<size_t>::max());
|
||||
if (id) {
|
||||
auto& ctx = details_pointer::getLinkingContext(des);
|
||||
auto& ptrInfo = ctx.getInfoById(id, PointerOwnershipType::Owner);
|
||||
details_pointer::RawPointerManager::deserializeObject(r, des, obj);
|
||||
ptrInfo.processOwner(obj);
|
||||
} else {
|
||||
if (_ptrType == PointerType::Nullable)
|
||||
details_pointer::RawPointerManager::destroyObject(obj);
|
||||
else
|
||||
r.setError(ReaderError::InvalidPointer);
|
||||
}
|
||||
}
|
||||
private:
|
||||
PointerType _ptrType;
|
||||
};
|
||||
|
||||
class PointerObserver {
|
||||
public:
|
||||
|
||||
explicit PointerObserver(PointerType ptrType = PointerType::Nullable):_ptrType{ptrType} {}
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &w, const T &obj, Fnc &&) const {
|
||||
auto& ctx = details_pointer::getLinkingContext(ser);
|
||||
if (obj) {
|
||||
details::writeSize(w, ctx.getInfoByPtr(obj, PointerOwnershipType::Observer).id);
|
||||
} else {
|
||||
assert(_ptrType == PointerType::Nullable);
|
||||
details::writeSize(w, 0);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &des, Reader &r, T &obj, Fnc &&) const {
|
||||
size_t id{};
|
||||
details::readSize(r, id, std::numeric_limits<size_t>::max());
|
||||
if (id) {
|
||||
auto& ctx = details_pointer::getLinkingContext(des);
|
||||
ctx.getInfoById(id, PointerOwnershipType::Observer).processObserver(reinterpret_cast<void*&>(obj));
|
||||
} else {
|
||||
if (_ptrType == PointerType::Nullable)
|
||||
obj = nullptr;
|
||||
else
|
||||
r.setError(ReaderError::InvalidPointer);
|
||||
}
|
||||
}
|
||||
private:
|
||||
PointerType _ptrType;
|
||||
};
|
||||
|
||||
class ReferencedByPointer {
|
||||
public:
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &w, const T &obj, Fnc && fnc) const {
|
||||
auto& ctx = details_pointer::getLinkingContext(ser);
|
||||
details::writeSize(w, ctx.getInfoByPtr(&obj, PointerOwnershipType::Owner).id);
|
||||
fnc(const_cast<T&>(obj));
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &des, Reader &r, T &obj, Fnc && fnc) const {
|
||||
size_t id{};
|
||||
details::readSize(r, id, std::numeric_limits<size_t>::max());
|
||||
if (id) {
|
||||
auto& ctx = details_pointer::getLinkingContext(des);
|
||||
fnc(obj);
|
||||
ctx.getInfoById(id, PointerOwnershipType::Owner).processOwner(&obj);
|
||||
} else {
|
||||
//cannot be null for references
|
||||
r.setError(ReaderError::InvalidPointer);
|
||||
}
|
||||
}
|
||||
ReferencedByPointer() : pointer_utils::PointerObjectExtensionBase<
|
||||
pointer_details::NonPtrManager, PolymorphicContext, pointer_details::NoRTTI>(
|
||||
PointerType::NotNull) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace traits {
|
||||
|
||||
template<typename T>
|
||||
struct ExtensionTraits<ext::PointerOwner, T*> {
|
||||
template<typename T, typename RTTI>
|
||||
struct ExtensionTraits<ext::PointerOwnerBase<RTTI>, T*> {
|
||||
using TValue = T;
|
||||
static constexpr bool SupportValueOverload = true;
|
||||
static constexpr bool SupportObjectOverload = true;
|
||||
//pointers cannot have lamba overload, when polymorphism support will be added
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
//if underlying type is not polymorphic, then we can enable lambda syntax
|
||||
static constexpr bool SupportLambdaOverload = !RTTI::template isPolymorphic<TValue>();
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct ExtensionTraits<ext::PointerObserver, T*> {
|
||||
//although pointer observer doesn't serialize anything, but we still add value overload support to be consistent with pointer owners
|
||||
//observer only writes/reads pointer id from pointer linking context
|
||||
using TValue = T;
|
||||
static constexpr bool SupportValueOverload = true;
|
||||
static constexpr bool SupportObjectOverload = true;
|
||||
//pointers cannot have lamba overload, when polymorphism support will be added
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
};
|
||||
|
||||
@@ -412,5 +217,4 @@ namespace bitsery {
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif //BITSERY_EXT_POINTER_H
|
||||
|
||||
94
include/bitsery/ext/std_chrono.h
Normal file
94
include/bitsery/ext/std_chrono.h
Normal file
@@ -0,0 +1,94 @@
|
||||
//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_EXT_STD_CHRONO_H
|
||||
#define BITSERY_EXT_STD_CHRONO_H
|
||||
|
||||
#include "../traits/core/traits.h"
|
||||
#include <chrono>
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
class StdDuration {
|
||||
public:
|
||||
|
||||
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(ser, res);
|
||||
}
|
||||
|
||||
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(des, res);
|
||||
obj = std::chrono::duration<T, Period>{res};
|
||||
}
|
||||
};
|
||||
|
||||
class StdTimePoint {
|
||||
public:
|
||||
|
||||
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(ser, res);
|
||||
}
|
||||
|
||||
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(des, res);
|
||||
auto dur = std::chrono::duration<T, Period>{res};
|
||||
obj = std::chrono::time_point<Clock, std::chrono::duration<T, Period>>{dur};
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace traits {
|
||||
template<typename Rep, typename Period>
|
||||
struct ExtensionTraits<ext::StdDuration, std::chrono::duration<Rep, Period>> {
|
||||
using TValue = Rep;
|
||||
static constexpr bool SupportValueOverload = true;
|
||||
static constexpr bool SupportObjectOverload = false;
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
};
|
||||
|
||||
template<typename Clock, typename Rep, typename Period>
|
||||
struct ExtensionTraits<ext::StdTimePoint,
|
||||
std::chrono::time_point<Clock, std::chrono::duration<Rep, Period>>> {
|
||||
using TValue = Rep;
|
||||
static constexpr bool SupportValueOverload = true;
|
||||
static constexpr bool SupportObjectOverload = false;
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif //BITSERY_EXT_STD_CHRONO_H
|
||||
@@ -24,7 +24,10 @@
|
||||
#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>
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
@@ -34,36 +37,50 @@ 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);
|
||||
auto hint = obj.begin();
|
||||
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) {
|
||||
TKey key;
|
||||
TValue value;
|
||||
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));
|
||||
}
|
||||
}
|
||||
private:
|
||||
template <typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
|
||||
void reserve(std::unordered_map<Key, T, Hash, KeyEqual, Allocator>& obj, size_t size) const {
|
||||
obj.reserve(size);
|
||||
}
|
||||
template <typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
|
||||
void reserve(std::unordered_multimap<Key, T, Hash, KeyEqual, Allocator>& obj, size_t size) const {
|
||||
obj.reserve(size);
|
||||
}
|
||||
template <typename T>
|
||||
void reserve(T& , size_t ) const {
|
||||
//for ordered container do nothing
|
||||
}
|
||||
size_t _maxSize;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -24,23 +24,13 @@
|
||||
#ifndef BITSERY_EXT_STD_OPTIONAL_H
|
||||
#define BITSERY_EXT_STD_OPTIONAL_H
|
||||
|
||||
|
||||
//this module do not include optional, but expects it to be declared in std::optional
|
||||
//if you're using experimental optional from <experimental/optional>
|
||||
//add it in std namespace like this:
|
||||
//namespace std {
|
||||
// template <typename T>
|
||||
// using optional = experimental::optional<T>;
|
||||
//}
|
||||
#include <type_traits>
|
||||
#include "../traits/core/traits.h"
|
||||
#include "../details/serialization_common.h"
|
||||
#include <optional>
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
template<typename T>
|
||||
using std_optional = ::std::optional<T>;
|
||||
|
||||
class StdOptional {
|
||||
public:
|
||||
|
||||
@@ -49,38 +39,27 @@ namespace bitsery {
|
||||
* @param alignBeforeData only makes sense when bit-packing enabled, by default aligns after writing/reading bool state of optional
|
||||
*/
|
||||
explicit StdOptional(bool alignBeforeData=true):_alignBeforeData{alignBeforeData} {}
|
||||
template<typename T>
|
||||
constexpr void assertType() const {
|
||||
using TOpt = typename std::remove_cv<T>::type;
|
||||
using TVal = typename TOpt::value_type;
|
||||
static_assert(std::is_same<TOpt, std_optional<TVal>>(), "");
|
||||
static_assert(std::is_default_constructible<TVal>::value, "");
|
||||
};
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &, const T &obj, Fnc &&fnc) const {
|
||||
assertType<T>();
|
||||
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<typename T::value_type & >(*obj));
|
||||
fnc(ser, const_cast<T&>(*obj));
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &des, Reader &, T &obj, Fnc &&fnc) const {
|
||||
assertType<T>();
|
||||
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) {
|
||||
typename T::value_type tmp{};
|
||||
fnc(tmp);
|
||||
obj = tmp;
|
||||
obj = ::bitsery::Access::create<T>();
|
||||
fnc(des, *obj);
|
||||
} else {
|
||||
//experimental optional doesnt have .reset method
|
||||
obj = T{};
|
||||
obj = std::nullopt;
|
||||
}
|
||||
}
|
||||
private:
|
||||
@@ -90,8 +69,8 @@ namespace bitsery {
|
||||
|
||||
namespace traits {
|
||||
template<typename T>
|
||||
struct ExtensionTraits<ext::StdOptional, T> {
|
||||
using TValue = typename T::value_type;
|
||||
struct ExtensionTraits<ext::StdOptional, std::optional<T>> {
|
||||
using TValue = T;
|
||||
static constexpr bool SupportValueOverload = true;
|
||||
static constexpr bool SupportObjectOverload = true;
|
||||
static constexpr bool SupportLambdaOverload = true;
|
||||
|
||||
@@ -51,15 +51,15 @@ namespace bitsery {
|
||||
}
|
||||
};
|
||||
//inherit from queue so we could take underlying container
|
||||
template <typename T, typename C>
|
||||
struct PriorityQueueCnt : public std::priority_queue<T, C>
|
||||
template <typename T, typename Seq, typename Cmp>
|
||||
struct PriorityQueueCnt : public std::priority_queue<T, Seq, Cmp>
|
||||
{
|
||||
static const C& getContainer(const std::priority_queue<T, C>& s )
|
||||
static const Seq& getContainer(const std::priority_queue<T, Seq, Cmp>& s )
|
||||
{
|
||||
//get address of underlying container
|
||||
return s.*(&PriorityQueueCnt::c);
|
||||
}
|
||||
static C& getContainer(std::priority_queue<T, C>& s )
|
||||
static Seq& getContainer(std::priority_queue<T, Seq, Cmp>& s )
|
||||
{
|
||||
//get address of underlying container
|
||||
return s.*(&PriorityQueueCnt::c);
|
||||
@@ -71,25 +71,25 @@ 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 Fnc>
|
||||
void serialize(Ser &ser, Writer &, const std::priority_queue<T,C> &obj, Fnc &&fnc) const {
|
||||
ser.container(PriorityQueueCnt<T,C>::getContainer(obj), _maxSize, std::forward<Fnc>(fnc));
|
||||
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 Fnc>
|
||||
void deserialize(Des &des, Reader &, std::priority_queue<T,C> &obj, Fnc &&fnc) const {
|
||||
des.container(PriorityQueueCnt<T,C>::getContainer(obj), _maxSize, std::forward<Fnc>(fnc));
|
||||
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,10 +24,10 @@
|
||||
#define BITSERY_EXT_STD_SET_H
|
||||
|
||||
#include <cassert>
|
||||
#include "../details/adapter_utils.h"
|
||||
//we need this, so we could
|
||||
#include "../details/adapter_common.h"
|
||||
#include "../details/serialization_common.h"
|
||||
//we need this, so we could reserve for non ordered set
|
||||
#include <unordered_set>
|
||||
#include "../traits/core/traits.h"
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
@@ -37,43 +37,45 @@ 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);
|
||||
auto hint = obj.begin();
|
||||
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) {
|
||||
TKey key;
|
||||
fnc(key);
|
||||
auto key = bitsery::Access::create<TKey>();
|
||||
fnc(des, key);
|
||||
hint = obj.emplace_hint(hint, std::move(key));
|
||||
}
|
||||
}
|
||||
private:
|
||||
|
||||
template <typename T>
|
||||
void reserve(std::unordered_set<T>& obj, size_t size) const {
|
||||
template <typename Key, typename Hash, typename KeyEqual, typename Allocator>
|
||||
void reserve(std::unordered_set<Key, Hash, KeyEqual, Allocator>& obj, size_t size) const {
|
||||
obj.reserve(size);
|
||||
}
|
||||
template <typename T>
|
||||
void reserve(std::unordered_multiset<T>& obj, size_t size) const {
|
||||
template <typename Key, typename Hash, typename KeyEqual, typename Allocator>
|
||||
void reserve(std::unordered_multiset<Key, Hash, KeyEqual, Allocator>& obj, size_t size) const {
|
||||
obj.reserve(size);
|
||||
}
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
void reserve(T& , size_t ) const {
|
||||
//for ordered container do nothing
|
||||
|
||||
198
include/bitsery/ext/std_smart_ptr.h
Normal file
198
include/bitsery/ext/std_smart_ptr.h
Normal file
@@ -0,0 +1,198 @@
|
||||
//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_STD_SMART_PTR_H
|
||||
#define BITSERY_EXT_STD_SMART_PTR_H
|
||||
|
||||
#include <cassert>
|
||||
#include "../traits/core/traits.h"
|
||||
#include "utils/pointer_utils.h"
|
||||
#include "utils/polymorphism_utils.h"
|
||||
#include "utils/rtti_utils.h"
|
||||
#include <memory>
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
namespace smart_ptr_details {
|
||||
|
||||
//further code is for managing shared ownership
|
||||
//do not nest this type in pointer manager class itself, because it will be different type for different T
|
||||
struct SharedPtrSharedState : pointer_utils::PointerSharedStateBase {
|
||||
std::shared_ptr<void> obj{};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct SmartPtrOwnerManager {
|
||||
|
||||
using TElement = typename T::element_type;
|
||||
|
||||
template<typename TDeleter>
|
||||
static TElement* getPtr(std::unique_ptr<TElement, TDeleter>& obj) {
|
||||
return obj.get();
|
||||
}
|
||||
|
||||
static TElement* getPtr(std::shared_ptr<TElement>& obj) {
|
||||
return obj.get();
|
||||
}
|
||||
|
||||
static TElement* getPtr(std::weak_ptr<TElement>& obj) {
|
||||
if (auto ptr = obj.lock())
|
||||
return ptr.get();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static constexpr PointerOwnershipType getOwnership() {
|
||||
return ::bitsery::details::IsSpecializationOf<T, std::unique_ptr>::value
|
||||
? PointerOwnershipType::Owner
|
||||
: std::is_same<std::shared_ptr<TElement>, T>::value
|
||||
? PointerOwnershipType::SharedOwner
|
||||
: PointerOwnershipType::SharedObserver;
|
||||
}
|
||||
|
||||
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 destroyPolymorphic(std::shared_ptr<TElement>& obj, MemResourceBase*,
|
||||
const std::shared_ptr<PolymorphicHandlerBase>&) {
|
||||
obj.reset();
|
||||
}
|
||||
|
||||
static void destroy(std::weak_ptr<TElement>& obj, MemResourceBase*, size_t) {
|
||||
obj.reset();
|
||||
}
|
||||
|
||||
static void destroyPolymorphic(std::weak_ptr<TElement>& obj, MemResourceBase*,
|
||||
const std::shared_ptr<PolymorphicHandlerBase>&) {
|
||||
obj.reset();
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
template<typename RTTI>
|
||||
using StdSmartPtrBase = pointer_utils::PointerObjectExtensionBase<
|
||||
smart_ptr_details::SmartPtrOwnerManager, PolymorphicContext, RTTI>;
|
||||
|
||||
//helper type for convienience
|
||||
using StdSmartPtr = StdSmartPtrBase<StandardRTTI>;
|
||||
|
||||
}
|
||||
|
||||
namespace traits {
|
||||
|
||||
template<typename T, typename RTTI>
|
||||
struct ExtensionTraits<ext::StdSmartPtrBase<RTTI>, T> {
|
||||
using TValue = typename T::element_type;
|
||||
static constexpr bool SupportValueOverload = true;
|
||||
static constexpr bool SupportObjectOverload = true;
|
||||
//if underlying type is not polymorphic, then we can enable lambda syntax
|
||||
static constexpr bool SupportLambdaOverload = !RTTI::template isPolymorphic<TValue>();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_EXT_STD_SMART_PTR_H
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -67,9 +67,9 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
namespace traits {
|
||||
template<typename T>
|
||||
struct ExtensionTraits<ext::StdStack, T> {
|
||||
using TValue = typename T::value_type;
|
||||
template<typename T, typename Seq>
|
||||
struct ExtensionTraits<ext::StdStack, std::stack<T, Seq>> {
|
||||
using TValue = T;
|
||||
static constexpr bool SupportValueOverload = true;
|
||||
static constexpr bool SupportObjectOverload = true;
|
||||
static constexpr bool SupportLambdaOverload = true;
|
||||
|
||||
81
include/bitsery/ext/std_tuple.h
Normal file
81
include/bitsery/ext/std_tuple.h
Normal file
@@ -0,0 +1,81 @@
|
||||
//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_EXT_STD_TUPLE_H
|
||||
#define BITSERY_EXT_STD_TUPLE_H
|
||||
|
||||
|
||||
#include "utils/composite_type_overloads.h"
|
||||
#include "../traits/core/traits.h"
|
||||
#include <tuple>
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
template<typename ...Overloads>
|
||||
class StdTuple : public details::CompositeTypeOverloadsUtils<std::tuple, Overloads...> {
|
||||
public:
|
||||
|
||||
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 Fnc, typename ...Ts>
|
||||
void deserialize(Des& des, std::tuple<Ts...>& obj, Fnc&&) const {
|
||||
serializeAll(des, obj);
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename S, typename ...Ts>
|
||||
void serializeAll(S& s, std::tuple<Ts...>& obj) const {
|
||||
this->execAll(obj, [this, &s](auto& data, auto index) {
|
||||
constexpr size_t Index = decltype(index)::value;
|
||||
this->serializeType(s, std::get<Index>(data));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// deduction guide
|
||||
template<typename ...Overloads>
|
||||
StdTuple(Overloads...) -> StdTuple<Overloads...>;
|
||||
}
|
||||
|
||||
namespace traits {
|
||||
|
||||
template<typename Tuple, typename ... Overloads>
|
||||
struct ExtensionTraits<ext::StdTuple<Overloads...>, Tuple> {
|
||||
static_assert(bitsery::details::IsSpecializationOf<Tuple, std::tuple>::value,
|
||||
"StdTuple only works with std::tuple");
|
||||
using TValue = void;
|
||||
static constexpr bool SupportValueOverload = false;
|
||||
static constexpr bool SupportObjectOverload = true;
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif //BITSERY_EXT_STD_TUPLE_H
|
||||
91
include/bitsery/ext/std_variant.h
Normal file
91
include/bitsery/ext/std_variant.h
Normal file
@@ -0,0 +1,91 @@
|
||||
//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_EXT_STD_VARIANT_H
|
||||
#define BITSERY_EXT_STD_VARIANT_H
|
||||
|
||||
|
||||
#include "utils/composite_type_overloads.h"
|
||||
#include "../traits/core/traits.h"
|
||||
#include <variant>
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
template<typename ...Overloads>
|
||||
class StdVariant : public details::CompositeTypeOverloadsUtils<std::variant, Overloads...> {
|
||||
public:
|
||||
|
||||
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(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 Fnc, typename ...Ts>
|
||||
void deserialize(Des& des, std::variant<Ts...>& obj, Fnc&&) const {
|
||||
size_t index{};
|
||||
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;
|
||||
TElem item = ::bitsery::Access::create<TElem>();
|
||||
this->serializeType(des, item);
|
||||
data = std::variant<Ts...>(std::in_place_index_t<Index>{}, std::move(item));
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// deduction guide
|
||||
template<typename ...Overloads>
|
||||
StdVariant(Overloads...) -> StdVariant<Overloads...>;
|
||||
}
|
||||
|
||||
//defines empty fuction, that handles monostate
|
||||
template <typename S>
|
||||
void serialize(S& , std::monostate&) {}
|
||||
|
||||
namespace traits {
|
||||
|
||||
template<typename Variant, typename ... Overloads>
|
||||
struct ExtensionTraits<ext::StdVariant<Overloads...>, Variant> {
|
||||
static_assert(bitsery::details::IsSpecializationOf<Variant, std::variant>::value,
|
||||
"StdVariant only works with std::variant");
|
||||
using TValue = void;
|
||||
static constexpr bool SupportValueOverload = false;
|
||||
static constexpr bool SupportObjectOverload = true;
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif //BITSERY_EXT_STD_VARIANT_H
|
||||
136
include/bitsery/ext/utils/composite_type_overloads.h
Normal file
136
include/bitsery/ext/utils/composite_type_overloads.h
Normal file
@@ -0,0 +1,136 @@
|
||||
//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_EXT_COMPOSITE_TYPE_OVERLOADS_H
|
||||
#define BITSERY_EXT_COMPOSITE_TYPE_OVERLOADS_H
|
||||
|
||||
#include "../../details/serialization_common.h"
|
||||
#include <functional>
|
||||
|
||||
#if __cplusplus < 201703L
|
||||
#error these utils requires c++17
|
||||
// in theory, it could be implemented using C++11
|
||||
// but without class template argument deduction guides that would be very inconvenient to use
|
||||
// these are very helpul for sum types (e.g. std::variant),
|
||||
// but for product types (e.g. std::tuple) you can you can easily do it your self with lambda, without extension
|
||||
#endif
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
namespace ext {
|
||||
// might be usable, when you want to have one overload set for different composite types,
|
||||
// e.g. variant, tuple and pair
|
||||
template<class... Ts>
|
||||
struct CompositeTypeOverloads : Ts ... {
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
template<typename ...Overloads>
|
||||
CompositeTypeOverloads(Overloads...) -> CompositeTypeOverloads<Overloads...>;
|
||||
|
||||
// convenient way to invoke s.value<N>, shorter than specifying a lambda
|
||||
template<typename T, size_t N>
|
||||
struct OverloadValue {
|
||||
template <typename S>
|
||||
void operator()(S& s, T& v) const {
|
||||
s.template value<N>(v);
|
||||
}
|
||||
};
|
||||
|
||||
// convenient way to invoke other extension using value or object overloads
|
||||
// there is no reason to write OverloadExtLambda,
|
||||
// because you'll need to specify lambda type, which is very inconvenient and it will be much
|
||||
// easier to simple write a lambda with extension inside it,
|
||||
// in order to implement it in a convenient way, i need a way to deduce only last template parameter (lambda type)
|
||||
// but this is not possible with deduction guides at the moment
|
||||
|
||||
template<typename T, size_t N, typename Ext>
|
||||
struct OverloadExtValue : public Ext {
|
||||
template <typename S>
|
||||
void operator()(S& s, T& v) const {
|
||||
s.template ext<N>(v, static_cast<const Ext&>(*this));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename Ext>
|
||||
struct OverloadExtObject : public Ext {
|
||||
template <typename S>
|
||||
void operator()(S& s, T& v) const {
|
||||
s.ext(v, static_cast<const Ext&>(*this));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace details {
|
||||
|
||||
template<template<typename ...> typename CompositeType, typename ...Overloads>
|
||||
class CompositeTypeOverloadsUtils : public ext::CompositeTypeOverloads<Overloads...> {
|
||||
protected:
|
||||
// converts run-time index to compile-time index,
|
||||
// by calling lambda with std::integral_constant<size_t, INDEX>
|
||||
template<typename Fnc, typename ... Ts>
|
||||
void execIndex(size_t index, CompositeType<Ts...>& obj, Fnc&& fnc) const {
|
||||
execIndexImpl(index, obj, std::forward<Fnc>(fnc), std::index_sequence_for<Ts...>{});
|
||||
}
|
||||
|
||||
// call lambda for all indexes in composite type
|
||||
template<typename Fnc, typename ... Ts>
|
||||
void execAll(CompositeType<Ts...>& obj, Fnc&& fnc) const {
|
||||
execAllImpl(obj, std::forward<Fnc>(fnc), std::index_sequence_for<Ts...>{});
|
||||
}
|
||||
|
||||
// serialize a type, by using overload first
|
||||
template<typename S, typename T>
|
||||
void serializeType(S& s, T& v) const {
|
||||
// first check if overload exists, otherwise try to call serialize method
|
||||
if constexpr (hasOverload<S, T>()) {
|
||||
std::invoke(*this, s, v);
|
||||
} else {
|
||||
static_assert(details::SerializeFunction<S, T>::isDefined(),
|
||||
"Please define overload or 'serialize' function for your type.");
|
||||
s.object(v);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<typename S, typename T>
|
||||
static constexpr bool hasOverload() {
|
||||
return std::is_invocable<ext::CompositeTypeOverloads<Overloads...>,
|
||||
std::add_lvalue_reference_t<S>, std::add_lvalue_reference_t<T>>::value;
|
||||
}
|
||||
|
||||
template<typename Variant, typename Fnc, size_t ...Is>
|
||||
void execIndexImpl(size_t index, Variant& obj, Fnc&& fnc, std::index_sequence<Is...>) const {
|
||||
((index == Is ? fnc(obj, std::integral_constant<size_t, Is>{}), 0 : 0), ...);
|
||||
}
|
||||
|
||||
template<typename Variant, typename Fnc, size_t ...Is>
|
||||
void execAllImpl(Variant& obj, Fnc&& fnc, std::index_sequence<Is...>) const {
|
||||
(fnc(obj, std::integral_constant<size_t, Is>{}), ...);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_EXT_COMPOSITE_TYPE_OVERLOADS_H
|
||||
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 : 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
|
||||
487
include/bitsery/ext/utils/pointer_utils.h
Normal file
487
include/bitsery/ext/utils/pointer_utils.h
Normal file
@@ -0,0 +1,487 @@
|
||||
//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_POINTER_UTILS_H
|
||||
#define BITSERY_POINTER_UTILS_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include "polymorphism_utils.h"
|
||||
#include "../../details/adapter_common.h"
|
||||
#include "../../details/serialization_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
//change name
|
||||
enum class PointerType : uint8_t {
|
||||
Nullable,
|
||||
NotNull
|
||||
};
|
||||
|
||||
// Observer - not responsible for pointer lifetime management.
|
||||
// Owner - only ONE owner is responsible for this pointers creation/destruction
|
||||
// SharedOwner, SharedObserver - MANY shared owners is responsible for pointer creation/destruction
|
||||
// requires additional context to manage shared owners themselves.
|
||||
// SharedOwner actually manages life time e.g. std::shared_ptr
|
||||
// SharedObserver do not manage life time of the pointer, but can observe shared state .e.. std::weak_ptr
|
||||
// and differently from Observer, creates new object if necessary and saves to shared state
|
||||
enum class PointerOwnershipType : uint8_t {
|
||||
Observer,
|
||||
Owner,
|
||||
SharedOwner,
|
||||
SharedObserver
|
||||
};
|
||||
|
||||
namespace pointer_utils {
|
||||
|
||||
//this class is used to store context for shared ptr owners
|
||||
struct PointerSharedStateBase {
|
||||
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;
|
||||
};
|
||||
|
||||
//PLC info is internal classes for serializer, and deserializer
|
||||
struct PLCInfo {
|
||||
explicit PLCInfo(PointerOwnershipType ownershipType_)
|
||||
: ownershipType{ownershipType_},
|
||||
isSharedProcessed{false} {};
|
||||
PointerOwnershipType ownershipType;
|
||||
bool isSharedProcessed;
|
||||
|
||||
void update(PointerOwnershipType ptrType) {
|
||||
//do nothing for observer
|
||||
if (ptrType == PointerOwnershipType::Observer)
|
||||
return;
|
||||
if (ownershipType == PointerOwnershipType::Observer) {
|
||||
//set ownership type
|
||||
ownershipType = ptrType;
|
||||
return;
|
||||
}
|
||||
//only shared ownership can get here multiple times
|
||||
assert(ptrType == PointerOwnershipType::SharedOwner ||
|
||||
ptrType == PointerOwnershipType::SharedObserver);
|
||||
//check if need to update to SharedOwner
|
||||
if (ptrType == PointerOwnershipType::SharedOwner)
|
||||
ownershipType = ptrType;
|
||||
//mark that object already processed, so we do not serialize/deserialize duplicate objects
|
||||
isSharedProcessed = true;
|
||||
}
|
||||
};
|
||||
|
||||
struct PLCInfoSerializer : PLCInfo {
|
||||
PLCInfoSerializer(size_t id_, PointerOwnershipType ownershipType_)
|
||||
: PLCInfo(ownershipType_), id{id_} {}
|
||||
|
||||
size_t id;
|
||||
};
|
||||
|
||||
struct PLCInfoDeserializer : PLCInfo {
|
||||
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) {
|
||||
ownerPtr = ptr;
|
||||
assert(ownershipType != PointerOwnershipType::Observer);
|
||||
for (auto& o:observersList)
|
||||
o.get() = ptr;
|
||||
observersList.clear();
|
||||
observersList.shrink_to_fit();
|
||||
}
|
||||
|
||||
void processObserver(void* (& ptr)) {
|
||||
if (ownerPtr) {
|
||||
ptr = ownerPtr;
|
||||
} else {
|
||||
observersList.emplace_back(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
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(MemResourceBase* memResource = nullptr)
|
||||
: _currId{0},
|
||||
_ptrMap{StdPolyAlloc<std::pair<const void* const, PLCInfoSerializer>>{memResource}} {}
|
||||
|
||||
PointerLinkingContextSerialization(const PointerLinkingContextSerialization&) = delete;
|
||||
|
||||
PointerLinkingContextSerialization& operator=(const PointerLinkingContextSerialization&) = delete;
|
||||
|
||||
PointerLinkingContextSerialization(PointerLinkingContextSerialization&&) = default;
|
||||
|
||||
PointerLinkingContextSerialization& operator=(PointerLinkingContextSerialization&&) = default;
|
||||
|
||||
~PointerLinkingContextSerialization() = default;
|
||||
|
||||
const PLCInfoSerializer& getInfoByPtr(const void* ptr, PointerOwnershipType ptrType) {
|
||||
auto res = _ptrMap.emplace(ptr, PLCInfoSerializer{_currId + 1u, ptrType});
|
||||
auto& ptrInfo = res.first->second;
|
||||
if (res.second) {
|
||||
++_currId;
|
||||
return ptrInfo;
|
||||
}
|
||||
ptrInfo.update(ptrType);
|
||||
return ptrInfo;
|
||||
}
|
||||
|
||||
//valid, when all pointers have owners.
|
||||
//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) {
|
||||
return p.second.ownershipType == PointerOwnershipType::SharedOwner ||
|
||||
p.second.ownershipType == PointerOwnershipType::Owner;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
size_t _currId;
|
||||
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(MemResourceBase* memResource = nullptr)
|
||||
: _memResource{memResource},
|
||||
_idMap{StdPolyAlloc<std::pair<const size_t, PLCInfoDeserializer>>{memResource}} {}
|
||||
|
||||
PointerLinkingContextDeserialization(const PointerLinkingContextDeserialization&) = delete;
|
||||
|
||||
PointerLinkingContextDeserialization& operator=(const PointerLinkingContextDeserialization&) = delete;
|
||||
|
||||
PointerLinkingContextDeserialization(PointerLinkingContextDeserialization&&) = default;
|
||||
|
||||
PointerLinkingContextDeserialization& operator=(PointerLinkingContextDeserialization&&) = default;
|
||||
|
||||
~PointerLinkingContextDeserialization() = default;
|
||||
|
||||
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)
|
||||
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) {
|
||||
return p.second.ownershipType == PointerOwnershipType::SharedOwner ||
|
||||
p.second.ownershipType == PointerOwnershipType::Owner;
|
||||
});
|
||||
}
|
||||
|
||||
MemResourceBase* getMemResource() noexcept {
|
||||
return _memResource;
|
||||
}
|
||||
|
||||
void setMemResource(MemResourceBase* resource) noexcept {
|
||||
_memResource = resource;
|
||||
}
|
||||
|
||||
private:
|
||||
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>
|
||||
class PointerObjectExtensionBase {
|
||||
public:
|
||||
|
||||
// helper types
|
||||
template<typename T>
|
||||
struct IsPolymorphic : std::integral_constant<bool,
|
||||
RTTI::template isPolymorphic<typename TPtrManager<T>::TElement>()> {
|
||||
};
|
||||
|
||||
template<PointerOwnershipType Value>
|
||||
using OwnershipType = std::integral_constant<PointerOwnershipType, Value>;
|
||||
|
||||
|
||||
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<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), IsPolymorphic<T>{});
|
||||
}
|
||||
} else {
|
||||
assert(_ptrType == PointerType::Nullable);
|
||||
details::writeSize(ser.adapter(), 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc>
|
||||
void deserialize(Des& des, T& obj, Fnc&& fnc) const {
|
||||
size_t id{};
|
||||
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& 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) {
|
||||
if (auto ptr = TPtrManager<T>::getPtr(obj)) {
|
||||
destroyPtr(memResource, des, obj, IsPolymorphic<T>{});
|
||||
};
|
||||
} else
|
||||
des.adapter().error(ReaderError::InvalidPointer);
|
||||
}
|
||||
if (_resource && _resourcePropagate) {
|
||||
ctx.setMemResource(prevResource);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
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 {
|
||||
// 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>
|
||||
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>
|
||||
void serializeImpl(Ser& ser, TPtr& ptr, Fnc&& fnc, std::false_type) const {
|
||||
fnc(ser, *ptr);
|
||||
}
|
||||
|
||||
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>
|
||||
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(des, *ptr);
|
||||
} else {
|
||||
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>
|
||||
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 (!ptrInfo.sharedState)
|
||||
TPtrManager<T>::saveToSharedState(createAndGetSharedStateObj<T>(ptrInfo), obj);
|
||||
}
|
||||
TPtrManager<T>::loadFromSharedState(getSharedStateObj<T>(ptrInfo), obj);
|
||||
ptrInfo.processOwner(TPtrManager<T>::getPtr(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 {
|
||||
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(getSharedStateObj<T>(ptrInfo), obj);
|
||||
ptrInfo.processOwner(TPtrManager<T>::getPtr(obj));
|
||||
}
|
||||
|
||||
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 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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_POINTER_UTILS_H
|
||||
287
include/bitsery/ext/utils/polymorphism_utils.h
Normal file
287
include/bitsery/ext/utils/polymorphism_utils.h
Normal file
@@ -0,0 +1,287 @@
|
||||
//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_POLYMORPHISM_UTILS_H
|
||||
#define BITSERY_EXT_POLYMORPHISM_UTILS_H
|
||||
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include "memory_resource.h"
|
||||
#include "../../details/adapter_common.h"
|
||||
#include "../../details/serialization_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
namespace ext {
|
||||
|
||||
//helper type, that contains list of types
|
||||
template<typename ...>
|
||||
struct PolymorphicClassesList {
|
||||
};
|
||||
|
||||
//specialize for your base class by deriving from PolymorphicDerivedClasses with list of derivatives that DIRECTLY inherits from your base class.
|
||||
//e.g.
|
||||
// template <> PolymorphicBaseClass<Animal>: PolymorphicDerivedClasses<Dog, Cat>{};
|
||||
// template <> PolymorphicBaseClass<Dog>: PolymorphicDerivedClasses<Bulldog, GoldenRetriever> {};
|
||||
// IMPORTANT !!!
|
||||
// although you can add all derivates to same base like this:
|
||||
// template <> PolymorphicBaseClass<Animal>:PolymorphicDerivedClasses<Dog, Cat, Bulldog, GoldenRetriever>{};
|
||||
// it will not work when you try to serialize Dog*, because it will not find Bulldog and GoldenRetriever
|
||||
template<typename TBase>
|
||||
struct PolymorphicBaseClass {
|
||||
using Childs = PolymorphicClassesList<>;
|
||||
};
|
||||
|
||||
//derive from this class when specifying childs for your base class, atleast one child must exists, hence T1
|
||||
//e.g.
|
||||
// template <> PolymorphicBaseClass<Animal>: PolymorphicDerivedClasses<Dog, Cat>{};
|
||||
template<typename T1, typename ... Tn>
|
||||
struct PolymorphicDerivedClasses {
|
||||
using Childs = PolymorphicClassesList<T1, Tn...>;
|
||||
};
|
||||
|
||||
class PolymorphicHandlerBase {
|
||||
public:
|
||||
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;
|
||||
};
|
||||
|
||||
template<typename RTTI, typename TSerializer, typename TBase, typename TDerived>
|
||||
class PolymorphicHandler : public PolymorphicHandlerBase {
|
||||
public:
|
||||
|
||||
void* create(const pointer_utils::PolyAllocWithTypeId& alloc) const final {
|
||||
return toBase(alloc.newObject<TDerived>(RTTI::template get<TDerived>()));
|
||||
}
|
||||
|
||||
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:
|
||||
|
||||
TDerived* fromBase(void* obj) const {
|
||||
return RTTI::template cast<TBase, TDerived>(static_cast<TBase*>(obj));
|
||||
}
|
||||
|
||||
TBase* toBase(void* obj) const {
|
||||
return RTTI::template cast<TDerived, TBase>(static_cast<TDerived*>(obj));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename RTTI>
|
||||
class PolymorphicContext {
|
||||
private:
|
||||
|
||||
struct BaseToDerivedKey {
|
||||
|
||||
std::size_t baseHash;
|
||||
std::size_t derivedHash;
|
||||
|
||||
bool operator==(const BaseToDerivedKey& other) const {
|
||||
return baseHash == other.baseHash && derivedHash == other.derivedHash;
|
||||
}
|
||||
};
|
||||
|
||||
struct BaseToDerivedKeyHashier {
|
||||
size_t operator()(const BaseToDerivedKey& key) const {
|
||||
return (key.baseHash + (key.baseHash << 6) + (key.derivedHash >> 2)) ^ key.derivedHash;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename TSerializer, template<typename> class THierarchy, typename TBase, typename TDerived>
|
||||
void add() {
|
||||
addToMap<TSerializer, TBase, TDerived>(std::is_abstract<TDerived>{});
|
||||
addChilds<TSerializer, THierarchy, TBase, TDerived>(typename THierarchy<TDerived>::Childs{});
|
||||
}
|
||||
|
||||
template<typename TSerializer, template<typename> class THierarchy, typename TBase, typename TDerived, typename T1, typename ... Tn>
|
||||
void addChilds(PolymorphicClassesList<T1, Tn...>) {
|
||||
static_assert(std::is_base_of<TDerived, T1>::value,
|
||||
"PolymorphicBaseClass<TBase> must derive a list of derived classes from TBase.");
|
||||
add<TSerializer, THierarchy, TBase, T1>();
|
||||
addChilds<TSerializer, THierarchy, TBase, TDerived>(PolymorphicClassesList<Tn...>{});
|
||||
//iterate through derived class hierarchy as well
|
||||
add<TSerializer, THierarchy, T1, T1>();
|
||||
}
|
||||
|
||||
template<typename TSerializer, template<typename> class THierarchy, typename TBase, typename TDerived>
|
||||
void addChilds(PolymorphicClassesList<>) {
|
||||
}
|
||||
|
||||
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>()};
|
||||
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>
|
||||
void addToMap(std::true_type) {
|
||||
//cannot add abstract class
|
||||
}
|
||||
|
||||
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, 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();
|
||||
}
|
||||
|
||||
// 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)
|
||||
// https://github.com/fraillt/bitsery/issues/9
|
||||
template<typename TSerializer, template<typename> class THierarchy = PolymorphicBaseClass, typename T1, typename ...Tn>
|
||||
void registerBasesList(PolymorphicClassesList<T1, Tn...>) {
|
||||
add<TSerializer, THierarchy, T1, T1>();
|
||||
registerBasesList<TSerializer, THierarchy>(PolymorphicClassesList<Tn...>{});
|
||||
}
|
||||
|
||||
template<typename TSerializer, template<typename> class THierarchy>
|
||||
void registerBasesList(PolymorphicClassesList<>) {
|
||||
}
|
||||
|
||||
// optional method, in case you want to construct base class hierarchy your self
|
||||
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");
|
||||
addToMap<TSerializer, TBase, TDerived>(std::false_type{});
|
||||
}
|
||||
|
||||
|
||||
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 derivedIndex = static_cast<size_t>(std::distance(vec.begin(), std::find(vec.begin(), vec.end(),
|
||||
key.derivedHash)));
|
||||
details::writeSize(ser.adapter(), derivedIndex);
|
||||
|
||||
//serialize
|
||||
it->second->process(&ser, &obj);
|
||||
}
|
||||
|
||||
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(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
|
||||
assert(baseToDerivedVecIt != _baseToDerivedArray.end());
|
||||
|
||||
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;
|
||||
//if object is null or different type, create new and assign it
|
||||
if (obj == nullptr || RTTI::template get<TBase>(*obj) != derivedHash) {
|
||||
if (obj) {
|
||||
destroyFnc(getPolymorphicHandler(*obj));
|
||||
}
|
||||
obj = createFnc(handler);
|
||||
}
|
||||
handler->process(&des, obj);
|
||||
} else
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_EXT_POLYMORPHISM_UTILS_H
|
||||
65
include/bitsery/ext/utils/rtti_utils.h
Normal file
65
include/bitsery/ext/utils/rtti_utils.h
Normal file
@@ -0,0 +1,65 @@
|
||||
//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_RTTI_UTILS_H
|
||||
#define BITSERY_RTTI_UTILS_H
|
||||
|
||||
#include <typeinfo>
|
||||
#include <type_traits>
|
||||
#include <cstddef>
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
struct StandardRTTI {
|
||||
|
||||
// static_assert(!std::is_pointer<TBase>::value &&
|
||||
// !std::is_const<TBase>::value &&
|
||||
// !std::is_volatile<TBase>::value, "");
|
||||
|
||||
template<typename TBase>
|
||||
static size_t get(TBase& obj) {
|
||||
return typeid(obj).hash_code();
|
||||
}
|
||||
|
||||
template<typename TBase>
|
||||
static constexpr size_t get() {
|
||||
return typeid(TBase).hash_code();
|
||||
}
|
||||
|
||||
template<typename TBase, typename TDerived>
|
||||
static constexpr TDerived* cast(TBase* obj) {
|
||||
static_assert(!std::is_pointer<TDerived>::value, "");
|
||||
return dynamic_cast<TDerived*>(obj);
|
||||
}
|
||||
|
||||
template<typename TBase>
|
||||
static constexpr bool isPolymorphic() {
|
||||
return std::is_polymorphic<TBase>::value;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_RTTI_UTILS_H
|
||||
@@ -96,7 +96,8 @@ namespace bitsery {
|
||||
constexpr RangeSpec(T minValue, T maxValue, T precision) :
|
||||
min{minValue},
|
||||
max{maxValue},
|
||||
bitsRequired{calcRequiredBits<details::SameSizeUnsigned<T>>({}, ((max - min) / precision))} {
|
||||
bitsRequired{calcRequiredBits<details::SameSizeUnsigned<T>>(
|
||||
{}, static_cast<details::SameSizeUnsigned<T>>((max - min) / precision))} {
|
||||
|
||||
}
|
||||
|
||||
@@ -163,29 +164,41 @@ namespace bitsery {
|
||||
public:
|
||||
|
||||
template<typename ... Args>
|
||||
explicit constexpr ValueRange(Args &&... args):_range{std::forward<Args>(args)...} {}
|
||||
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;
|
||||
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);
|
||||
this->_wrapped.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);
|
||||
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,7 +445,7 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,7 +454,7 @@ namespace bitsery {
|
||||
void procText(const T& str, size_t maxSize) {
|
||||
auto 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>{});
|
||||
}
|
||||
@@ -380,24 +468,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 +505,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 {nullptr};
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
#define BITSERY_TRAITS_CORE_STD_DEFAULTS_H
|
||||
|
||||
#include "traits.h"
|
||||
#include <iostream>
|
||||
#include "../../details/serialization_common.h"
|
||||
|
||||
namespace bitsery {
|
||||
namespace traits {
|
||||
@@ -53,13 +53,28 @@ namespace bitsery {
|
||||
return container.size();
|
||||
}
|
||||
static void resize(T& container, size_t size) {
|
||||
resizeImpl(container, size, std::is_default_constructible<TValue>{});
|
||||
}
|
||||
private:
|
||||
static void resizeImpl(T& container, size_t size, std::true_type) {
|
||||
container.resize(size);
|
||||
}
|
||||
static void resizeImpl(T& container, size_t newSize, std::false_type) {
|
||||
const auto oldSize = size(container);
|
||||
for (auto it = oldSize; it < newSize; ++it) {
|
||||
container.push_back(::bitsery::Access::create<TValue>());
|
||||
}
|
||||
if (oldSize > newSize) {
|
||||
container.erase(std::next(std::begin(container), newSize), std::end(container));
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template <typename T, bool Resizable = ContainerTraits<T>::isResizable>
|
||||
struct StdContainerForBufferAdapter {
|
||||
using TIterator = typename T::iterator;
|
||||
using TConstIterator = typename T::const_iterator;
|
||||
using TValue = typename ContainerTraits<T>::TValue;
|
||||
};
|
||||
|
||||
@@ -73,9 +88,10 @@ namespace bitsery {
|
||||
auto newSize = static_cast<size_t>(container.size() * 1.5 + 128);
|
||||
//make data cache friendly
|
||||
newSize -= newSize % 64;//64 is cache line size
|
||||
container.resize(std::max(newSize, container.capacity()));
|
||||
container.resize((std::max)(newSize, container.capacity()));
|
||||
}
|
||||
using TIterator = typename T::iterator;
|
||||
using TConstIterator = typename T::const_iterator;
|
||||
using TValue = typename ContainerTraits<T>::TValue;
|
||||
};
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -155,6 +154,7 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
using TIterator = details::NotDefinedType;
|
||||
using TConstIterator = details::NotDefinedType;
|
||||
using TValue = typename ContainerTraits<T>::TValue;
|
||||
};
|
||||
|
||||
@@ -162,6 +162,7 @@ namespace bitsery {
|
||||
template <typename T, size_t N>
|
||||
struct BufferAdapterTraits<T[N]> {
|
||||
using TIterator = T*;
|
||||
using TConstIterator = const T*;
|
||||
using TValue = T;
|
||||
};
|
||||
|
||||
@@ -169,12 +170,14 @@ namespace bitsery {
|
||||
template <typename T>
|
||||
struct BufferAdapterTraits<const T*> {
|
||||
using TIterator = const T*;
|
||||
using TConstIterator = const T*;
|
||||
using TValue = T;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct BufferAdapterTraits<T*> {
|
||||
using TIterator = T*;
|
||||
using TConstIterator = const T*;
|
||||
using TValue = T;
|
||||
};
|
||||
|
||||
|
||||
@@ -31,9 +31,9 @@ namespace bitsery {
|
||||
|
||||
namespace traits {
|
||||
|
||||
template<typename ... TArgs>
|
||||
struct ContainerTraits<std::deque<TArgs...>>
|
||||
: public StdContainer<std::deque<TArgs...>, true, false> {};
|
||||
template<typename T, typename Allocator>
|
||||
struct ContainerTraits<std::deque<T, Allocator>>
|
||||
: public StdContainer<std::deque<T, Allocator>, true, false> {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -24,23 +24,42 @@
|
||||
#ifndef BITSERY_TRAITS_STD_FORWARD_LIST_H
|
||||
#define BITSERY_TRAITS_STD_FORWARD_LIST_H
|
||||
|
||||
#include "core/traits.h"
|
||||
#include "../details/serialization_common.h"
|
||||
#include <forward_list>
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
namespace traits {
|
||||
|
||||
template<typename ... TArgs>
|
||||
struct ContainerTraits<std::forward_list<TArgs...>> {
|
||||
using TValue = typename std::forward_list<TArgs...>::value_type;
|
||||
template<typename T, typename Allocator>
|
||||
struct ContainerTraits<std::forward_list<T, Allocator>> {
|
||||
using TValue = T;
|
||||
static constexpr bool isResizable = true;
|
||||
static constexpr bool isContiguous = false;
|
||||
static size_t size(const std::forward_list<TArgs...>& container) {
|
||||
static size_t size(const std::forward_list<T, Allocator>& container) {
|
||||
return static_cast<size_t>(std::distance(container.begin(), container.end()));
|
||||
}
|
||||
static void resize(std::forward_list<TArgs...>& container, size_t size) {
|
||||
static void resize(std::forward_list<T, Allocator>& container, size_t size) {
|
||||
resizeImpl(container, size, std::is_default_constructible<TValue>{});
|
||||
}
|
||||
private:
|
||||
static void resizeImpl(std::forward_list<T, Allocator>& container, size_t size, std::true_type) {
|
||||
container.resize(size);
|
||||
}
|
||||
static void resizeImpl(std::forward_list<T, Allocator>& container, size_t newSize, std::false_type) {
|
||||
const auto oldSize = size(container);
|
||||
for (auto it = oldSize; it < newSize; ++it) {
|
||||
container.push_front(::bitsery::Access::create<TValue>());
|
||||
}
|
||||
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));
|
||||
else
|
||||
container.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ namespace bitsery {
|
||||
|
||||
namespace traits {
|
||||
|
||||
template<typename ... TArgs>
|
||||
struct ContainerTraits<std::list<TArgs...>>
|
||||
: public StdContainer<std::list<TArgs...>, true, false> {};
|
||||
template<typename T, typename Allocator>
|
||||
struct ContainerTraits<std::list<T, Allocator>>
|
||||
: public StdContainer<std::list<T, Allocator>, true, false> {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -33,18 +33,18 @@ namespace bitsery {
|
||||
|
||||
// specialization for string, because string is already included for std::char_traits
|
||||
|
||||
template<typename ... TArgs>
|
||||
struct ContainerTraits<std::basic_string<TArgs...>>
|
||||
:public StdContainer<std::basic_string<TArgs...>, true, true> {};
|
||||
template<typename CharT, typename Traits, typename Allocator>
|
||||
struct ContainerTraits<std::basic_string<CharT, Traits, Allocator>>
|
||||
:public StdContainer<std::basic_string<CharT, Traits, Allocator>, true, true> {};
|
||||
|
||||
template <typename ... TArgs>
|
||||
struct TextTraits<std::basic_string<TArgs...>> {
|
||||
using TValue = typename ContainerTraits<std::basic_string<TArgs...>>::TValue;
|
||||
template <typename CharT, typename Traits, typename Allocator>
|
||||
struct TextTraits<std::basic_string<CharT, Traits, Allocator>> {
|
||||
using TValue = typename ContainerTraits<std::basic_string<CharT, Traits, Allocator>>::TValue;
|
||||
//string is automatically null-terminated
|
||||
static constexpr bool addNUL = false;
|
||||
|
||||
//is is not 100% accurate, but for performance reasons assume that string stores text, not binary data
|
||||
static size_t length(const std::basic_string<TArgs...>& str) {
|
||||
static size_t length(const std::basic_string<CharT, Traits, Allocator>& str) {
|
||||
return str.size();
|
||||
}
|
||||
};
|
||||
@@ -60,9 +60,9 @@ namespace bitsery {
|
||||
}
|
||||
};
|
||||
|
||||
template<typename ... TArgs>
|
||||
struct BufferAdapterTraits<std::basic_string<TArgs...>>
|
||||
:public StdContainerForBufferAdapter<std::basic_string<TArgs...>> {};
|
||||
template<typename CharT, typename Traits, typename Allocator>
|
||||
struct BufferAdapterTraits<std::basic_string<CharT, Traits, Allocator>>
|
||||
:public StdContainerForBufferAdapter<std::basic_string<CharT, Traits, Allocator>> {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -30,18 +30,18 @@
|
||||
namespace bitsery {
|
||||
|
||||
namespace traits {
|
||||
template<typename ... TArgs>
|
||||
struct ContainerTraits<std::vector<TArgs...>>
|
||||
:public StdContainer<std::vector<TArgs...>, true, true> {};
|
||||
template<typename T, typename Allocator>
|
||||
struct ContainerTraits<std::vector<T, Allocator>>
|
||||
:public StdContainer<std::vector<T, Allocator>, true, true> {};
|
||||
|
||||
//bool vector is not contiguous, do not copy it directly to buffer
|
||||
template<typename Allocator>
|
||||
struct ContainerTraits<std::vector<bool, Allocator>>
|
||||
:public StdContainer<std::vector<bool, Allocator>, true, false> {};
|
||||
|
||||
template<typename ... TArgs>
|
||||
struct BufferAdapterTraits<std::vector<TArgs...>>
|
||||
:public StdContainerForBufferAdapter<std::vector<TArgs...>> {};
|
||||
template<typename T, typename Allocator>
|
||||
struct BufferAdapterTraits<std::vector<T, Allocator>>
|
||||
:public StdContainerForBufferAdapter<std::vector<T, Allocator>> {};
|
||||
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
6
scripts/CTestConfig.cmake
Normal file
6
scripts/CTestConfig.cmake
Normal file
@@ -0,0 +1,6 @@
|
||||
set(CTEST_PROJECT_NAME "bitsery")
|
||||
|
||||
set(CTEST_DROP_METHOD "http")
|
||||
set(CTEST_DROP_SITE "my.cdash.org")
|
||||
set(CTEST_DROP_LOCATION "/submit.php?project=bitsery")
|
||||
set(CTEST_DROP_SITE_CDASH TRUE)
|
||||
19
scripts/build.bitsery.cmake
Normal file
19
scripts/build.bitsery.cmake
Normal file
@@ -0,0 +1,19 @@
|
||||
# run from linux shell:
|
||||
#$ ctest -S build.bitsery.cmake
|
||||
set(CTEST_SOURCE_DIRECTORY "..") #path to bitsery root (top-level) directory
|
||||
set(CTEST_BINARY_DIRECTORY "build")
|
||||
|
||||
set(ENV{CXXFLAGS} "--coverage")
|
||||
#when using Ninja generator, ctest_coverage cannot find files...
|
||||
set(CTEST_CMAKE_GENERATOR "CodeBlocks - Unix Makefiles")
|
||||
|
||||
set(CTEST_COVERAGE_COMMAND "gcov")
|
||||
|
||||
configure_file(CTestConfig.cmake ${CTEST_SOURCE_DIRECTORY}/CTestConfig.cmake)
|
||||
|
||||
ctest_start("Continuous")
|
||||
ctest_configure(OPTIONS "-DBITSERY_BUILD_EXAMPLES=OFF;-DBITSERY_BUILD_TESTS=ON")
|
||||
ctest_build()
|
||||
ctest_test(BUILD ${CTEST_BINARY_DIRECTORY}/tests)
|
||||
ctest_coverage()
|
||||
#ctest_submit()
|
||||
8
scripts/show_coverage.sh
Executable file
8
scripts/show_coverage.sh
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
BUILD_DIR=./build
|
||||
TESTS_BUILD_DIR=$BUILD_DIR/tests/CMakeFiles/
|
||||
COV_INFO=$TESTS_BUILD_DIR/bitsery_coverage.info
|
||||
lcov --directory $TESTS_BUILD_DIR --capture --output-file $COV_INFO
|
||||
lcov --extract $COV_INFO '*include/bitsery*' --output-file $COV_INFO.clean
|
||||
genhtml --output-directory $TESTS_BUILD_DIR/coverage_web $COV_INFO.clean
|
||||
x-www-browser $TESTS_BUILD_DIR/coverage_web/index.html
|
||||
@@ -20,55 +20,43 @@
|
||||
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
#SOFTWARE.
|
||||
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
set(TestProjectName bitsery_tests)
|
||||
project(${TestProjectName} C CXX)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(bitsery_tests CXX)
|
||||
|
||||
find_package(GTest 1.8 REQUIRED)
|
||||
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
#add googletest external project
|
||||
#USE_GMOCK enable gmock
|
||||
#exports variables GTEST_INCLUDE_DIRS, GTEST_LIBS_DIR, GTEST_LIBNAME, GTEST_MAIN_LIBNAME
|
||||
set(ExtCMakeFilesDir ${CMAKE_SOURCE_DIR}/ext)
|
||||
set(UseGMock ON)
|
||||
add_subdirectory(${ExtCMakeFilesDir}/gtest ${CMAKE_BINARY_DIR}/gtest)
|
||||
|
||||
#this helps idea to know which files are actually used
|
||||
file(GLOB_RECURSE IncludeHeaders ${CMAKE_SOURCE_DIR}/include/bitsery/*.h)
|
||||
# set common include folder for module
|
||||
include_directories(SYSTEM ${GTestIncludeDirs})
|
||||
include_directories(${CMAKE_SOURCE_DIR}/include)
|
||||
if (NOT TARGET Bitsery::bitsery)
|
||||
message(FATAL_ERROR "Bitsery::bitsery alias not set. Please generate CMake from bitsery root directory.")
|
||||
endif()
|
||||
|
||||
file(GLOB TestSourceFiles ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
|
||||
|
||||
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
|
||||
message(WARNING "extension tests for optional is disable for VS, because VS currenty doesn't have <optional>")
|
||||
list(REMOVE_ITEM TestSourceFiles ${CMAKE_CURRENT_SOURCE_DIR}/serialization_ext_std_optional.cpp)
|
||||
endif()
|
||||
enable_testing()
|
||||
|
||||
include(${ExtCMakeFilesDir}/LinkTestLib.cmake)
|
||||
|
||||
FOREACH(TestFile ${TestSourceFiles})
|
||||
foreach (TestFile ${TestSourceFiles})
|
||||
get_filename_component(TestName ${TestFile} NAME_WE)
|
||||
set(TestName TEST_${TestName})
|
||||
add_executable(${TestName} ${TestFile} ${IncludeHeaders} serialization_test_utils.h)
|
||||
LinkTestLib(${TestName})
|
||||
set(TestName bitsery.test.${TestName})
|
||||
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)
|
||||
endif()
|
||||
gtest_discover_tests(${TestName})
|
||||
|
||||
add_test(NAME ${TestName} COMMAND $<TARGET_FILE:${TestName}>)
|
||||
# add_test(NAME ${TestName} COMMAND $<TARGET_FILE:${TestName}>)
|
||||
endforeach()
|
||||
|
||||
ENDFOREACH()
|
||||
#======================= setup development environment ====================
|
||||
|
||||
#all in one tests for code coverage
|
||||
add_executable(${TestProjectName} ${TestSourceFiles})
|
||||
|
||||
if(CMAKE_COMPILER_IS_GNUCXX)
|
||||
include(${ExtCMakeFilesDir}/CodeCoverage.cmake)
|
||||
target_compile_options(${TestProjectName} PUBLIC -O0 -fprofile-arcs -ftest-coverage)
|
||||
target_link_libraries(${TestProjectName} -O0 -fprofile-arcs -ftest-coverage)
|
||||
setup_target_for_coverage(tests_coverage ${TestProjectName} coverage)
|
||||
# get all header files for IDE (in my case Clion) and create dummy project that consumes theses files
|
||||
get_directory_property(ParentDir PARENT_DIRECTORY)
|
||||
if (ParentDir)
|
||||
# only include when working from root project (Bitsery)
|
||||
file(GLOB_RECURSE HeadersForIDE ${ParentDir}/include/bitsery/*.h)
|
||||
# create dummy target IDE
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/dummy_for_ide.cpp "//generated by CMake to create dummy target with all includes for IDE.")
|
||||
add_library(bitsery.dummy_for_ide ${CMAKE_BINARY_DIR}/dummy_for_ide.cpp)
|
||||
# add headers so IDE correctly show them
|
||||
target_sources(bitsery.dummy_for_ide PRIVATE ${HeadersForIDE} serialization_test_utils.h)
|
||||
target_link_libraries(bitsery.dummy_for_ide PRIVATE GTest::Main Bitsery::bitsery)
|
||||
endif()
|
||||
|
||||
LinkTestLib(${TestProjectName})
|
||||
|
||||
|
||||
|
||||
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_CASE(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_CASE(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_CASE(OutputStreamBuffered, BufferedAdapterInternalBufferTypes);
|
||||
|
||||
TYPED_TEST(OutputStreamBuffered, WhenInternalBufferIsFullThenWriteBufferAndRemainingDataToStream) {
|
||||
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(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,161 +0,0 @@
|
||||
//MIT License
|
||||
//
|
||||
//Copyright (c) 2017 Mindaugas Vinkelis
|
||||
//
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
//of this software and associated documentation files (the "Software"), to deal
|
||||
//in the Software without restriction, including without limitation the rights
|
||||
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
//copies of the Software, and to permit persons to whom the Software is
|
||||
//furnished to do so, subject to the following conditions:
|
||||
//
|
||||
//The above copyright notice and this permission notice shall be included in all
|
||||
//copies or substantial portions of the Software.
|
||||
//
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <bitsery/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 <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));
|
||||
}
|
||||
435
tests/brief_syntax.cpp
Normal file
435
tests/brief_syntax.cpp
Normal file
@@ -0,0 +1,435 @@
|
||||
//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/brief_syntax.h>
|
||||
#include <bitsery/brief_syntax/array.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/brief_syntax/tuple.h>
|
||||
#include <bitsery/brief_syntax/variant.h>
|
||||
#else
|
||||
#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
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
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()(ti, te, tf, td, tb);
|
||||
|
||||
//result
|
||||
int ri{};
|
||||
MyEnumClass re{};
|
||||
float rf{};
|
||||
double rd{};
|
||||
bool rb{};
|
||||
ctx.createDeserializer()(ri, re, rf, rd, rb);
|
||||
|
||||
//test
|
||||
EXPECT_THAT(ri, Eq(ti));
|
||||
EXPECT_THAT(re, Eq(te));
|
||||
EXPECT_THAT(rf, Eq(tf));
|
||||
EXPECT_THAT(rd, Eq(td));
|
||||
EXPECT_THAT(rb, Eq(tb));
|
||||
}
|
||||
|
||||
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();
|
||||
ser.object(ti);
|
||||
ser.object(te);
|
||||
ser.object(tf);
|
||||
ser.object(td);
|
||||
ser.object(tb);
|
||||
|
||||
//result
|
||||
int ri{};
|
||||
MyEnumClass re{};
|
||||
float rf{};
|
||||
double rd{};
|
||||
bool rb{};
|
||||
auto& des = ctx.createDeserializer();
|
||||
des.object(ri);
|
||||
des.object(re);
|
||||
des.object(rf);
|
||||
des.object(rd);
|
||||
des.object(rb);
|
||||
|
||||
//test
|
||||
EXPECT_THAT(ri, Eq(ti));
|
||||
EXPECT_THAT(re, Eq(te));
|
||||
EXPECT_THAT(rf, Eq(tf));
|
||||
EXPECT_THAT(rd, Eq(td));
|
||||
EXPECT_THAT(rb, Eq(tb));
|
||||
}
|
||||
|
||||
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();
|
||||
ser.value<sizeof(ti)>(ti);
|
||||
ser(te, tf, td);
|
||||
ser.object(tb);
|
||||
|
||||
//result
|
||||
int ri{};
|
||||
MyEnumClass re{};
|
||||
float rf{};
|
||||
double rd{};
|
||||
bool rb{};
|
||||
auto& des = ctx.createDeserializer();
|
||||
des(ri, re, rf);
|
||||
des.value8b(rd);
|
||||
des.object(rb);
|
||||
|
||||
//test
|
||||
EXPECT_THAT(ri, Eq(ti));
|
||||
EXPECT_THAT(re, Eq(te));
|
||||
EXPECT_THAT(rf, Eq(tf));
|
||||
EXPECT_THAT(rd, Eq(td));
|
||||
EXPECT_THAT(rb, Eq(tb));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T procBriefSyntax(const T& testData) {
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer()(testData);
|
||||
T res{};
|
||||
ctx.createDeserializer()(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T procBriefSyntaxWithMaxSize(const T& testData) {
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer()(bitsery::maxSize(testData, 100));
|
||||
T res{};
|
||||
ctx.createDeserializer()(bitsery::maxSize(res, 100));
|
||||
return res;
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, CStyleArrayForValueTypesAsContainer) {
|
||||
const int t1[3]{8748, -484, 45};
|
||||
int r1[3]{0, 0, 0};
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer()(bitsery::asContainer(t1));
|
||||
ctx.createDeserializer()(bitsery::asContainer(r1));
|
||||
|
||||
EXPECT_THAT(r1, ::testing::ContainerEq(t1));
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, CStyleArrayForIntegralTypesAsText) {
|
||||
const char t1[3]{"hi"};
|
||||
char r1[3]{0, 0, 0};
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer()(bitsery::asText(t1));
|
||||
ctx.createDeserializer()(bitsery::asText(r1));
|
||||
|
||||
EXPECT_THAT(r1, ::testing::ContainerEq(t1));
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, CStyleArray) {
|
||||
const MyEnumClass t1[3]{MyEnumClass::E1, MyEnumClass::E4, MyEnumClass::E2};
|
||||
MyEnumClass r1[3]{};
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer()(t1);
|
||||
ctx.createDeserializer()(r1);
|
||||
|
||||
EXPECT_THAT(r1, ::testing::ContainerEq(t1));
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdString) {
|
||||
std::string t1{"my nice string"};
|
||||
std::string 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(BriefSyntax, StdArray) {
|
||||
std::array<int, 3> t1{8748, -484, 45};
|
||||
std::array<int, 0> t2{};
|
||||
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntax(t2), Eq(t2));
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdVector) {
|
||||
std::vector<int> t1{8748, -484, 45};
|
||||
std::vector<float> t2{5.f, 0.198f};
|
||||
|
||||
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(BriefSyntax, StdList) {
|
||||
std::list<int> t1{8748, -484, 45};
|
||||
std::list<float> t2{5.f, 0.198f};
|
||||
|
||||
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(BriefSyntax, StdForwardList) {
|
||||
std::forward_list<int> t1{8748, -484, 45};
|
||||
std::forward_list<float> t2{5.f, 0.198f};
|
||||
|
||||
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(BriefSyntax, StdDeque) {
|
||||
std::deque<int> t1{8748, -484, 45};
|
||||
std::deque<float> t2{5.f, 0.198f};
|
||||
|
||||
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(BriefSyntax, StdQueue) {
|
||||
std::queue<std::string> t1;
|
||||
t1.push("first");
|
||||
t1.push("second string");
|
||||
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdPriorityQueue) {
|
||||
std::priority_queue<std::string> t1;
|
||||
t1.push("first");
|
||||
t1.push("second string");
|
||||
t1.push("third");
|
||||
t1.push("fourth");
|
||||
auto r1 = procBriefSyntax(t1);
|
||||
//we cannot compare priority queue directly
|
||||
|
||||
EXPECT_THAT(r1.size(), Eq(t1.size()));
|
||||
for (auto i = 0u; i < r1.size(); ++i) {
|
||||
EXPECT_THAT(r1.top(), Eq(t1.top()));
|
||||
r1.pop();
|
||||
t1.pop();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdStack) {
|
||||
std::stack<std::string> t1;
|
||||
t1.push("first");
|
||||
t1.push("second string");
|
||||
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdUnorderedMap) {
|
||||
std::unordered_map<int, int> t1;
|
||||
t1.emplace(3423, 624);
|
||||
t1.emplace(-5484, -845);
|
||||
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdUnorderedMultiMap) {
|
||||
std::unordered_multimap<std::string, int> t1;
|
||||
t1.emplace("one", 624);
|
||||
t1.emplace("two", -845);
|
||||
t1.emplace("one", 897);
|
||||
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntaxWithMaxSize(t1) == t1);
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdMap) {
|
||||
std::map<int, int> t1;
|
||||
t1.emplace(3423, 624);
|
||||
t1.emplace(-5484, -845);
|
||||
|
||||
EXPECT_THAT(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdMultiMap) {
|
||||
std::multimap<std::string, int> t1;
|
||||
t1.emplace("one", 624);
|
||||
t1.emplace("two", -845);
|
||||
t1.emplace("one", 897);
|
||||
|
||||
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));
|
||||
for (auto it = t1.begin(); it != t1.end();) {
|
||||
const auto lr = t1.equal_range(it->first);
|
||||
const auto rr = res.equal_range(it->first);
|
||||
EXPECT_TRUE(std::distance(lr.first, lr.second) == std::distance(rr.first, rr.second));
|
||||
EXPECT_TRUE(std::is_permutation(lr.first, lr.second, rr.first));
|
||||
it = lr.second;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdUnorderedSet) {
|
||||
std::unordered_set<std::string> t1;
|
||||
t1.emplace("one");
|
||||
t1.emplace("two");
|
||||
t1.emplace("three");
|
||||
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntaxWithMaxSize(t1) == t1);
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdUnorderedMultiSet) {
|
||||
std::unordered_multiset<std::string> t1;
|
||||
t1.emplace("one");
|
||||
t1.emplace("two");
|
||||
t1.emplace("three");
|
||||
t1.emplace("one");
|
||||
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntaxWithMaxSize(t1) == t1);
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdSet) {
|
||||
std::set<std::string> t1;
|
||||
t1.emplace("one");
|
||||
t1.emplace("two");
|
||||
t1.emplace("three");
|
||||
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntaxWithMaxSize(t1) == t1);
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdMultiSet) {
|
||||
std::multiset<std::string> t1;
|
||||
t1.emplace("one");
|
||||
t1.emplace("two");
|
||||
t1.emplace("three");
|
||||
t1.emplace("one");
|
||||
t1.emplace("two");
|
||||
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
EXPECT_TRUE(procBriefSyntaxWithMaxSize(t1) == t1);
|
||||
}
|
||||
|
||||
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::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)(resShared1, resWeak1, resUnique1);
|
||||
//clear shared state from pointer linking context
|
||||
plctx1.clearSharedState();
|
||||
|
||||
EXPECT_TRUE(plctx1.isValid());
|
||||
EXPECT_THAT(*resShared1, Eq(*dataShared1));
|
||||
EXPECT_THAT(*resWeak1.lock(), Eq(*dataWeak1.lock()));
|
||||
EXPECT_THAT(*resUnique1, Eq(*dataUnique1));
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdDuration) {
|
||||
std::chrono::duration<int64_t, std::milli> t1{54654};
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
}
|
||||
|
||||
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(procBriefSyntax(data) == data);
|
||||
}
|
||||
|
||||
#if __cplusplus > 201402L
|
||||
|
||||
TEST(BriefSyntax, StdTuple) {
|
||||
std::tuple<int, std::string, std::vector<char>> t1{5,"hello hello", {'A','B','C'}};
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
}
|
||||
|
||||
TEST(BriefSyntax, StdVariant) {
|
||||
std::variant<float, std::string, std::chrono::milliseconds> t1{std::string("hello hello")};
|
||||
EXPECT_TRUE(procBriefSyntax(t1) == t1);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
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(procBriefSyntax(t1), Eq(t1));
|
||||
EXPECT_THAT(procBriefSyntaxWithMaxSize(t1), Eq(t1));
|
||||
}
|
||||
@@ -20,12 +20,11 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#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>
|
||||
|
||||
using testing::Eq;
|
||||
using testing::ContainerEq;
|
||||
@@ -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,25 +51,25 @@ struct IntegralTypes {
|
||||
int8_t e;
|
||||
};
|
||||
|
||||
using InverseReader = bitsery::AdapterReader<InputAdapter, InverseEndiannessConfig>;
|
||||
using InverseReader = bitsery::InputBufferAdapter<Buffer, InverseEndiannessConfig>;
|
||||
|
||||
|
||||
TEST(DataEndianness, WhenWriteBytesThenBytesAreSwapped) {
|
||||
//fill initial values
|
||||
IntegralTypes src{};
|
||||
src.a = static_cast<int64_t>(0x1122334455667788);
|
||||
src.b = 0xBBCCDDEE;
|
||||
src.c = static_cast<int16_t>(0xCCDD);
|
||||
src.d = static_cast<uint8_t>(0xDD);
|
||||
src.e = static_cast<int8_t>(0xEE);
|
||||
src.a = static_cast<int64_t>(0x1122334455667788u);
|
||||
src.b = 0xBBCCDDEEu;
|
||||
src.c = static_cast<int16_t>(0xCCDDu);
|
||||
src.d = static_cast<uint8_t>(0xDDu);
|
||||
src.e = static_cast<int8_t>(0xEEu);
|
||||
|
||||
//fill expected result after swap
|
||||
IntegralTypes resInv{};
|
||||
resInv.a = static_cast<int64_t>(0x8877665544332211);
|
||||
resInv.b = 0xEEDDCCBB;
|
||||
resInv.c = static_cast<int16_t>(0xDDCC);
|
||||
resInv.d = static_cast<uint8_t>(0xDD);
|
||||
resInv.e = static_cast<int8_t>(0xEE);
|
||||
resInv.a = static_cast<int64_t>(0x8877665544332211u);
|
||||
resInv.b = 0xEEDDCCBBu;
|
||||
resInv.c = static_cast<int16_t>(0xDDCCu);
|
||||
resInv.d = static_cast<uint8_t>(0xDDu);
|
||||
resInv.e = static_cast<int8_t>(0xEEu);
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
@@ -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);
|
||||
|
||||
@@ -21,15 +21,18 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#include <bitsery/ext/value_range.h>
|
||||
#include <bitsery/serializer.h>
|
||||
#include <bitsery/deserializer.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/ext/value_range.h>
|
||||
|
||||
|
||||
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 {
|
||||
@@ -57,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{};
|
||||
@@ -105,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{};
|
||||
@@ -138,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);
|
||||
@@ -149,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));
|
||||
@@ -157,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
|
||||
@@ -180,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));
|
||||
@@ -213,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);
|
||||
@@ -265,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);
|
||||
@@ -311,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);
|
||||
@@ -342,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));
|
||||
@@ -365,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{};
|
||||
@@ -393,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,155 +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));
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
//MIT License
|
||||
//
|
||||
//Copyright (c) 2017 Mindaugas Vinkelis
|
||||
//
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
//of this software and associated documentation files (the "Software"), to deal
|
||||
//in the Software without restriction, including without limitation the rights
|
||||
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
//copies of the Software, and to permit persons to whom the Software is
|
||||
//furnished to do so, subject to the following conditions:
|
||||
//
|
||||
//The above copyright notice and this permission notice shall be included in all
|
||||
//copies or substantial portions of the Software.
|
||||
//
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/traits/string.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));
|
||||
}
|
||||
@@ -20,11 +20,12 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/details/serialization_common.h>
|
||||
#include <bitsery/traits/array.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
using testing::Eq;
|
||||
using testing::ContainerEq;
|
||||
using bitsery::EndiannessType;
|
||||
@@ -32,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;
|
||||
};
|
||||
|
||||
@@ -73,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();
|
||||
@@ -87,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();
|
||||
@@ -100,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);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user