mirror of
https://github.com/fraillt/bitsery.git
synced 2026-06-18 13:19:11 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54f69a5eea | ||
|
|
275c4138ee | ||
|
|
1ca45aab79 | ||
|
|
952635ff70 | ||
|
|
507b5ae01d | ||
|
|
f6d02aba38 | ||
|
|
be9ccf08d9 | ||
|
|
5b1dc3bcfa | ||
|
|
bdc24eb3c2 | ||
|
|
1acb9af188 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
||||
.vs/
|
||||
build/
|
||||
cmake-build-debug/
|
||||
CTestConfig.cmake
|
||||
|
||||
13
.travis.yml
13
.travis.yml
@@ -9,15 +9,22 @@ before_install:
|
||||
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||
- sudo apt-get update -qq
|
||||
|
||||
install:
|
||||
install:
|
||||
- sudo apt-get install -qq g++-5 lcov
|
||||
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-5 90
|
||||
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 90
|
||||
- wget https://github.com/google/googletest/archive/release-1.8.0.tar.gz
|
||||
- tar xf release-1.8.0.tar.gz
|
||||
- cd googletest-release-1.8.0
|
||||
- cmake -DBUILD_SHARED_LIBS=ON .
|
||||
- make
|
||||
- sudo make install
|
||||
- cd ..
|
||||
|
||||
before_script:
|
||||
- mkdir build
|
||||
- cd build
|
||||
- cmake ..
|
||||
- cmake -DBITSERY_BUILD_TESTS=ON ..
|
||||
|
||||
script: make && make test
|
||||
script: make && (cd tests && ctest)
|
||||
|
||||
|
||||
98
CHANGELOG.md
98
CHANGELOG.md
@@ -1,3 +1,101 @@
|
||||
# [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
|
||||
|
||||
* serializer/deserializer can now have **internal context(s)** via configuration.
|
||||
It is convenient way to pass context, when it doesn't convey useful information outside of serializer/deserializer and is default constructable.
|
||||
* added **contextOrNull\<T\>()** overload to *BasicSerializer/BasicDeserializer*.
|
||||
Difference between *contextOrNull\<T\>()* and *context\<T\>()* is, that using *context\<T\>()* code doesn't compile if T doesn't exists at all, while using *contextOrNull\<T\>()* code compiles, but returns *nullptr* at runtime.
|
||||
* added inheritance support via extensions.
|
||||
In order to correctly manage virtual inheritance two extensions was created in **<bitsery/ext/inheritance.h>** header:
|
||||
* **BaseClass\<TBase\>** - use when inheriting from objects without virtual inheritance.
|
||||
* **VirtualBaseClass\<TBase\>** - ensures that only one copy of each virtual base class is serialized.
|
||||
|
||||
To keep track of virtual base classes **InheritanceContext** is required, but it is optional if no virtual bases exists in serialization flow.
|
||||
I.e. if context is not defined, code will not compile only if virtual inheritance is used.
|
||||
See [inheritance](examples/inheritance.cpp) for usage example.
|
||||
|
||||
### Improvements
|
||||
* added optional ctor parameter for *PointerOwner* and *PointerObserver* - **PointerType**, which specifies if pointer can be null or not.
|
||||
Default is **Nullable**.
|
||||
|
||||
# [4.1.0](https://github.com/fraillt/bitsery/compare/v4.0.1...v4.1.0) (2017-10-27)
|
||||
|
||||
### Features
|
||||
* added raw pointers support via extensions.
|
||||
In order to correctly manage pointer ownership, three extensions was created in **<bitsery/ext/pointer.h>** header:
|
||||
* **PointerOwner** - manages life time of the pointer, creates or destroys if required.
|
||||
* **PointerObserver** - doesn't own pointer so it doesn't create or destroy anything.
|
||||
* **ReferencedByPointer** - when non-owning pointer (*PointerObserver*) points to reference type, this extension marks this object as a valid target for PointerObserver.
|
||||
|
||||
To validate and update pointers **PointerLinkingContext** have to be passed to serialization/deserialization.
|
||||
It ensures that all pointers are valid, that same pointer doesn't have multiple owners, and non-owning pointers doesn't point outside of scope (i.e. non owning pointers points to data that is serialized/deserialized), see [raw_pointers example](examples/raw_pointers.cpp) for usage example.
|
||||
|
||||
*Currently polimorphism and std::shared_ptr, std::unique_ptr is not supported.*
|
||||
|
||||
* added **context\<T\>()** overload to *BasicSerializer/BasicDeserializer* and now they became typesafe.
|
||||
For better extensions support, added posibility to have multiple types in context with *std::tuple*.
|
||||
E.g. when using multiple extensions, that requires specific contexts, together with your custom context, you can define your context as *std::tuple\<PointerLinkingContext, MyContext\>* and in serialization function you can correctly get your data via *context\<MyContext\>()*.
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* new **OutputBufferedStreamAdapter** use internal buffer instead of directly writing to stream, can get more than 2x performance increase.
|
||||
* can use any contiguous container as internal buffer.
|
||||
* when using fixed-size, stack allocated container (*std::array*), buffer size via constructor is ignored.
|
||||
* default internal buffer is *std::array<char,256>*.
|
||||
* added *static_assert* when trying to use *BufferAdapter* with non contiguous container.
|
||||
|
||||
|
||||
# [4.0.1](https://github.com/fraillt/bitsery/compare/v4.0.0...v4.0.1) (2017-10-18)
|
||||
|
||||
### Improvements
|
||||
|
||||
* improved usage with Visual Studio:
|
||||
* improved CMake.
|
||||
* Visual Studio doesn't properly support expression SFINAE using std::void_t, so it was rewritten.
|
||||
* refactorings to remove compiler warnings when using *-Wextra -Wno-missing-braces -Wpedantic -Weffc++*
|
||||
* added assertion when session is empty (sessions is created via *growable* extension).
|
||||
* stream adapter manually *setstate* to *std::ios_base::eofbit* when unable to read required bytes.
|
||||
|
||||
# [4.0.0](https://github.com/fraillt/bitsery/compare/v3.0.0...v4.0.0) (2017-10-13)
|
||||
|
||||
I feel that current library public API is complete, and should be stable for long time.
|
||||
|
||||
@@ -1,12 +1,57 @@
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
cmake_minimum_required(VERSION 3.1)
|
||||
project(bitsery
|
||||
LANGUAGES CXX
|
||||
VERSION 4.3.0)
|
||||
|
||||
project(bitsery)
|
||||
#======== build options ===================================
|
||||
option(BITSERY_BUILD_EXAMPLES "Build examples" OFF)
|
||||
option(BITSERY_BUILD_TESTS "Build tests" OFF)
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
#============= 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)
|
||||
|
||||
add_subdirectory(examples)
|
||||
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)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
#=============== 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()
|
||||
message("skip bitsery examples")
|
||||
endif()
|
||||
|
||||
if (BITSERY_BUILD_TESTS)
|
||||
message("build bitsery tests")
|
||||
add_subdirectory(tests)
|
||||
else()
|
||||
message("skip bitsery tests")
|
||||
endif()
|
||||
|
||||
@@ -8,15 +8,19 @@ 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.
|
||||
|
||||
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
|
||||
|
||||
23
README.md
23
README.md
@@ -14,14 +14,15 @@ All cross-platform requirements are enforced at compile time, so serialized data
|
||||
|
||||
* Cross-platform compatible.
|
||||
* Optimized for speed and space.
|
||||
* Allows flexible or/and verbose syntax for better serialization control.
|
||||
* No code generation required: no IDL or metadata, just use your types directly.
|
||||
* Runtime error checking on deserialization.
|
||||
* Supports forward/backward compatibility for your types.
|
||||
* 2-in-1 declarative control flow, same code for serialization and deserialization.
|
||||
* Allows fine-grained bit-level serialization control.
|
||||
* Can read/write from any source: stream (file, network stream. etc... ), or buffer (vector, c-array, etc...).
|
||||
* Don't pay for what you don't use! - customize your serialization via **extensions**. Some notable *extensions* allow:
|
||||
* forward/backward compatibility for your types.
|
||||
* smart and raw pointers with customizable runtime polymorphism support.
|
||||
* fine-grained bit-level serialization control.
|
||||
* Easily extendable for any type.
|
||||
* Allows flexible or/and verbose syntax for better serialization control.
|
||||
* Configurable endianess support.
|
||||
* No macros.
|
||||
|
||||
@@ -31,12 +32,12 @@ Look at the numbers and features list, and decide yourself.
|
||||
|
||||
| | binary size | data size | serialize | deserialize |
|
||||
|------------------------------|-------------|-----------|-------------|-------------|
|
||||
| **test_bitsery** | 64704 | **7565** | **1229 ms** | **1086 ms** |
|
||||
| **test_bitsery_compression** | 44000 | **4784** | **1370 ms** | **2463 ms** |
|
||||
| test_yas | 63864 | 11311 | 1616 ms | 1712 ms |
|
||||
| test_yas_compression | 72688 | 8523 | 2387 ms | 2890 ms |
|
||||
| test_cereal | 74848 | 11261 | 6708 ms | 6799 ms |
|
||||
| test_flatbuffers | 67032 | 16100 | 8793 ms | 3028 ms |
|
||||
| **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 |
|
||||
|
||||
*benchmarked on Ubuntu with GCC 7.1.0, more details can be found [here](https://github.com/fraillt/cpp_serializers_benchmark.git)*
|
||||
|
||||
@@ -104,4 +105,4 @@ This library was tested on
|
||||
|
||||
## License
|
||||
|
||||
**bitsery** is licensed under the [MIT license](LICENSE).
|
||||
**bitsery** is licensed under the [MIT license](LICENSE).
|
||||
|
||||
@@ -9,27 +9,38 @@ Library design:
|
||||
* `extending library functionality`
|
||||
* `errors handling`
|
||||
* `forward/backward compatibility via Growable extension`
|
||||
* `pointers`
|
||||
* `inheritance`
|
||||
* `polymorphism`
|
||||
|
||||
|
||||
Core Serializer/Deserializer functions (alphabetical order):
|
||||
* `align`
|
||||
* `boolValue`
|
||||
* `container`
|
||||
* `ext`
|
||||
* `context`
|
||||
* `object`
|
||||
* `text`
|
||||
* `value`
|
||||
* `align` (1.0.0)
|
||||
* `boolValue` (4.0.0)
|
||||
* `container` (1.0.0)
|
||||
* `ext` (2.0.0)
|
||||
* `context` (3.0.0)
|
||||
* `context<T>` (4.1.0)
|
||||
* `contextOrNull<T>` (4.2.0)
|
||||
* `object` (1.0.0)
|
||||
* `text` (1.0.0)
|
||||
* `value` (1.0.0)
|
||||
|
||||
Serializer/Deserializer extensions via `ext` method (alphabetical order):
|
||||
* `Entropy`
|
||||
* `Growable`
|
||||
* `StdMap`
|
||||
* `StdOptional`
|
||||
* `StdQueue`
|
||||
* `StdSet`
|
||||
* `StdStack`
|
||||
* `ValueRange`
|
||||
* `BaseClass` (4.2.0)
|
||||
* `Entropy` (3.0.0)
|
||||
* `Growable` (3.0.0)
|
||||
* `PointerOwner` (4.1.0)
|
||||
* `PointerObserver` (4.1.0)
|
||||
* `ReferencedByPointer` (4.1.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)
|
||||
* `ValueRange` (3.0.0)
|
||||
* `VirtualBaseClass` (4.2.0)
|
||||
|
||||
AdapterWriter/Reader functions:
|
||||
* `writeBits/readBits`
|
||||
@@ -52,14 +63,11 @@ Input adapters (buffer and stream) functions:
|
||||
Output adapters (buffer and stream) functions:
|
||||
* `write`
|
||||
* `flush`
|
||||
* `writtenBytesCount`
|
||||
* `writtenBytesCount` (buffer adapter only)
|
||||
|
||||
|
||||
Tips and tricks:
|
||||
* if you're getting static assert "please define 'serialize' function", most likely it is because your **serialize** function is not defined in same namespace as object.
|
||||
|
||||
Limitations:
|
||||
* max **text** or **container** size can be 2^(n-2) (where n = sizeof(std::size_t) * 8) for 32-bit systems it is 1073741823 (0x3FFFFFF).
|
||||
* 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)
|
||||
|
||||
@@ -41,9 +41,9 @@ But do it on your own risk, and static assert using *assertFundamentalTypeSizes*
|
||||
It also doesn't serialize any type information, all information needed is writen in your code!
|
||||
* **No code generation required: no IDL or metadata** since it doesn't support any other formats except binary, it doesn't need any metadata.
|
||||
* **Runtime error checking on deserialization** library designed to be save with untrusted network data, that's why all overloads that work on containers has *maxSize* value, unless container is static size like *std::array*, this way bitsery ensures that no malicious data crash you.
|
||||
* **Supports forward/backward compatibility for your types** library has optional forward/backward compatibility for types implemented in *BasicBufferReader/BasicBufferWriter* by allowing to have inner data sessions in inside buffer.
|
||||
* **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.
|
||||
*Glowable* extension use these sessions to add compatibility support for your types, in most basic form.
|
||||
*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.
|
||||
* **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.
|
||||
@@ -67,5 +67,3 @@ To use same container for buffer writing/reading add specialization to *BufferAd
|
||||
You want to customize serialization flow - use extensions, only two methods to define, and *ExtensionTraits* to further customize usage.
|
||||
* **Configurable endianess support.** default is *Little Endian*, but if your primary target is PowerPC architecture, eg. PlayStation3, just change your configuration to be *Big Endian*.
|
||||
* **No macros.** Not so much to say, if you are like me, then it's a feature :)
|
||||
|
||||
*project for performance benchmark will be added to separate github project, i'll give you a link to it when its done.*
|
||||
@@ -20,16 +20,26 @@
|
||||
#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)
|
||||
|
||||
FOREACH(ExampleFile ${ExampleFiles})
|
||||
if (WIN32)
|
||||
message(WARNING "Removing example `flexible_assert_linux_x64` for Windows")
|
||||
list(REMOVE_ITEM ExampleFiles ${CMAKE_CURRENT_SOURCE_DIR}/flexible_assert_linux_x64.cpp)
|
||||
endif()
|
||||
|
||||
foreach(ExampleFile ${ExampleFiles})
|
||||
get_filename_component(ExampleName ${ExampleFile} NAME_WE)
|
||||
add_executable(${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()
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ void serialize(S& s, MyStruct& o) {
|
||||
s.value4b(o.i);//fundamental types (ints, floats, enums) of size 4b
|
||||
s.value2b(o.e);
|
||||
s.container4b(o.fs, 10);//resizable containers also requires maxSize, to make it safe from buffer-overflow attacks
|
||||
};
|
||||
}
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
|
||||
64
examples/bit_packing.cpp
Normal file
64
examples/bit_packing.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
//we'll be using std::array as a buffer type, so include traits for this
|
||||
#include <bitsery/traits/array.h>
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
//include extension that will allow to compress our data
|
||||
#include <bitsery/ext/value_range.h>
|
||||
|
||||
namespace MyTypes {
|
||||
|
||||
struct Vec3 { float x, y, z; };
|
||||
|
||||
struct Monster {
|
||||
Vec3 pos;
|
||||
std::vector<Vec3> path;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
template<typename S>
|
||||
void serialize(S& s, MyTypes::Vec3 &o) {
|
||||
s.value4b(o.x);
|
||||
s.value4b(o.y);
|
||||
s.value4b(o.z);
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
void serialize (S& s, Monster& o) {
|
||||
s.text1b(o.name, 20);
|
||||
s.object(o.pos);
|
||||
//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) {
|
||||
constexpr bitsery::ext::ValueRange<float> range{-1.0f,1.0f, 0.01f};
|
||||
sbp.ext(vec3.x, range);
|
||||
sbp.ext(vec3.y, range);
|
||||
sbp.ext(vec3.z, range);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
//use fixed-size buffer
|
||||
using Buffer = std::array<uint8_t, 10000>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
MyTypes::Monster data{};
|
||||
data.name = "lew";
|
||||
|
||||
//create buffer to store data to
|
||||
Buffer buffer{};
|
||||
auto writtenSize = quickSerialization<OutputAdapter>(buffer, data);
|
||||
|
||||
MyTypes::Monster res{};
|
||||
auto state = quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
|
||||
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
}
|
||||
109
examples/context_usage.cpp
Normal file
109
examples/context_usage.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
|
||||
#include <bitsery/ext/value_range.h>
|
||||
|
||||
namespace MyTypes {
|
||||
|
||||
struct Monster {
|
||||
Monster() = default;
|
||||
Monster(std::string _name, uint32_t minDmg, uint32_t maxDmg)
|
||||
:name{_name}, minDamage{minDmg}, maxDamage{maxDmg} {}
|
||||
|
||||
std::string name{};
|
||||
uint32_t minDamage{};
|
||||
uint32_t maxDamage{};
|
||||
//...
|
||||
};
|
||||
|
||||
struct GameState {
|
||||
std::vector<Monster> monsters;
|
||||
};
|
||||
|
||||
//default flow for monster
|
||||
template <typename S>
|
||||
void serialize (S& s, Monster& o) {
|
||||
s.text1b(o.name, 20);
|
||||
s.value4b(o.minDamage);
|
||||
s.value4b(o.maxDamage);
|
||||
}
|
||||
|
||||
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) {
|
||||
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};
|
||||
//enable bit packing
|
||||
s.enableBitPacking([&m, &range](typename S::BPEnabledType& sbp) {
|
||||
sbp.ext(m.minDamage, range);
|
||||
sbp.ext(m.maxDamage, range);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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:
|
||||
// s.template context<int>();
|
||||
//if we want to get whole tuple, just call s.context() without template paramter.
|
||||
//this templated version also works if our context is the same as cast:
|
||||
// struct MyContext {...};
|
||||
// ...
|
||||
// s.template context<MyContext>();
|
||||
using Context = std::tuple<int, std::pair<uint32_t, uint32_t>>;
|
||||
|
||||
//NOTE:
|
||||
// if your context has no additional usage outside of serialization flow,
|
||||
// then you can create it internally via configuration (see inheritance.cpp)
|
||||
|
||||
int main() {
|
||||
|
||||
MyTypes::GameState data{};
|
||||
data.monsters.push_back({"weaksy", 100, 200});
|
||||
data.monsters.push_back({"bigsy", 500, 1000});
|
||||
data.monsters.push_back({"tootoo", 350, 750});
|
||||
|
||||
//set context
|
||||
Context ctx{};
|
||||
//max monsters
|
||||
std::get<0>(ctx) = 4;
|
||||
//damage range
|
||||
std::get<1>(ctx).first = 100;
|
||||
std::get<1>(ctx).second = 1000;
|
||||
|
||||
|
||||
//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();
|
||||
|
||||
MyTypes::GameState res{};
|
||||
BasicDeserializer <AdapterReader<InputAdapter, bitsery::DefaultConfig>, Context> des { InputAdapter{buffer.begin(), writtenSize}, &ctx};
|
||||
des.object(res);
|
||||
auto& r = AdapterAccess::getReader(des);
|
||||
|
||||
assert(r.error() == ReaderError::NoError && r.isCompletedSuccessfully());
|
||||
}
|
||||
@@ -17,13 +17,12 @@ void serialize(S& s, MyStruct& o) {
|
||||
s.value4b(o.i);
|
||||
s.value2b(o.e);
|
||||
s.value8b(o.f);
|
||||
};
|
||||
}
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
//some helper types
|
||||
using Stream = std::fstream;
|
||||
using IOAdapter = IOStreamAdapter;
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
@@ -37,10 +36,12 @@ int main() {
|
||||
std::cout << "cannot open " << fileName << " for writing\n";
|
||||
return 0;
|
||||
}
|
||||
//use same quick serialization function
|
||||
//streams do not return written size
|
||||
quickSerialization<IOAdapter>(s, data);
|
||||
|
||||
//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();
|
||||
s.close();
|
||||
//reopen for reading
|
||||
|
||||
@@ -52,7 +53,7 @@ int main() {
|
||||
|
||||
//same as serialization, but returns deserialization state as a pair
|
||||
//first = error code, second = is buffer was successfully read from begin to the end.
|
||||
auto state = quickDeserialization<IOAdapter>(s, res);
|
||||
auto state = quickDeserialization<InputStreamAdapter>(s, res);
|
||||
|
||||
assert(state.first == ReaderError::NoError && state.second);
|
||||
assert(data.f == res.f && data.i == res.i && data.e == res.e);
|
||||
|
||||
@@ -15,7 +15,7 @@ struct MyStruct {
|
||||
//now we can use flexible syntax with
|
||||
//member function has same name as parameter
|
||||
s.archive(this->s, i, vl, ll);
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ struct MyStruct {
|
||||
void serialize(S& s) {
|
||||
//now we can use flexible syntax with
|
||||
s.archive(i, e, fs);
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -15,8 +15,10 @@ namespace MyTypes {
|
||||
struct Vec3 { float x, y, z; };
|
||||
|
||||
struct Weapon {
|
||||
std::string name;
|
||||
int16_t damage;
|
||||
std::string name{};
|
||||
int16_t damage{};
|
||||
Weapon() = default;
|
||||
Weapon(const std::string& _name, int16_t dmg):name{_name}, damage{dmg} {}
|
||||
private:
|
||||
//define serialize function as private, and give access to bitsery
|
||||
friend bitsery::Access;
|
||||
117
examples/inheritance.cpp
Normal file
117
examples/inheritance.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
//
|
||||
//this example coverls all the corner cases that can happen using inherintace
|
||||
//in reality virtual inherintance is usually avoided, so your code would look much simpler.
|
||||
//
|
||||
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
|
||||
//include inheritance extension
|
||||
//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. for normal inheritance it is not required
|
||||
//it can be created either internally (via configuration) or externally (pointer to context).
|
||||
#include <bitsery/ext/inheritance.h>
|
||||
|
||||
using bitsery::ext::BaseClass;
|
||||
using bitsery::ext::VirtualBaseClass;
|
||||
|
||||
struct Base {
|
||||
uint8_t x{};
|
||||
//Base doesn't have to be polymorphic class, inheritance works at compile-time.
|
||||
};
|
||||
template <typename S>
|
||||
void serialize(S& s, Base& o) {
|
||||
s.value1b(o.x);
|
||||
}
|
||||
|
||||
struct Derive1:virtual Base {// virtually inherits from base
|
||||
uint8_t y1{};
|
||||
};
|
||||
template <typename S>
|
||||
void serialize(S& s, Derive1& o) {
|
||||
//define virtual inheritance, it will not compile if InheritanceContext is not defined in serializer/deserialzer
|
||||
s.ext(o, VirtualBaseClass<Base>{});
|
||||
s.value1b(o.y1);
|
||||
}
|
||||
|
||||
//to make it more interesting, serialize private member
|
||||
struct Derived2:virtual Base {
|
||||
explicit Derived2(uint8_t y):y2{y} {}
|
||||
|
||||
uint8_t getY2() const {
|
||||
return y2;
|
||||
};
|
||||
private:
|
||||
friend bitsery::Access;
|
||||
uint8_t y2{};
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
//notice virtual inheritance
|
||||
s.ext(*this, VirtualBaseClass<Base>{});
|
||||
s.value1b(y2);
|
||||
}
|
||||
};
|
||||
|
||||
struct MultipleInheritance: Derive1, Derived2 {
|
||||
explicit MultipleInheritance(uint8_t y2):Derived2{y2} {}
|
||||
uint8_t z{};
|
||||
};
|
||||
template <typename S>
|
||||
void serialize(S& s, MultipleInheritance& o) {
|
||||
//has two bases, serialize them separately
|
||||
s.ext(o, BaseClass<Derive1>{});
|
||||
s.ext(o, BaseClass<Derived2>{});
|
||||
s.value1b(o.z);
|
||||
}
|
||||
|
||||
namespace bitsery {
|
||||
// call to serialize function with Derived2 and MultipleInheritance is ambiguous,
|
||||
// it matches two serialize functions: Base classes non-member fnc and Derived2 member fnc
|
||||
// we need explicitly select which function to use
|
||||
template <>
|
||||
struct SelectSerializeFnc<Derived2>:UseMemberFnc {};
|
||||
|
||||
//multiple inheritance has non-member serialize function defined
|
||||
template <>
|
||||
struct SelectSerializeFnc<MultipleInheritance>:UseNonMemberFnc {};
|
||||
}
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
// since in this example we're using virtual inheritance we need InheritanceContext as well.
|
||||
// InheritanceContext is default constructable and has no useful information outside of serializer/deserializer
|
||||
// lets create it internally, via configuration
|
||||
struct ConfigWithContext:DefaultConfig {
|
||||
//always add internal contexts to tuple
|
||||
using InternalContext = std::tuple<ext::InheritanceContext>;
|
||||
};
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using Writer = AdapterWriter<OutputBufferAdapter<Buffer>, ConfigWithContext>;
|
||||
using Reader = AdapterReader<InputBufferAdapter<Buffer>, ConfigWithContext>;
|
||||
|
||||
int main() {
|
||||
|
||||
MultipleInheritance data{98};
|
||||
data.x = 254;
|
||||
data.y1 = 47;
|
||||
data.z = 1;
|
||||
|
||||
Buffer buf{};
|
||||
|
||||
BasicSerializer<Writer> ser{OutputBufferAdapter<Buffer>{buf}, nullptr};
|
||||
ser.object(data);
|
||||
auto writtenSize = AdapterAccess::getWriter(ser).writtenBytesCount();
|
||||
|
||||
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());
|
||||
|
||||
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
|
||||
}
|
||||
160
examples/raw_pointers.cpp
Normal file
160
examples/raw_pointers.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
#include <bitsery/bitsery.h>
|
||||
#include <bitsery/adapter/buffer.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
|
||||
//include pointers extension
|
||||
//this header contains multiple extensions for different pointer types and pointer linking context,
|
||||
//that validates pointer ownership and checks if there are and no dangling pointers after serialization/deserialization.
|
||||
//dangling pointer in this context means, that non-owning pointer points to data, that was not serialized.
|
||||
#include <bitsery/ext/pointer.h>
|
||||
|
||||
using bitsery::ext::ReferencedByPointer;
|
||||
using bitsery::ext::PointerObserver;
|
||||
using bitsery::ext::PointerOwner;
|
||||
using bitsery::ext::PointerType ;
|
||||
|
||||
enum class MyEnum:uint16_t { V1,V2,V3 };
|
||||
struct MyStruct {
|
||||
MyStruct(uint32_t i_, MyEnum e_, std::vector<float> fs_)
|
||||
:i{i_},
|
||||
e{e_},
|
||||
fs{fs_} {}
|
||||
MyStruct():MyStruct{0, MyEnum::V1, {}} {}
|
||||
uint32_t i;
|
||||
MyEnum e;
|
||||
std::vector<float> fs;
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, MyStruct& o) {
|
||||
s.value4b(o.i);
|
||||
s.value2b(o.e);
|
||||
s.container4b(o.fs, 10);
|
||||
}
|
||||
|
||||
//our test data
|
||||
struct Test1Data {
|
||||
//regular data, nothing fancy here
|
||||
MyStruct o1;
|
||||
int32_t i1;
|
||||
//these container elements can be referenced by pointers
|
||||
std::vector<MyStruct> vdata;
|
||||
//container that holds non owning pointers (observers),
|
||||
std::vector<MyStruct*> vptr;
|
||||
//treat it as is observer
|
||||
MyStruct* po1;
|
||||
//we treat this as owner (responsible for allocation/deallocation
|
||||
int32_t* pi1;
|
||||
|
||||
private:
|
||||
friend bitsery::Access;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
//just a regular fields
|
||||
s.object(o1);
|
||||
s.value4b(i1);
|
||||
|
||||
//set container elements to be candidates for non-owning pointers
|
||||
s.container(vdata, 100, [&s](MyStruct& d){
|
||||
s.ext(d, ReferencedByPointer{});
|
||||
});
|
||||
//contains non owning pointers
|
||||
//
|
||||
//IMPORTANT !!!
|
||||
//ALWAYS ACCEPT BY REFERENCE like this: T* (&obj)
|
||||
//if using c++14, then auto& always works.
|
||||
//
|
||||
//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.ext(d, PointerObserver{});
|
||||
});
|
||||
//observer
|
||||
s.ext(po1, PointerObserver{});
|
||||
//owner, mark it as not null
|
||||
s.ext4b(pi1, PointerOwner{PointerType::NotNull});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
using namespace bitsery;
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = 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:
|
||||
// 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
|
||||
Test1Data data{};
|
||||
data.vdata.emplace_back(8941, MyEnum::V1, std::vector<float>{4.4f});
|
||||
data.vdata.emplace_back(15478, MyEnum::V2, std::vector<float>{15.0f});
|
||||
data.vdata.emplace_back(59, MyEnum::V3, std::vector<float>{-8.5f, 0.045f});
|
||||
//container of non owning pointers (observers)
|
||||
data.vptr.emplace_back(nullptr);
|
||||
data.vptr.emplace_back(std::addressof(data.vdata[0]));
|
||||
data.vptr.emplace_back(std::addressof(data.vdata[2]));
|
||||
//regular fields
|
||||
data.o1 = MyStruct{4, MyEnum::V2, {57.078f}};
|
||||
data.i1 = 9455;
|
||||
//observer
|
||||
data.po1 = std::addressof(data.vdata[1]);
|
||||
//owning pointer
|
||||
data.pi1 = new int32_t{};
|
||||
|
||||
//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
|
||||
{
|
||||
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();
|
||||
|
||||
//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(ctx.isValid());
|
||||
}
|
||||
|
||||
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);
|
||||
//check if everything went find
|
||||
assert(r.error() == ReaderError::NoError && r.isCompletedSuccessfully());
|
||||
//also check for dangling pointers, after deserialization
|
||||
assert(ctx.isValid());
|
||||
}
|
||||
//owning pointers owns data
|
||||
assert(*res.pi1 == *data.pi1);
|
||||
assert(res.pi1 != data.pi1);
|
||||
//observers, points to other data
|
||||
assert(res.vptr[0] == nullptr);
|
||||
assert(res.vptr[1] == std::addressof(res.vdata[0]));
|
||||
assert(res.vptr[2] == std::addressof(res.vdata[2]));
|
||||
assert(res.po1 == std::addressof(res.vdata[1]));
|
||||
|
||||
//delete raw owning pointers
|
||||
delete data.pi1;
|
||||
delete res.pi1;
|
||||
}
|
||||
269
examples/smart_pointers_with_polymorphism.cpp
Normal file
269
examples/smart_pointers_with_polymorphism.cpp
Normal file
@@ -0,0 +1,269 @@
|
||||
//
|
||||
// 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, 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](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> {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//use bitsery namespace for convenience
|
||||
using namespace bitsery;
|
||||
|
||||
//some helper types
|
||||
using Buffer = std::vector<uint8_t>;
|
||||
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
||||
using InputAdapter = InputBufferAdapter<Buffer>;
|
||||
|
||||
//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 = BasicSerializer<AdapterWriter<OutputAdapter, DefaultConfig>, TContext>;
|
||||
using MyDeserializer = BasicDeserializer<AdapterReader<InputAdapter, DefaultConfig>, 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{};
|
||||
{
|
||||
TContext ctx{};
|
||||
MySerializer ser{OutputAdapter{buffer}, &ctx};
|
||||
//STEP 2
|
||||
//bind serializer with base polymorphic types, it will go through all reachable classes that is defined in first step.
|
||||
//so you dont need to add Rectangle to reach for RoundedRectangle
|
||||
std::get<1>(ctx).registerBasesList(ser, ext::PolymorphicClassesList<Shape>{});
|
||||
//serialize our data
|
||||
ser.object(data);
|
||||
auto &w = AdapterAccess::getWriter(ser);
|
||||
w.flush();
|
||||
writtenSize = w.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{};
|
||||
MyDeserializer des{InputAdapter{buffer.begin(), writtenSize}, &ctx};
|
||||
//same as in serialization
|
||||
std::get<1>(ctx).registerBasesList(des, ext::PolymorphicClassesList<Shape>{});
|
||||
//serialize our data
|
||||
des.object(res);
|
||||
auto &r = AdapterAccess::getReader(des);
|
||||
//check if everything went find
|
||||
assert(r.error() == ReaderError::NoError && r.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,18 +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}${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,9 +20,8 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_ADAPTERS_INPUT_BUFFER_ADAPTER_H
|
||||
#define BITSERY_ADAPTERS_INPUT_BUFFER_ADAPTER_H
|
||||
#ifndef BITSERY_ADAPTER_BUFFER_H
|
||||
#define BITSERY_ADAPTER_BUFFER_H
|
||||
|
||||
#include "../details/adapter_common.h"
|
||||
#include "../traits/core/traits.h"
|
||||
@@ -30,15 +29,14 @@
|
||||
namespace bitsery {
|
||||
|
||||
//base class that stores container iterators, and is required for session support (for reading sessions data)
|
||||
template <typename Buffer>
|
||||
template<typename Buffer>
|
||||
class BufferIterators {
|
||||
protected:
|
||||
using TIterator = typename traits::BufferAdapterTraits<Buffer>::TIterator;
|
||||
|
||||
BufferIterators(TIterator begin, TIterator end)
|
||||
:posIt{begin},
|
||||
endIt{end}
|
||||
{
|
||||
: posIt{begin},
|
||||
endIt{end} {
|
||||
}
|
||||
|
||||
friend details::SessionAccess;
|
||||
@@ -47,25 +45,26 @@ namespace bitsery {
|
||||
TIterator endIt;
|
||||
};
|
||||
|
||||
|
||||
template <typename Buffer>
|
||||
class InputBufferAdapter: public BufferIterators<Buffer> {
|
||||
template<typename Buffer>
|
||||
class InputBufferAdapter : public BufferIterators<Buffer> {
|
||||
public:
|
||||
|
||||
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(details::IsDefined<TValue>::value,
|
||||
"Please define BufferAdapterTraits or include from <bitsery/traits/...>");
|
||||
static_assert(traits::ContainerTraits<Buffer>::isContiguous,
|
||||
"BufferAdapter only works with contiguous containers");
|
||||
|
||||
InputBufferAdapter(TIterator begin, TIterator end): BufferIterators<Buffer>(begin, end)
|
||||
{
|
||||
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)) {
|
||||
}
|
||||
|
||||
void read(TValue* data, size_t size) {
|
||||
void read(TValue *data, size_t size) {
|
||||
//for optimization
|
||||
auto tmp = this->posIt;
|
||||
this->posIt += size;
|
||||
@@ -75,7 +74,6 @@ namespace bitsery {
|
||||
this->posIt -= size;
|
||||
//set everything to zeros
|
||||
std::memset(data, 0, size);
|
||||
|
||||
if (error() == ReaderError::NoError)
|
||||
setError(ReaderError::DataOverflow);
|
||||
}
|
||||
@@ -101,24 +99,66 @@ namespace bitsery {
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Buffer>
|
||||
class UnsafeInputBufferAdapter : public BufferIterators<Buffer> {
|
||||
public:
|
||||
|
||||
template<typename Container>
|
||||
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");
|
||||
|
||||
UnsafeInputBufferAdapter(TIterator begin, TIterator end) : BufferIterators<Buffer>(begin, end) {
|
||||
}
|
||||
|
||||
UnsafeInputBufferAdapter(TIterator begin, size_t size)
|
||||
: BufferIterators<Buffer>(begin, std::next(begin, size)) {
|
||||
}
|
||||
|
||||
void read(TValue *data, size_t size) {
|
||||
//for optimization
|
||||
auto tmp = this->posIt;
|
||||
this->posIt += size;
|
||||
assert(std::distance(this->posIt, this->endIt) >= 0);
|
||||
std::memcpy(data, std::addressof(*tmp), size);
|
||||
}
|
||||
|
||||
ReaderError error() const {
|
||||
return err;
|
||||
}
|
||||
|
||||
void setError(ReaderError error) {
|
||||
err = error;
|
||||
}
|
||||
|
||||
bool isCompletedSuccessfully() const {
|
||||
return this->posIt == this->endIt;
|
||||
}
|
||||
|
||||
private:
|
||||
ReaderError err = ReaderError::NoError;
|
||||
};
|
||||
|
||||
template<typename Buffer>
|
||||
class OutputBufferAdapter {
|
||||
public:
|
||||
|
||||
using TIterator = typename traits::BufferAdapterTraits<Container>::TIterator;
|
||||
using TValue = typename traits::BufferAdapterTraits<Container>::TValue;
|
||||
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(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(Container &buffer)
|
||||
: _buffer{buffer}
|
||||
{
|
||||
OutputBufferAdapter(Buffer &buffer)
|
||||
: _buffer{std::addressof(buffer)} {
|
||||
|
||||
init(TResizable{});
|
||||
}
|
||||
|
||||
|
||||
void write(const TValue *data, size_t size) {
|
||||
writeInternal(data, size, TResizable{});
|
||||
}
|
||||
@@ -128,15 +168,15 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
size_t writtenBytesCount() const {
|
||||
return static_cast<size_t>(std::distance(std::begin(_buffer), _outIt));
|
||||
return static_cast<size_t>(std::distance(std::begin(*_buffer), _outIt));
|
||||
}
|
||||
|
||||
private:
|
||||
using TResizable = std::integral_constant<bool, traits::ContainerTraits<Container>::isResizable>;
|
||||
using TResizable = std::integral_constant<bool, traits::ContainerTraits<Buffer>::isResizable>;
|
||||
|
||||
Container &_buffer;
|
||||
TIterator _outIt;
|
||||
TIterator _end;
|
||||
Buffer *_buffer;
|
||||
TIterator _outIt{};
|
||||
TIterator _end{};
|
||||
|
||||
/*
|
||||
* resizable buffer
|
||||
@@ -144,28 +184,39 @@ namespace bitsery {
|
||||
|
||||
void init(std::true_type) {
|
||||
//resize buffer immediately, because we need output iterator at valid position
|
||||
if (traits::ContainerTraits<Container>::size(_buffer) == 0u) {
|
||||
traits::BufferAdapterTraits<Container>::increaseBufferSize(_buffer);
|
||||
if (traits::ContainerTraits<Buffer>::size(*_buffer) == 0u) {
|
||||
traits::BufferAdapterTraits<Buffer>::increaseBufferSize(*_buffer);
|
||||
}
|
||||
_end = std::end(_buffer);
|
||||
_outIt = std::begin(_buffer);
|
||||
_end = std::end(*_buffer);
|
||||
_outIt = std::begin(*_buffer);
|
||||
}
|
||||
|
||||
void writeInternal(const TValue *data, const size_t size, std::true_type) {
|
||||
//optimization
|
||||
#if defined(_MSC_VER) && (_ITERATOR_DEBUG_LEVEL > 0)
|
||||
using TDistance = typename std::iterator_traits<TIterator>::difference_type;
|
||||
if (std::distance(_outIt , _end) >= static_cast<TDistance>(size)) {
|
||||
std::memcpy(std::addressof(*_outIt), data, size);
|
||||
_outIt += size;
|
||||
#else
|
||||
auto tmp = _outIt;
|
||||
_outIt += size;
|
||||
if (std::distance(_outIt , _end) >= 0) {
|
||||
if (std::distance(_outIt, _end) >= 0) {
|
||||
std::memcpy(std::addressof(*tmp), data, size);
|
||||
#endif
|
||||
} else {
|
||||
#if defined(_MSC_VER) && (_ITERATOR_DEBUG_LEVEL > 0)
|
||||
|
||||
#else
|
||||
_outIt -= size;
|
||||
#endif
|
||||
//get current position before invalidating iterators
|
||||
const auto pos = std::distance(std::begin(_buffer), _outIt);
|
||||
const auto pos = std::distance(std::begin(*_buffer), _outIt);
|
||||
//increase container size
|
||||
traits::BufferAdapterTraits<Container>::increaseBufferSize(_buffer);
|
||||
traits::BufferAdapterTraits<Buffer>::increaseBufferSize(*_buffer);
|
||||
//restore iterators
|
||||
_end = std::end(_buffer);
|
||||
_outIt = std::next(std::begin(_buffer), pos);
|
||||
_end = std::end(*_buffer);
|
||||
_outIt = std::next(std::begin(*_buffer), pos);
|
||||
|
||||
writeInternal(data, size, std::true_type{});
|
||||
}
|
||||
@@ -175,8 +226,8 @@ namespace bitsery {
|
||||
* non resizable buffer
|
||||
*/
|
||||
void init(std::false_type) {
|
||||
_outIt = std::begin(_buffer);
|
||||
_end = std::end(_buffer);
|
||||
_outIt = std::begin(*_buffer);
|
||||
_end = std::end(*_buffer);
|
||||
}
|
||||
|
||||
void writeInternal(const TValue *data, size_t size, std::false_type) {
|
||||
@@ -188,7 +239,6 @@ namespace bitsery {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_ADAPTERS_INPUT_BUFFER_ADAPTER_H
|
||||
#endif //BITSERY_ADAPTER_BUFFER_H
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_ADAPTERS_DYNAMIC_STREAM_H
|
||||
#define BITSERY_ADAPTERS_DYNAMIC_STREAM_H
|
||||
#ifndef BITSERY_ADAPTER_STREAM_H
|
||||
#define BITSERY_ADAPTER_STREAM_H
|
||||
|
||||
#include "../details/adapter_common.h"
|
||||
#include "../traits/core/traits.h"
|
||||
#include "../traits/array.h"
|
||||
#include <ios>
|
||||
|
||||
|
||||
@@ -38,32 +38,42 @@ namespace bitsery {
|
||||
using TIterator = void;//TIterator is used with sessions, but streams cannot be used with sessions
|
||||
|
||||
BasicInputStreamAdapter(std::basic_ios<TChar, CharTraits>& istream)
|
||||
:_ios{istream} {}
|
||||
:_ios{std::addressof(istream)} {}
|
||||
|
||||
template <typename T>
|
||||
void read(T& data) {
|
||||
read(reinterpret_cast<TValue*>(&data), sizeof(T));
|
||||
}
|
||||
|
||||
void read(TValue* data, size_t size) {
|
||||
if (static_cast<size_t>(_ios.rdbuf()->sgetn( data , size )) != 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ReaderError error() const {
|
||||
if (_ios.good())
|
||||
if (_ios->good())
|
||||
return ReaderError::NoError;
|
||||
return _ios.eof()
|
||||
return _ios->eof()
|
||||
? ReaderError::DataOverflow
|
||||
: ReaderError::ReadingError;
|
||||
}
|
||||
bool isCompletedSuccessfully() const {
|
||||
if (error() == ReaderError::NoError) {
|
||||
return _ios.rdbuf()->sgetc() == CharTraits::eof();
|
||||
return _ios->rdbuf()->sgetc() == CharTraits::eof();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void setError(ReaderError error) {
|
||||
void setError(ReaderError ) {
|
||||
//has no effect when using
|
||||
}
|
||||
|
||||
private:
|
||||
std::basic_ios<TChar, CharTraits>& _ios;
|
||||
std::basic_ios<TChar, CharTraits>* _ios;
|
||||
};
|
||||
|
||||
template <typename TChar, typename CharTraits>
|
||||
@@ -72,30 +82,156 @@ namespace bitsery {
|
||||
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{ostream} {}
|
||||
BasicOutputStreamAdapter(std::basic_ios<TChar, CharTraits>& ostream)
|
||||
:_ios{std::addressof(ostream)} {}
|
||||
|
||||
template <typename T>
|
||||
void write(const T& data) {
|
||||
write(reinterpret_cast<const TValue*>(&data), sizeof(T));
|
||||
}
|
||||
|
||||
void write(const TValue* data, size_t size) {
|
||||
//for optimization
|
||||
_ios.rdbuf()->sputn( data , size );
|
||||
_ios->rdbuf()->sputn( data , size );
|
||||
}
|
||||
|
||||
void flush() {
|
||||
if (auto ostream = dynamic_cast<std::basic_ostream<TChar, CharTraits>*>(&_ios))
|
||||
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 0;
|
||||
return 0u;
|
||||
}
|
||||
|
||||
//this method is only for stream writing
|
||||
bool isValidState() const {
|
||||
return !_ios.bad();
|
||||
return !_ios->bad();
|
||||
}
|
||||
|
||||
private:
|
||||
std::basic_ios<TChar, CharTraits>& _ios;
|
||||
std::basic_ios<TChar, CharTraits>* _ios;
|
||||
};
|
||||
|
||||
template <typename TChar, typename CharTraits, typename TBuffer = std::array<TChar, 256>>
|
||||
class BasicBufferedOutputStreamAdapter {
|
||||
public:
|
||||
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),
|
||||
_buf{},
|
||||
_outIt{}
|
||||
{
|
||||
init(bufferSize, TResizable{});
|
||||
}
|
||||
|
||||
//we need to explicitly declare move logic, in case buffer is static, because after move it will be invalidated
|
||||
BasicBufferedOutputStreamAdapter(const BasicBufferedOutputStreamAdapter&) = delete;
|
||||
BasicBufferedOutputStreamAdapter& operator = (const BasicBufferedOutputStreamAdapter&) = delete;
|
||||
|
||||
BasicBufferedOutputStreamAdapter(BasicBufferedOutputStreamAdapter&& rhs)
|
||||
: _adapter{std::move(rhs._adapter)},
|
||||
_buf{},
|
||||
_outIt{}
|
||||
{
|
||||
auto size = std::distance(std::begin(rhs._buf), rhs._outIt);
|
||||
_buf = std::move(rhs._buf);
|
||||
_outIt = std::next(std::begin(_buf), size);
|
||||
};
|
||||
|
||||
BasicBufferedOutputStreamAdapter& operator = (BasicBufferedOutputStreamAdapter&& rhs) {
|
||||
_adapter = std::move(rhs._adapter);
|
||||
//get current written size, before move
|
||||
auto size = std::distance(std::begin(rhs._buf), rhs._outIt);
|
||||
_buf = std::move(rhs._buf);
|
||||
_outIt = std::next(std::begin(_buf), size);
|
||||
return *this;
|
||||
};
|
||||
|
||||
~BasicBufferedOutputStreamAdapter() = default;
|
||||
|
||||
template <typename T>
|
||||
void write(const T& data) {
|
||||
auto tmp = _outIt;
|
||||
|
||||
#if defined(_MSC_VER) && (_ITERATOR_DEBUG_LEVEL > 0)
|
||||
using TDistance = typename std::iterator_traits<BufferIt>::difference_type;
|
||||
if (std::distance(_outIt , std::end(_buf)) >= static_cast<TDistance>(size)) {
|
||||
*reinterpret_cast<T*>(std::addressof(*tmp)) = data;
|
||||
_outIt += sizeof(T);
|
||||
#else
|
||||
_outIt += sizeof(T);
|
||||
if (std::distance(_outIt , std::end(_buf)) >= 0) {
|
||||
*reinterpret_cast<T*>(std::addressof(*tmp)) = data;
|
||||
#endif
|
||||
} else {
|
||||
//when buffer is full write out to stream
|
||||
_outIt = std::begin(_buf);
|
||||
_adapter.write(std::addressof(*_outIt), static_cast<size_t>(std::distance(_outIt, tmp)));
|
||||
_adapter.write(reinterpret_cast<const TValue*>(&data), sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
void write(const TValue* data, size_t size) {
|
||||
auto tmp = _outIt;
|
||||
|
||||
#if defined(_MSC_VER) && (_ITERATOR_DEBUG_LEVEL > 0)
|
||||
using TDistance = typename std::iterator_traits<BufferIt>::difference_type;
|
||||
if (std::distance(_outIt , std::end(_buf)) >= static_cast<TDistance>(size)) {
|
||||
std::memcpy(std::addressof(*_outIt), data, size);
|
||||
_outIt += size;
|
||||
#else
|
||||
_outIt += size;
|
||||
if (std::distance(_outIt , std::end(_buf)) >= 0) {
|
||||
std::memcpy(std::addressof(*tmp), data, size);
|
||||
#endif
|
||||
} else {
|
||||
//when buffer is full write out to stream
|
||||
_outIt = std::begin(_buf);
|
||||
_adapter.write(std::addressof(*_outIt), static_cast<size_t>(std::distance(_outIt, tmp)));
|
||||
_adapter.write(data, size);
|
||||
}
|
||||
}
|
||||
|
||||
void flush() {
|
||||
auto begin = std::begin(_buf);
|
||||
_adapter.write(std::addressof(*begin), static_cast<size_t>(std::distance(begin, _outIt)));
|
||||
_outIt = begin;
|
||||
_adapter.flush();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
void init (size_t, std::false_type) {
|
||||
_outIt = std::begin(_buf);
|
||||
}
|
||||
|
||||
BasicOutputStreamAdapter<TChar, CharTraits> _adapter;
|
||||
TBuffer _buf;
|
||||
BufferIt _outIt;
|
||||
};
|
||||
|
||||
template <typename TChar, typename CharTraits>
|
||||
@@ -112,11 +248,12 @@ namespace bitsery {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
//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 OutputBufferedStreamAdapter = BasicBufferedOutputStreamAdapter<char, std::char_traits<char>>;
|
||||
}
|
||||
|
||||
#endif //BITSERY_ADAPTERS_DYNAMIC_STREAM_H
|
||||
#endif //BITSERY_ADAPTER_STREAM_H
|
||||
|
||||
@@ -20,16 +20,13 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
|
||||
#ifndef BITSERY_BASIC_READER_H
|
||||
#define BITSERY_BASIC_READER_H
|
||||
#ifndef BITSERY_ADAPTER_READER_H
|
||||
#define BITSERY_ADAPTER_READER_H
|
||||
|
||||
#include "details/sessions.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
template <typename TReader>
|
||||
@@ -39,7 +36,7 @@ namespace bitsery {
|
||||
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/...>");
|
||||
@@ -63,7 +60,6 @@ namespace bitsery {
|
||||
|
||||
~AdapterReader() noexcept = default;
|
||||
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void readBytes(T &v) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
@@ -99,7 +95,8 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
void setError(ReaderError error) {
|
||||
return _inputAdapter.setError(error);
|
||||
if (this->error() == ReaderError::NoError)
|
||||
_inputAdapter.setError(error);
|
||||
}
|
||||
|
||||
void beginSession() {
|
||||
@@ -114,17 +111,13 @@ namespace bitsery {
|
||||
}
|
||||
}
|
||||
|
||||
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::DisabledSessionsReader<AdapterReader<InputAdapter, Config>>>::type
|
||||
_session;
|
||||
|
||||
template<typename T>
|
||||
@@ -142,16 +135,18 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void _swapDataBits(T *v, size_t count, std::false_type) {
|
||||
void _swapDataBits(T *, size_t , std::false_type) {
|
||||
//empty function because no swap is required
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename TReader>
|
||||
struct AdapterReaderBitPackingWrapper {
|
||||
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;
|
||||
@@ -198,7 +193,6 @@ namespace bitsery {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
void readBits(T &v, size_t bitsCount) {
|
||||
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
|
||||
@@ -266,4 +260,4 @@ namespace bitsery {
|
||||
};
|
||||
}
|
||||
|
||||
#endif //BITSERY_BUFFER_READER_H
|
||||
#endif //BITSERY_ADAPTER_READER_H
|
||||
|
||||
@@ -20,23 +20,22 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
|
||||
#ifndef BITSERY_BASIC_WRITER_H
|
||||
#define BITSERY_BASIC_WRITER_H
|
||||
#ifndef BITSERY_ADAPTER_WRITER_H
|
||||
#define BITSERY_ADAPTER_WRITER_H
|
||||
|
||||
#include "details/sessions.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
struct MeasureSize {
|
||||
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>(), "");
|
||||
@@ -95,6 +94,8 @@ namespace bitsery {
|
||||
size_t _sessionsBytesCount{};
|
||||
};
|
||||
|
||||
//helper type for default config
|
||||
using MeasureSize = BasicMeasureSize<DefaultConfig>;
|
||||
|
||||
template <typename TWriter>
|
||||
class AdapterWriterBitPackingWrapper;
|
||||
@@ -103,7 +104,7 @@ namespace bitsery {
|
||||
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/...>");
|
||||
@@ -169,10 +170,6 @@ namespace bitsery {
|
||||
_session.end(*this);
|
||||
}
|
||||
|
||||
const OutputAdapter& adapter() const {
|
||||
return _outputAdapter;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class AdapterWriterBitPackingWrapper<AdapterWriter<OutputAdapter, Config>>;
|
||||
template<typename T>
|
||||
@@ -207,6 +204,7 @@ namespace bitsery {
|
||||
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;
|
||||
@@ -334,4 +332,4 @@ namespace bitsery {
|
||||
};
|
||||
}
|
||||
|
||||
#endif //BITSERY_BASIC_WRITER_H
|
||||
#endif //BITSERY_ADAPTER_WRITER_H
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#define BITSERY_BITSERY_H
|
||||
|
||||
#define BITSERY_MAJOR_VERSION 4
|
||||
#define BITSERY_MINOR_VERSION 0
|
||||
#define BITSERY_MINOR_VERSION 3
|
||||
#define BITSERY_PATCH_VERSION 0
|
||||
|
||||
#define BITSERY_QUOTE_MACRO(name) #name
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#ifndef BITSERY_COMMON_H
|
||||
#define BITSERY_COMMON_H
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
/*
|
||||
@@ -41,7 +43,10 @@ namespace bitsery {
|
||||
//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<>;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -31,22 +31,25 @@
|
||||
namespace bitsery {
|
||||
|
||||
|
||||
template<typename TAdapterReader>
|
||||
template<typename TAdapterReader, typename TContext = void>
|
||||
class BasicDeserializer {
|
||||
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>;
|
||||
TAdapterReader, AdapterReaderBitPackingWrapper<TAdapterReader>>::type, TContext>;
|
||||
|
||||
static_assert(details::IsSpecializationOf<typename TReader::TConfig::InternalContext, std::tuple>::value,
|
||||
"Config::InternalContext must be std::tuple");
|
||||
|
||||
template <typename ReaderParam>
|
||||
explicit BasicDeserializer(ReaderParam&& r, void* context = nullptr)
|
||||
explicit BasicDeserializer(ReaderParam&& r, TContext* context = nullptr)
|
||||
: _reader{std::forward<ReaderParam>(r)},
|
||||
_context{context}
|
||||
_context{context},
|
||||
_internalContext{}
|
||||
{
|
||||
};
|
||||
}
|
||||
|
||||
//copying disabled
|
||||
BasicDeserializer(const BasicDeserializer&) = delete;
|
||||
@@ -60,10 +63,20 @@ namespace bitsery {
|
||||
* get serialization context.
|
||||
* this is optional, but might be required for some specific deserialization flows.
|
||||
*/
|
||||
void* context() {
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* object function
|
||||
*/
|
||||
@@ -76,7 +89,7 @@ namespace bitsery {
|
||||
template<typename T, typename Fnc>
|
||||
void object(T &&obj, Fnc &&fnc) {
|
||||
fnc(std::forward<T>(obj));
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* functionality, that enables simpler serialization syntax, by including additional header
|
||||
@@ -117,7 +130,7 @@ namespace bitsery {
|
||||
static_assert(traits::ExtensionTraits<Ext,T>::SupportLambdaOverload,
|
||||
"extension doesn't support overload with lambda");
|
||||
extension.deserialize(*this, _reader, obj, std::forward<Fnc>(fnc));
|
||||
};
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, typename Ext>
|
||||
void ext(T &obj, const Ext &extension) {
|
||||
@@ -127,7 +140,7 @@ namespace bitsery {
|
||||
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);});
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T, typename Ext>
|
||||
void ext(T &obj, const Ext &extension) {
|
||||
@@ -137,7 +150,7 @@ namespace bitsery {
|
||||
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); });
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* boolValue
|
||||
@@ -263,16 +276,16 @@ namespace bitsery {
|
||||
void value8b(T &&v) { value<8>(std::forward<T>(v)); }
|
||||
|
||||
template<typename T, typename Ext>
|
||||
void ext1b(T &v, Ext &&extension) { ext<1, T, Ext>(v, std::forward<Ext>(extension)); };
|
||||
void ext1b(T &v, Ext &&extension) { ext<1, T, Ext>(v, std::forward<Ext>(extension)); }
|
||||
|
||||
template<typename T, typename Ext>
|
||||
void ext2b(T &v, Ext &&extension) { ext<2, T, Ext>(v, std::forward<Ext>(extension)); };
|
||||
void ext2b(T &v, Ext &&extension) { ext<2, T, Ext>(v, std::forward<Ext>(extension)); }
|
||||
|
||||
template<typename T, typename Ext>
|
||||
void ext4b(T &v, Ext &&extension) { ext<4, T, Ext>(v, std::forward<Ext>(extension)); };
|
||||
void ext4b(T &v, Ext &&extension) { ext<4, T, Ext>(v, std::forward<Ext>(extension)); }
|
||||
|
||||
template<typename T, typename Ext>
|
||||
void ext8b(T &v, Ext &&extension) { ext<8, T, Ext>(v, std::forward<Ext>(extension)); };
|
||||
void ext8b(T &v, Ext &&extension) { ext<8, T, Ext>(v, std::forward<Ext>(extension)); }
|
||||
|
||||
template<typename T>
|
||||
void text1b(T &str, size_t maxSize) { text<1>(str, maxSize); }
|
||||
@@ -320,7 +333,9 @@ namespace bitsery {
|
||||
friend AdapterAccess;
|
||||
|
||||
TAdapterReader _reader;
|
||||
void* _context;
|
||||
TContext* _context;
|
||||
typename TReader::TConfig::InternalContext _internalContext;
|
||||
|
||||
|
||||
//process value types
|
||||
//false_type means that we must process all elements individually
|
||||
@@ -328,7 +343,7 @@ namespace bitsery {
|
||||
void procContainer(It first, It last, std::false_type) {
|
||||
for (; first != last; ++first)
|
||||
value<VSIZE>(*first);
|
||||
};
|
||||
}
|
||||
|
||||
//process value types
|
||||
//true_type means, that we can copy whole buffer
|
||||
@@ -338,21 +353,21 @@ namespace bitsery {
|
||||
using TIntegral = typename details::IntegralFromFundamental<TValue>::TValue;
|
||||
if (first != last)
|
||||
_reader.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);
|
||||
};
|
||||
}
|
||||
|
||||
//process object types
|
||||
template<typename It>
|
||||
void procContainer(It first, It last) {
|
||||
for (; first != last; ++first)
|
||||
object(*first);
|
||||
};
|
||||
}
|
||||
|
||||
template <size_t VSIZE, typename T>
|
||||
void procText(T& str, size_t length) {
|
||||
@@ -390,7 +405,7 @@ namespace bitsery {
|
||||
template <typename Fnc>
|
||||
void procEnableBitPacking(const Fnc& fnc, std::false_type) {
|
||||
//create serializer using bitpacking wrapper
|
||||
BasicDeserializer<AdapterReaderBitPackingWrapper<TAdapterReader>> tmp(_reader, _context);
|
||||
BPEnabledType tmp(_reader, _context);
|
||||
fnc(tmp);
|
||||
}
|
||||
|
||||
@@ -421,7 +436,7 @@ namespace bitsery {
|
||||
des.object(value);
|
||||
auto& r = AdapterAccess::getReader(des);
|
||||
return {r.error(), r.isCompletedSuccessfully()};
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#ifndef BITSERY_DETAILS_BUFFER_COMMON_H
|
||||
#define BITSERY_DETAILS_BUFFER_COMMON_H
|
||||
#ifndef BITSERY_DETAILS_ADAPTER_COMMON_H
|
||||
#define BITSERY_DETAILS_ADAPTER_COMMON_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
@@ -124,4 +124,4 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
|
||||
#endif //BITSERY_DETAILS_BUFFER_COMMON_H
|
||||
#endif //BITSERY_DETAILS_ADAPTER_COMMON_H
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#ifndef BITSERY_DETAILS_BOTH_COMMON_H
|
||||
#define BITSERY_DETAILS_BOTH_COMMON_H
|
||||
#ifndef BITSERY_DETAILS_ADAPTER_UTILS_H
|
||||
#define BITSERY_DETAILS_ADAPTER_UTILS_H
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
@@ -33,7 +33,8 @@ namespace bitsery {
|
||||
NoError,
|
||||
ReadingError, // this might be used with stream adapter
|
||||
DataOverflow,
|
||||
InvalidData
|
||||
InvalidData,
|
||||
InvalidPointer
|
||||
};
|
||||
|
||||
namespace details {
|
||||
@@ -83,4 +84,4 @@ namespace bitsery {
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_DETAILS_BOTH_COMMON_H
|
||||
#endif //BITSERY_DETAILS_ADAPTER_UTILS_H
|
||||
|
||||
@@ -117,31 +117,31 @@ namespace bitsery {
|
||||
template<typename S, typename T>
|
||||
void processMaxSize(S &s, T& data, size_t maxSize, std::true_type) {
|
||||
processContainer(s, data, maxSize);
|
||||
};
|
||||
}
|
||||
|
||||
//overload for const T&
|
||||
template<typename S, typename T>
|
||||
void processMaxSize(S &s, const T& data, size_t maxSize, std::true_type) {
|
||||
processContainer(s, const_cast<T&>(data), maxSize);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
//try to call serialize overload with maxsize, extensions use this technique
|
||||
template<typename S, typename T>
|
||||
void processMaxSize(S &s, T& data, size_t maxSize, std::false_type) {
|
||||
serialize(s, data, maxSize);
|
||||
};
|
||||
}
|
||||
|
||||
//overload for const T&
|
||||
template<typename S, typename T>
|
||||
void processMaxSize(S &s, const T& data, size_t maxSize, std::false_type) {
|
||||
serialize(s, const_cast<T&>(data), maxSize);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename S, typename T>
|
||||
void serialize(S &s, const MaxSize<T> &ms) {
|
||||
processMaxSize(s, ms.data, ms.maxSize, details::IsContainerTraitsDefined<typename std::decay<T>::type>{});
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#ifndef BITSERY_TRAITS_CORE_NOT_DEFINED_TYPE_H
|
||||
#define BITSERY_TRAITS_CORE_NOT_DEFINED_TYPE_H
|
||||
#ifndef BITSERY_DETAILS_NOT_DEFINED_TYPE_H
|
||||
#define BITSERY_DETAILS_NOT_DEFINED_TYPE_H
|
||||
|
||||
#include <iterator>
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace bitsery {
|
||||
struct NotDefinedType {
|
||||
//just swallow anything that is passed during creating
|
||||
template <typename ... T>
|
||||
NotDefinedType(T&& ...){};
|
||||
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
|
||||
@@ -52,6 +52,7 @@ namespace bitsery {
|
||||
friend int operator - (const NotDefinedType&, const NotDefinedType&) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int& operator*() {
|
||||
return data;
|
||||
}
|
||||
@@ -75,4 +76,4 @@ namespace std {
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
};
|
||||
}
|
||||
#endif //BITSERY_TRAITS_CORE_NOT_DEFINED_TYPE_H
|
||||
#endif //BITSERY_DETAILS_NOT_DEFINED_TYPE_H
|
||||
|
||||
@@ -25,9 +25,11 @@
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <tuple>
|
||||
#include "adapter_utils.h"
|
||||
#include "../traits/core/traits.h"
|
||||
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
//this allows to call private serialize method for the class
|
||||
@@ -39,18 +41,33 @@ namespace bitsery {
|
||||
}
|
||||
};
|
||||
|
||||
//when call to serialize function is ambiguous (member and non-member serialize function exists for a type)
|
||||
//specialize this class, by inheriting from either UseNonMemberFnc or UseMemberFnc
|
||||
//e.g.
|
||||
//template <> struct SelectSerializeFnc<MyDerivedClass>:UseMemberFnc {};
|
||||
template<typename T>
|
||||
struct SelectSerializeFnc : std::integral_constant<int, 0> {
|
||||
};
|
||||
|
||||
//types you need to inherit from when specializing SelectSerializeFnc class
|
||||
struct UseNonMemberFnc : std::integral_constant<int, 1> {
|
||||
};
|
||||
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) {
|
||||
template<typename Serializer>
|
||||
static typename Serializer::TWriter &getWriter(Serializer &s) {
|
||||
return s._writer;
|
||||
}
|
||||
|
||||
template <typename Deserializer>
|
||||
static typename Deserializer::TReader& getReader(Deserializer& s) {
|
||||
template<typename Deserializer>
|
||||
static typename Deserializer::TReader &getReader(Deserializer &s) {
|
||||
return s._reader;
|
||||
}
|
||||
};
|
||||
@@ -58,18 +75,62 @@ namespace bitsery {
|
||||
namespace details {
|
||||
|
||||
//helper types for error handling
|
||||
template <typename T>
|
||||
struct IsContainerTraitsDefined:public IsDefined<typename traits::ContainerTraits<T>::TValue> {
|
||||
template<typename T>
|
||||
struct IsContainerTraitsDefined : public IsDefined<typename traits::ContainerTraits<T>::TValue> {
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct IsTextTraitsDefined:public IsDefined<typename traits::TextTraits<T>::TValue> {
|
||||
template<typename T>
|
||||
struct IsTextTraitsDefined : public IsDefined<typename traits::TextTraits<T>::TValue> {
|
||||
};
|
||||
|
||||
template <typename Ext, typename T>
|
||||
struct IsExtensionTraitsDefined:public IsDefined<typename traits::ExtensionTraits<Ext, T>::TValue> {
|
||||
template<typename Ext, typename T>
|
||||
struct IsExtensionTraitsDefined : public IsDefined<typename traits::ExtensionTraits<Ext, T>::TValue> {
|
||||
};
|
||||
|
||||
#ifdef _MSC_VER
|
||||
//helper types for HasSerializeFunction
|
||||
template <typename S, typename T>
|
||||
using TrySerializeFunction = decltype(serialize(std::declval<S &>(), std::declval<T &>()));
|
||||
|
||||
template <typename S, typename T>
|
||||
struct HasSerializeFunctionHelper {
|
||||
template <typename Q, typename R, typename = TrySerializeFunction<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 HasSerializeFunction :HasSerializeFunctionHelper<S, T>::type {};
|
||||
|
||||
//helper types for HasSerializeMethod
|
||||
template <typename S, typename T>
|
||||
using TrySerializeMethod = decltype(Access::serialize(std::declval<S &>(), std::declval<T &>()));
|
||||
|
||||
template <typename S, typename T>
|
||||
struct HasSerializeMethodHelper {
|
||||
template <typename Q, typename R, typename = TrySerializeMethod<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 HasSerializeMethod :HasSerializeMethodHelper<S, T>::type {};
|
||||
|
||||
//helper types for IsFlexibleIncluded
|
||||
template <typename S, typename T>
|
||||
using TryArchiveProcess = decltype(archiveProcess(std::declval<S &>(), std::declval<T &&>()));
|
||||
|
||||
template <typename S, typename T>
|
||||
struct IsFlexibleIncludedHelper {
|
||||
template <typename Q, typename R, typename = TryArchiveProcess<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 {};
|
||||
#else
|
||||
//helper metafunction, that is added to c++17
|
||||
template<typename... Ts>
|
||||
struct make_void {
|
||||
@@ -78,30 +139,39 @@ namespace bitsery {
|
||||
template<typename... Ts>
|
||||
using void_t = typename make_void<Ts...>::type;
|
||||
|
||||
template <typename, typename, typename = void>
|
||||
struct HasSerializeFunction:std::false_type {};
|
||||
template<typename, typename, typename = void>
|
||||
struct HasSerializeFunction : std::false_type {
|
||||
};
|
||||
|
||||
template <typename S, typename T>
|
||||
struct HasSerializeFunction<S,T,
|
||||
template<typename S, typename T>
|
||||
struct HasSerializeFunction<S, T,
|
||||
void_t<decltype(serialize(std::declval<S &>(), std::declval<T &>()))>
|
||||
> : std::true_type {};
|
||||
> : std::true_type {
|
||||
};
|
||||
|
||||
|
||||
template <typename, typename, typename = void>
|
||||
struct HasSerializeMethod:std::false_type {};
|
||||
template<typename, typename, typename = void>
|
||||
struct HasSerializeMethod : std::false_type {
|
||||
};
|
||||
|
||||
template <typename S, typename T>
|
||||
struct HasSerializeMethod<S,T,
|
||||
template<typename S, typename T>
|
||||
struct HasSerializeMethod<S, T,
|
||||
void_t<decltype(Access::serialize(std::declval<S &>(), std::declval<T &>()))>
|
||||
> : std::true_type {};
|
||||
> : std::true_type {
|
||||
};
|
||||
|
||||
template <typename, typename, typename = void>
|
||||
struct IsFlexibleIncluded:std::false_type {};
|
||||
//this solution doesn't work with visual studio, but is more elegant
|
||||
template<typename, typename, typename = void>
|
||||
struct IsFlexibleIncluded : std::false_type {
|
||||
};
|
||||
|
||||
template <typename S, typename T>
|
||||
struct IsFlexibleIncluded<S,T,
|
||||
template<typename S, typename T>
|
||||
struct IsFlexibleIncluded<S, T,
|
||||
void_t<decltype(archiveProcess(std::declval<S &>(), std::declval<T &&>()))>
|
||||
> : std::true_type {};
|
||||
> : std::true_type {
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
//used for extensions, when extension TValue = void
|
||||
struct DummyType {
|
||||
@@ -149,23 +219,34 @@ namespace bitsery {
|
||||
struct SerializeFunction {
|
||||
|
||||
static void invoke(S &s, T &v) {
|
||||
static_assert(HasSerializeFunction<S,T>::value || HasSerializeMethod<S,T>::value,
|
||||
static_assert(HasSerializeFunction<S, T>::value || HasSerializeMethod<S, T>::value,
|
||||
"\nPlease define 'serialize' function for your type (inside or outside of class):\n"
|
||||
" template<typename S>\n"
|
||||
" void serialize(S& s)\n"
|
||||
" {\n"
|
||||
" ...\n"
|
||||
" }\n");
|
||||
static_assert(!(HasSerializeFunction<S,T>::value && HasSerializeMethod<S,T>::value),
|
||||
"\nPlease define only one 'serialize' function (member OR free), not both.");
|
||||
internalInvoke(s,v, HasSerializeMethod<S,T>{});
|
||||
using TDecayed = typename std::decay<T>::type;
|
||||
selectSerializeFnc(s, v, SelectSerializeFnc<TDecayed>{});
|
||||
}
|
||||
|
||||
private:
|
||||
static void internalInvoke(S& s, T& v,std::true_type) {
|
||||
Access::serialize(s,v);
|
||||
static void selectSerializeFnc(S &s, T &v, std::integral_constant<int, 0>) {
|
||||
static_assert(!(HasSerializeFunction<S, T>::value && HasSerializeMethod<S, T>::value),
|
||||
"\nPlease define only one 'serialize' function (member OR free).\n"
|
||||
"If serialization function is inherited from base class, then explicitly select correct function for your type e.g.:\n"
|
||||
" template <>\n"
|
||||
" struct SelectSerializeFnc<DerivedClass>:UseMemberFnc {};\n");
|
||||
selectSerializeFnc(s, v, std::integral_constant<int,
|
||||
HasSerializeFunction<S, T>::value ? 1 : 2>{});
|
||||
}
|
||||
static void internalInvoke(S& s, T& v,std::false_type) {
|
||||
serialize(s,v);
|
||||
|
||||
static void selectSerializeFnc(S &s, T &v, std::integral_constant<int, 1>) {
|
||||
serialize(s, v);
|
||||
}
|
||||
|
||||
static void selectSerializeFnc(S &s, T &v, std::integral_constant<int, 2>) {
|
||||
Access::serialize(s, v);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -176,18 +257,145 @@ namespace bitsery {
|
||||
template<typename S, typename T, typename Enabled = void>
|
||||
struct ArchiveFunction {
|
||||
|
||||
static void invoke(S &s, T&& obj) {
|
||||
static_assert(IsFlexibleIncluded<S,T>::value,
|
||||
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(HasSerializeFunction<S,T>::value || HasSerializeMethod<S,T>::value,
|
||||
// "\nPlease define 'serialize' function or include '<bitsery/flexible/...>' to use with 'archive'\n");
|
||||
|
||||
archiveProcess(s, std::forward<T>(obj));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
/*
|
||||
* helper function for getting context from serializer/deserializer
|
||||
*/
|
||||
|
||||
template<typename T, template<typename...> class Template>
|
||||
struct IsSpecializationOf : std::false_type {
|
||||
};
|
||||
|
||||
template<template<typename...> class Template, typename... Args>
|
||||
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>{});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_DETAILS_SERIALIZATION_COMMON_H
|
||||
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
//MIT License
|
||||
//
|
||||
// Created by fraillt on 17.10.5.
|
||||
//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_BUFFER_SESSIONS_H
|
||||
#define BITSERY_BUFFER_SESSIONS_H
|
||||
#ifndef BITSERY_DETAILS_SESSIONS_H
|
||||
#define BITSERY_DETAILS_SESSIONS_H
|
||||
|
||||
#include <vector>
|
||||
#include <stack>
|
||||
@@ -54,6 +72,13 @@ namespace bitsery {
|
||||
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());
|
||||
@@ -65,7 +90,9 @@ namespace bitsery {
|
||||
//change position to session end
|
||||
auto sessionIt = std::next(std::begin(_sessions), _sessionIndex.top());
|
||||
_sessionIndex.pop();
|
||||
*sessionIt = writer.writtenBytesCount();
|
||||
auto sessionSize = writer.writtenBytesCount();
|
||||
assert(sessionSize > 0);
|
||||
*sessionIt = sessionSize;
|
||||
}
|
||||
|
||||
void flushSessions(TWriter& writer) {
|
||||
@@ -85,7 +112,7 @@ namespace bitsery {
|
||||
}
|
||||
private:
|
||||
std::vector<size_t> _sessions{};
|
||||
std::stack<size_t> _sessionIndex;
|
||||
std::stack<size_t> _sessionIndex{};
|
||||
};
|
||||
|
||||
template <typename TReader>
|
||||
@@ -100,6 +127,13 @@ namespace bitsery {
|
||||
_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())
|
||||
@@ -125,7 +159,7 @@ namespace bitsery {
|
||||
} else {
|
||||
//there is no data to read anymore
|
||||
//pos == end or buffer overflow while session is active
|
||||
if (!(_posItRef == _endItRef || _reader.error() == ReaderError::NoError)) {
|
||||
if (!(_posItRef == _endItRef || _reader.error() == ReaderError::NoError) || !(_sessionsStack.size() > 1)) {
|
||||
_reader.setError(ReaderError::InvalidData);
|
||||
}
|
||||
}
|
||||
@@ -147,9 +181,12 @@ namespace bitsery {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_posItRef = _endItRef;
|
||||
//restore end position
|
||||
_endItRef = _sessionsStack.top();
|
||||
//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();
|
||||
}
|
||||
}
|
||||
@@ -206,4 +243,4 @@ namespace bitsery {
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_BUFFER_SESSIONS_H
|
||||
#endif //BITSERY_DETAILS_SESSIONS_H
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace bitsery {
|
||||
++index;
|
||||
}
|
||||
return 0u;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
namespace ext {
|
||||
@@ -57,7 +57,7 @@ namespace bitsery {
|
||||
};
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &s, Writer &writer, const T &obj, Fnc &&fnc) const {
|
||||
void serialize(Ser &s, Writer &, 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)});
|
||||
@@ -68,7 +68,7 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &d, Reader &reader, T &obj, Fnc &&fnc) const {
|
||||
void deserialize(Des &d, Reader &, 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)});
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#ifndef BITSERY_EXT_GROWABLE_H
|
||||
#define BITSERY_EXT_GROWABLE_H
|
||||
|
||||
#include "../traits/core/traits.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
namespace ext {
|
||||
@@ -34,14 +36,14 @@ namespace bitsery {
|
||||
public:
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &s, Writer &writer, const T &obj, Fnc &&fnc) const {
|
||||
void serialize(Ser &, Writer &writer, const T &obj, Fnc &&fnc) const {
|
||||
writer.beginSession();
|
||||
fnc(const_cast<T&>(obj));
|
||||
writer.endSession();
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &d, Reader &reader, T &obj, Fnc &&fnc) const {
|
||||
void deserialize(Des &, Reader &reader, T &obj, Fnc &&fnc) const {
|
||||
reader.beginSession();
|
||||
fnc(obj);
|
||||
reader.endSession();
|
||||
|
||||
156
include/bitsery/ext/inheritance.h
Normal file
156
include/bitsery/ext/inheritance.h
Normal file
@@ -0,0 +1,156 @@
|
||||
//MIT License
|
||||
//
|
||||
//Copyright (c) 2017 Mindaugas Vinkelis
|
||||
//
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
//of this software and associated documentation files (the "Software"), to deal
|
||||
//in the Software without restriction, including without limitation the rights
|
||||
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
//copies of the Software, and to permit persons to whom the Software is
|
||||
//furnished to do so, subject to the following conditions:
|
||||
//
|
||||
//The above copyright notice and this permission notice shall be included in all
|
||||
//copies or substantial portions of the Software.
|
||||
//
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#ifndef BITSERY_EXT_INHERITANCE_H
|
||||
#define BITSERY_EXT_INHERITANCE_H
|
||||
|
||||
#include <unordered_set>
|
||||
#include "../traits/core/traits.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
namespace ext {
|
||||
|
||||
//required when virtual inheritance (ext::VirtualBaseClass) exists in serialization flow.
|
||||
//for standard inheritance (ext::BaseClass) it is optional.
|
||||
class InheritanceContext {
|
||||
public:
|
||||
InheritanceContext() = default;
|
||||
InheritanceContext(const InheritanceContext&) = delete;
|
||||
InheritanceContext&operator = (const InheritanceContext&) = delete;
|
||||
InheritanceContext(InheritanceContext&&) = default;
|
||||
InheritanceContext& operator = (InheritanceContext&&) = default;
|
||||
|
||||
template <typename TDerived, typename TBase>
|
||||
void beginBase(const TDerived &derived, const TBase &) {
|
||||
if (_depth == 0) {
|
||||
const void* ptr = std::addressof(derived);
|
||||
if ( _parentPtr != ptr)
|
||||
_virtualBases.clear();
|
||||
_parentPtr = ptr;
|
||||
}
|
||||
++_depth;
|
||||
}
|
||||
|
||||
template <typename TDerived, typename TBase>
|
||||
bool beginVirtualBase(const TDerived &derived, const TBase &base) {
|
||||
beginBase(derived, base);
|
||||
return _virtualBases.emplace(std::addressof(base)).second;
|
||||
}
|
||||
|
||||
void end() {
|
||||
--_depth;
|
||||
}
|
||||
|
||||
private:
|
||||
//these members are required to know when we can clear _virtualBases
|
||||
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{};
|
||||
};
|
||||
|
||||
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 {
|
||||
auto& resObj = static_cast<const TBase&>(obj);
|
||||
if (auto ctx = ser.template contextOrNull<InheritanceContext>()) {
|
||||
ctx->beginBase(obj, resObj);
|
||||
fnc(const_cast<TBase&>(resObj));
|
||||
ctx->end();
|
||||
} else {
|
||||
fnc(const_cast<TBase&>(resObj));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &des, Reader &, T &obj, Fnc &&fnc) const {
|
||||
auto& resObj = static_cast<TBase&>(obj);
|
||||
if (auto ctx = des.template contextOrNull<InheritanceContext>()) {
|
||||
ctx->beginBase(obj, resObj);
|
||||
fnc(resObj);
|
||||
ctx->end();
|
||||
} else {
|
||||
fnc(resObj);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//requires InheritanceContext
|
||||
template <typename TBase>
|
||||
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>();
|
||||
auto& resObj = static_cast<const TBase&>(obj);
|
||||
if (ctx->beginVirtualBase(obj, resObj))
|
||||
fnc(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>();
|
||||
auto& resObj = static_cast<TBase&>(obj);
|
||||
if (ctx->beginVirtualBase(obj, resObj))
|
||||
fnc(resObj);
|
||||
ctx->end();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace traits {
|
||||
template<typename TBase, typename T>
|
||||
struct ExtensionTraits<ext::BaseClass<TBase>, T> {
|
||||
static_assert(std::is_base_of<TBase, T>::value, "Invalid base class");
|
||||
|
||||
using TValue = TBase;
|
||||
static constexpr bool SupportValueOverload = false;
|
||||
static constexpr bool SupportObjectOverload = true;
|
||||
static constexpr bool SupportLambdaOverload = true;
|
||||
};
|
||||
|
||||
template<typename TBase, typename T>
|
||||
struct ExtensionTraits<ext::VirtualBaseClass<TBase>, T> {
|
||||
static_assert(std::is_base_of<TBase, T>::value, "Invalid base class");
|
||||
|
||||
using TValue = TBase;
|
||||
static constexpr bool SupportValueOverload = false;
|
||||
static constexpr bool SupportObjectOverload = true;
|
||||
//disable lambda overload, when serializing virtually inherited base class.
|
||||
//Only one instance of virtual base will be serialized, when using multiple inheritance
|
||||
//and it will be undefined behaviour if derived classes would have different virtual base class serialization flow.
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif //BITSERY_EXT_INHERITANCE_H
|
||||
196
include/bitsery/ext/pointer.h
Normal file
196
include/bitsery/ext/pointer.h
Normal file
@@ -0,0 +1,196 @@
|
||||
//MIT License
|
||||
//
|
||||
//Copyright (c) 2017 Mindaugas Vinkelis
|
||||
//
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
//of this software and associated documentation files (the "Software"), to deal
|
||||
//in the Software without restriction, including without limitation the rights
|
||||
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
//copies of the Software, and to permit persons to whom the Software is
|
||||
//furnished to do so, subject to the following conditions:
|
||||
//
|
||||
//The above copyright notice and this permission notice shall be included in all
|
||||
//copies or substantial portions of the Software.
|
||||
//
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#ifndef BITSERY_EXT_POINTER_H
|
||||
#define BITSERY_EXT_POINTER_H
|
||||
|
||||
#include <cassert>
|
||||
#include "../traits/core/traits.h"
|
||||
#include "utils/pointer_utils.h"
|
||||
#include "utils/polymorphism_utils.h"
|
||||
#include "utils/rtti_utils.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
namespace ext {
|
||||
|
||||
namespace pointer_details {
|
||||
|
||||
template<typename T>
|
||||
struct PtrOwnerManager {
|
||||
static_assert(std::is_pointer<T>::value, "");
|
||||
|
||||
using TElement = typename std::remove_pointer<T>::type;
|
||||
|
||||
static TElement* getPtr(T &obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
static constexpr PointerOwnershipType getOwnership() {
|
||||
return PointerOwnershipType::Owner;
|
||||
}
|
||||
|
||||
static void assign(T& obj, TElement* valuePtr) {
|
||||
delete obj;
|
||||
obj = valuePtr;
|
||||
}
|
||||
|
||||
static void clear(T& obj) {
|
||||
delete obj;
|
||||
obj = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct PtrObserverManager {
|
||||
static_assert(std::is_pointer<T>::value, "");
|
||||
|
||||
using TElement = typename std::remove_pointer<T>::type;
|
||||
|
||||
//observer must return reference to pointer, so that it could be updated later
|
||||
static TElement*& getPtrRef(T& obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
static TElement* getPtr(T& obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
static constexpr PointerOwnershipType getOwnership() {
|
||||
return PointerOwnershipType::Observer;
|
||||
}
|
||||
|
||||
static void assign(T& obj, TElement* valuePtr) {
|
||||
//do not delete existing object
|
||||
obj = valuePtr;
|
||||
}
|
||||
|
||||
static void clear(T& obj) {
|
||||
obj = nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
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 assign(T& , TElement* ) {}
|
||||
|
||||
static void clear(T& ) {}
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
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:
|
||||
ReferencedByPointer() : pointer_utils::PointerObjectExtensionBase<
|
||||
pointer_details::NonPtrManager, PolymorphicContext, pointer_details::NoRTTI>(
|
||||
PointerType::NotNull) {}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace traits {
|
||||
|
||||
template<typename T, typename RTTI>
|
||||
struct ExtensionTraits<ext::PointerOwnerBase<RTTI>, T*> {
|
||||
using TValue = T;
|
||||
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>();
|
||||
};
|
||||
|
||||
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;
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct ExtensionTraits<ext::ReferencedByPointer, T> {
|
||||
//allow everything, because it is serialized as regular type, except it also creates pointerId that is required by NonOwningPointer to work
|
||||
using TValue = T;
|
||||
static constexpr bool SupportValueOverload = true;
|
||||
static constexpr bool SupportObjectOverload = true;
|
||||
static constexpr bool SupportLambdaOverload = true;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_EXT_POINTER_H
|
||||
@@ -23,6 +23,7 @@
|
||||
#ifndef BITSERY_EXT_STD_MAP_H
|
||||
#define BITSERY_EXT_STD_MAP_H
|
||||
|
||||
#include "../traits/core/traits.h"
|
||||
#include "../details/adapter_utils.h"
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
// using optional = experimental::optional<T>;
|
||||
//}
|
||||
#include <type_traits>
|
||||
#include "../traits/core/traits.h"
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
@@ -25,8 +25,9 @@
|
||||
|
||||
#include <cassert>
|
||||
#include "../details/adapter_utils.h"
|
||||
//we need this, so we could
|
||||
//we need this, so we could reserve for non ordered set
|
||||
#include <unordered_set>
|
||||
#include "../traits/core/traits.h"
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
@@ -74,7 +75,7 @@ namespace bitsery {
|
||||
obj.reserve(size);
|
||||
}
|
||||
template <typename T>
|
||||
void reserve(T obj, size_t size) const {
|
||||
void reserve(T& , size_t ) const {
|
||||
//for ordered container do nothing
|
||||
}
|
||||
size_t _maxSize;
|
||||
|
||||
128
include/bitsery/ext/std_smart_ptr.h
Normal file
128
include/bitsery/ext/std_smart_ptr.h
Normal file
@@ -0,0 +1,128 @@
|
||||
//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;
|
||||
|
||||
static TElement *getPtr(std::unique_ptr<TElement> &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 std::is_same<std::unique_ptr<TElement>, T>::value
|
||||
? PointerOwnershipType::Owner
|
||||
: std::is_same<std::shared_ptr<TElement>, T>::value
|
||||
? PointerOwnershipType::SharedOwner
|
||||
: PointerOwnershipType::SharedObserver;
|
||||
}
|
||||
|
||||
static void clear(T &obj) {
|
||||
obj.reset();
|
||||
}
|
||||
|
||||
static void assign(T &obj, TElement *valuePtr) {
|
||||
obj.reset(valuePtr);
|
||||
}
|
||||
|
||||
//this is used, when old object exists and is the same type
|
||||
static std::unique_ptr<pointer_utils::PointerSharedStateBase> saveToSharedState(T &obj) {
|
||||
auto state = new SharedPtrSharedState{};
|
||||
//to work with weak_ptr and shared_ptr create new std::shared_ptr
|
||||
state->obj = std::shared_ptr<TElement>(obj);
|
||||
return std::unique_ptr<pointer_utils::PointerSharedStateBase>{state};
|
||||
}
|
||||
|
||||
//this is used, when old object doesn't exists or is not the same type
|
||||
static std::unique_ptr<pointer_utils::PointerSharedStateBase> createSharedState(TElement *valuePtr) {
|
||||
auto state = new SharedPtrSharedState{};
|
||||
state->obj = std::shared_ptr<TElement>(valuePtr);
|
||||
return std::unique_ptr<pointer_utils::PointerSharedStateBase>{state};
|
||||
}
|
||||
|
||||
static void loadFromSharedState(pointer_utils::PointerSharedStateBase *ctx, T &obj) {
|
||||
auto state = dynamic_cast<SharedPtrSharedState *>(ctx);
|
||||
//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
|
||||
@@ -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;
|
||||
|
||||
385
include/bitsery/ext/utils/pointer_utils.h
Normal file
385
include/bitsery/ext/utils/pointer_utils.h
Normal file
@@ -0,0 +1,385 @@
|
||||
//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 "../../details/adapter_utils.h"
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
//change name
|
||||
enum class PointerType {
|
||||
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
|
||||
};
|
||||
|
||||
//forward declaration
|
||||
class PointerLinkingContext;
|
||||
|
||||
namespace pointer_utils {
|
||||
|
||||
//this class is used to store context for shared ptr owners
|
||||
struct PointerSharedStateBase {
|
||||
virtual ~PointerSharedStateBase() = default;
|
||||
};
|
||||
|
||||
//PLC info is internal classes for serializer, and deserializer
|
||||
struct PLCInfo {
|
||||
explicit PLCInfo(PointerOwnershipType ownershipType_)
|
||||
: ownershipType{ownershipType_},
|
||||
isSharedProcessed{false} {};
|
||||
PointerOwnershipType ownershipType;
|
||||
bool isSharedProcessed;
|
||||
};
|
||||
|
||||
struct PLCInfoSerializer: PLCInfo {
|
||||
PLCInfoSerializer(size_t id_, PointerOwnershipType ownershipType_)
|
||||
: PLCInfo(ownershipType_), id{id_} {}
|
||||
size_t id;
|
||||
};
|
||||
|
||||
struct PLCInfoDeserializer : PLCInfo {
|
||||
PLCInfoDeserializer(void *ptr, PointerOwnershipType ownershipType_)
|
||||
: PLCInfo(ownershipType_),
|
||||
ownerPtr{ptr} {};
|
||||
|
||||
//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;
|
||||
std::vector<std::reference_wrapper<void *>> observersList{};
|
||||
std::unique_ptr<PointerSharedStateBase> sharedState{};
|
||||
};
|
||||
|
||||
void updatePLCInfo(PLCInfo &ptrInfo, PointerOwnershipType ptrType) {
|
||||
//do nothing for observer
|
||||
if (ptrType == PointerOwnershipType::Observer)
|
||||
return;
|
||||
if (ptrInfo.ownershipType == PointerOwnershipType::Observer) {
|
||||
//set ownership type
|
||||
ptrInfo.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)
|
||||
ptrInfo.ownershipType = ptrType;
|
||||
//mark that object already processed, so we do not serialize/deserialize duplicate objects
|
||||
ptrInfo.isSharedProcessed = true;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
updatePLCInfo(ptrInfo, 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> _ptrMap;
|
||||
|
||||
};
|
||||
|
||||
class PointerLinkingContextDeserialization {
|
||||
public:
|
||||
explicit PointerLinkingContextDeserialization()
|
||||
: _idMap{} {}
|
||||
|
||||
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});
|
||||
auto &ptrInfo = res.first->second;
|
||||
if (!res.second)
|
||||
updatePLCInfo(ptrInfo, 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;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<size_t, PLCInfoDeserializer> _idMap;
|
||||
};
|
||||
|
||||
template<template<typename> class TPtrManager,
|
||||
template<typename> class TPolymorphicContext, typename RTTI>
|
||||
class PointerObjectExtensionBase {
|
||||
public:
|
||||
|
||||
explicit PointerObjectExtensionBase(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 &&fnc) const {
|
||||
|
||||
auto ptr = TPtrManager<T>::getPtr(const_cast<T &>(obj));
|
||||
if (ptr) {
|
||||
auto ctx = ser.template context<PointerLinkingContext>();
|
||||
assert(ctx != nullptr);
|
||||
auto &ptrInfo = ctx->getInfoByPtr(getBasePtr(ptr), TPtrManager<T>::getOwnership());
|
||||
details::writeSize(w, ptrInfo.id);
|
||||
if (TPtrManager<T>::getOwnership() != PointerOwnershipType::Observer) {
|
||||
if (!ptrInfo.isSharedProcessed)
|
||||
serializeImpl(ser, ptr, std::forward<Fnc>(fnc), w, IsPolymorphic<T>{});
|
||||
}
|
||||
} 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 &&fnc) const {
|
||||
size_t id{};
|
||||
details::readSize(r, id, std::numeric_limits<size_t>::max());
|
||||
if (id) {
|
||||
auto ctx = des.template context<PointerLinkingContext>();
|
||||
assert(ctx != nullptr);
|
||||
auto &ptrInfo = ctx->getInfoById(id, TPtrManager<T>::getOwnership());
|
||||
deserializeImpl(ptrInfo, des, obj, std::forward<Fnc>(fnc), r, IsPolymorphic<T>{},
|
||||
std::integral_constant<PointerOwnershipType, TPtrManager<T>::getOwnership()>{});
|
||||
} else {
|
||||
if (_ptrType == PointerType::Nullable) {
|
||||
TPtrManager<T>::clear(obj);
|
||||
} else
|
||||
r.setError(ReaderError::InvalidPointer);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
template<typename T>
|
||||
struct IsPolymorphic : std::integral_constant<bool,
|
||||
RTTI::template isPolymorphic<typename TPtrManager<T>::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, typename Writer>
|
||||
void serializeImpl(Ser &ser, TPtr &ptr, Fnc &&, Writer &w, std::true_type) const {
|
||||
const auto &ctx = ser.template context<TPolymorphicContext<RTTI>>();
|
||||
ctx->serialize(ser, w, *ptr);
|
||||
}
|
||||
|
||||
template<typename Ser, typename TPtr, typename Fnc, typename Writer>
|
||||
void serializeImpl(Ser &, TPtr &ptr, Fnc &&fnc, Writer &, std::false_type) const {
|
||||
fnc(*ptr);
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc, typename Reader>
|
||||
void deserializeImpl(PLCInfoDeserializer &ptrInfo, Des &des, T &obj, Fnc &&,
|
||||
Reader &r, std::true_type ,
|
||||
std::integral_constant<PointerOwnershipType, PointerOwnershipType::Owner>) const {
|
||||
const auto &ctx = des.template context<TPolymorphicContext<RTTI>>();
|
||||
ctx->deserialize(des, r, TPtrManager<T>::getPtr(obj),
|
||||
[&obj, this](typename TPtrManager<T>::TElement *valuePtr) {
|
||||
TPtrManager<T>::assign(obj, valuePtr);
|
||||
});
|
||||
ptrInfo.processOwner(TPtrManager<T>::getPtr(obj));
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc, typename Reader>
|
||||
void deserializeImpl(PLCInfoDeserializer &ptrInfo, Des &, T &obj, Fnc &&fnc,
|
||||
Reader &, std::false_type ,
|
||||
std::integral_constant<PointerOwnershipType, PointerOwnershipType::Owner>) const {
|
||||
auto ptr = TPtrManager<T>::getPtr(obj);
|
||||
if (ptr) {
|
||||
fnc(*ptr);
|
||||
} else {
|
||||
ptr = new typename TPtrManager<T>::TElement{};
|
||||
fnc(*ptr);
|
||||
TPtrManager<T>::assign(obj, ptr);
|
||||
}
|
||||
ptrInfo.processOwner(ptr);
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc, typename Reader>
|
||||
void deserializeImpl(PLCInfoDeserializer &ptrInfo, Des &des, T &obj, Fnc &&,
|
||||
Reader &r, std::true_type ,
|
||||
std::integral_constant<PointerOwnershipType, PointerOwnershipType::SharedOwner>) const {
|
||||
auto &sharedState = ptrInfo.sharedState;
|
||||
if (!sharedState) {
|
||||
const auto &ctx = des.template context<TPolymorphicContext<RTTI>>();
|
||||
ctx->deserialize(des, r, TPtrManager<T>::getPtr(obj),
|
||||
[&obj, &sharedState](typename TPtrManager<T>::TElement *valuePtr) {
|
||||
sharedState = TPtrManager<T>::createSharedState(valuePtr);
|
||||
});
|
||||
if (!sharedState)
|
||||
sharedState = TPtrManager<T>::saveToSharedState(obj);
|
||||
}
|
||||
TPtrManager<T>::loadFromSharedState(sharedState.get(), obj);
|
||||
ptrInfo.processOwner(TPtrManager<T>::getPtr(obj));
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc, typename Reader>
|
||||
void deserializeImpl(PLCInfoDeserializer &ptrInfo, Des &, T &obj, Fnc &&fnc,
|
||||
Reader &, std::false_type ,
|
||||
std::integral_constant<PointerOwnershipType, PointerOwnershipType::SharedOwner>) const {
|
||||
auto &sharedState = ptrInfo.sharedState;
|
||||
if (!sharedState) {
|
||||
if (auto ptr = TPtrManager<T>::getPtr(obj)) {
|
||||
fnc(*ptr);
|
||||
sharedState = TPtrManager<T>::saveToSharedState(obj);
|
||||
} else {
|
||||
auto res = new typename TPtrManager<T>::TElement{};
|
||||
fnc(*res);
|
||||
sharedState = TPtrManager<T>::createSharedState(res);
|
||||
}
|
||||
}
|
||||
TPtrManager<T>::loadFromSharedState(sharedState.get(), obj);
|
||||
ptrInfo.processOwner(TPtrManager<T>::getPtr(obj));
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc, typename Reader, typename isPolymorph>
|
||||
void deserializeImpl(PLCInfoDeserializer &ptrInfo, Des &des, T &obj, Fnc &&fnc,
|
||||
Reader &r, isPolymorph polymorph,
|
||||
std::integral_constant<PointerOwnershipType, PointerOwnershipType::SharedObserver>) const {
|
||||
deserializeImpl(ptrInfo, des, obj, fnc, r, polymorph,
|
||||
std::integral_constant<PointerOwnershipType, PointerOwnershipType::SharedOwner>{});
|
||||
}
|
||||
|
||||
template<typename Des, typename T, typename Fnc, typename Reader, typename isPolymorphic>
|
||||
void deserializeImpl(PLCInfoDeserializer &ptrInfo, Des &, T &obj, Fnc &&,
|
||||
Reader &, isPolymorphic,
|
||||
std::integral_constant<PointerOwnershipType, PointerOwnershipType::Observer>) const {
|
||||
ptrInfo.processObserver(reinterpret_cast<void *&>(TPtrManager<T>::getPtrRef(obj)));
|
||||
}
|
||||
|
||||
PointerType _ptrType;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//this class is for convenience
|
||||
class PointerLinkingContext :
|
||||
public pointer_utils::PointerLinkingContextSerialization,
|
||||
public pointer_utils::PointerLinkingContextDeserialization {
|
||||
public:
|
||||
bool isValid() {
|
||||
return isPointerSerializationValid() && isPointerDeserializationValid();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_POINTER_UTILS_H
|
||||
214
include/bitsery/ext/utils/polymorphism_utils.h
Normal file
214
include/bitsery/ext/utils/polymorphism_utils.h
Normal file
@@ -0,0 +1,214 @@
|
||||
//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 "../../details/adapter_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 = 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 final {
|
||||
return toBase(new TDerived{});
|
||||
};
|
||||
|
||||
void process(void *ser, void *obj) const final {
|
||||
static_cast<TSerializer *>(ser)->object(*static_cast<TDerived *>(fromBase(obj)));
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
void *fromBase(void *obj) const {
|
||||
return RTTI::template cast<TBase, TDerived>(static_cast<TBase *>(obj));
|
||||
}
|
||||
|
||||
void *toBase(void *obj) const {
|
||||
return RTTI::template cast<TDerived, TBase>(static_cast<TDerived *>(obj));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
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, typename TBase, typename TDerived>
|
||||
void add() {
|
||||
addToMap<TSerializer, TBase, TDerived>(std::is_abstract<TDerived>{});
|
||||
addChilds<TSerializer, TBase, TDerived>(typename PolymorphicBaseClass<TDerived>::Childs{});
|
||||
}
|
||||
|
||||
template<typename TSerializer, 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, TBase, T1>();
|
||||
addChilds<TSerializer, TBase, TDerived>(PolymorphicClassesList<Tn...>{});
|
||||
//iterate through derived class hierarchy as well
|
||||
add<TSerializer, T1, T1>();
|
||||
}
|
||||
|
||||
template<typename TSerializer, typename TBase, typename TDerived>
|
||||
void addChilds(PolymorphicClassesList<>) {
|
||||
}
|
||||
|
||||
template<typename TSerializer, typename TBase, typename TDerived>
|
||||
void addToMap(std::false_type) {
|
||||
BaseToDerivedKey key{RTTI::template get<TBase>(), RTTI::template get<TDerived>()};
|
||||
if (_baseToDerivedMap.emplace(key, std::unique_ptr<PolymorphicHandlerBase>(
|
||||
new PolymorphicHandler<RTTI, TSerializer, TBase, TDerived>{})).second)
|
||||
_baseToDerivedArray[key.baseHash].push_back(key.derivedHash);
|
||||
}
|
||||
|
||||
template<typename TSerializer, typename TBase, typename TDerived>
|
||||
void addToMap(std::true_type) {
|
||||
//cannot add abstract class
|
||||
}
|
||||
|
||||
std::unordered_map<BaseToDerivedKey, std::unique_ptr<PolymorphicHandlerBase>, BaseToDerivedKeyHashier> _baseToDerivedMap{};
|
||||
// this will allow convert from platform specific type information, to platform independent base->derived index
|
||||
// this only works if all polymorphic relationships (PolymorphicBaseClass<TBase> -> PolymorphicDerivedClasses<TDerived...>)
|
||||
// is equal between platforms.
|
||||
std::unordered_map<size_t, std::vector<size_t>> _baseToDerivedArray{};
|
||||
|
||||
public:
|
||||
|
||||
void clear() {
|
||||
_baseToDerivedMap.clear();
|
||||
_baseToDerivedArray.clear();
|
||||
}
|
||||
|
||||
template<typename TSerializer, typename T1, typename ...Tn>
|
||||
void registerBasesList(const TSerializer &s, PolymorphicClassesList<T1, Tn...>) {
|
||||
add<TSerializer, T1, T1>();
|
||||
registerBasesList<TSerializer>(s, PolymorphicClassesList<Tn...>{});
|
||||
}
|
||||
|
||||
template<typename TSerializer>
|
||||
void registerBasesList(const TSerializer &, PolymorphicClassesList<>) {
|
||||
}
|
||||
|
||||
template<typename Serializer, typename Writer, typename TBase>
|
||||
void serialize(Serializer &ser, Writer &writer, TBase &obj) {
|
||||
//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(writer, derivedIndex);
|
||||
|
||||
//serialize
|
||||
it->second->process(&ser, &obj);
|
||||
}
|
||||
|
||||
template<typename Deserializer, typename Reader, typename TBase, typename TAssignFnc>
|
||||
void deserialize(Deserializer &des, Reader &reader, TBase *obj, TAssignFnc assignFnc) {
|
||||
size_t derivedIndex{};
|
||||
details::readSize(reader, derivedIndex, std::numeric_limits<size_t>::max());
|
||||
|
||||
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) {
|
||||
obj = static_cast<TBase *>(handler->create());
|
||||
assignFnc(obj);
|
||||
}
|
||||
handler->process(&des, obj);
|
||||
} else
|
||||
reader.setError(ReaderError::InvalidPointer);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#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
|
||||
@@ -108,13 +108,13 @@ namespace bitsery {
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
|
||||
details::SameSizeUnsigned<T> getRangeValue(const T &v, const RangeSpec<T> &r) {
|
||||
return static_cast<details::SameSizeUnsigned<T>>(v - r.min);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
|
||||
details::SameSizeUnsigned<T> getRangeValue(const T &v, const RangeSpec<T> &r) {
|
||||
using VT = details::SameSizeUnsigned<T>;
|
||||
return static_cast<VT>(static_cast<VT>(v) - static_cast<VT>(r.min));
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
|
||||
details::SameSizeUnsigned<T> getRangeValue(const T &v, const RangeSpec<T> &r) {
|
||||
@@ -122,18 +122,18 @@ namespace bitsery {
|
||||
const VT maxUint = (static_cast<VT>(1) << r.bitsRequired) - 1;
|
||||
const auto ratio = (v - r.min) / (r.max - r.min);
|
||||
return static_cast<VT>(ratio * maxUint);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value>::type * = nullptr>
|
||||
void setRangeValue(T &v, const RangeSpec<T> &r) {
|
||||
v += r.min;
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T, typename std::enable_if<std::is_enum<T>::value>::type * = nullptr>
|
||||
void setRangeValue(T &v, const RangeSpec<T> &r) {
|
||||
using VT = typename std::underlying_type<T>::type;
|
||||
reinterpret_cast<VT &>(v) += static_cast<VT>(r.min);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T, typename std::enable_if<std::is_floating_point<T>::value>::type * = nullptr>
|
||||
void setRangeValue(T &v, const RangeSpec<T> &r) {
|
||||
@@ -141,7 +141,7 @@ namespace bitsery {
|
||||
const auto intRep = reinterpret_cast<UIT &>(v);
|
||||
const UIT maxUint = (static_cast<UIT>(1) << r.bitsRequired) - 1;
|
||||
v = r.min + (static_cast<T>(intRep) / maxUint) * (r.max - r.min);
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T, typename std::enable_if<std::is_arithmetic<T>::value>::type * = nullptr>
|
||||
bool isRangeValid(const T &v, const RangeSpec<T> &r) {
|
||||
@@ -163,7 +163,7 @@ namespace bitsery {
|
||||
public:
|
||||
|
||||
template<typename ... Args>
|
||||
explicit constexpr ValueRange(Args &&... args):_range{std::forward<Args>(args)...} {};
|
||||
explicit constexpr ValueRange(Args &&... args):_range{std::forward<Args>(args)...} {}
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &, Writer &writer, const T &v, Fnc &&) const {
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace bitsery {
|
||||
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>
|
||||
@@ -43,7 +43,7 @@ namespace bitsery {
|
||||
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);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,12 +57,12 @@ namespace bitsery {
|
||||
template<typename T, size_t N>
|
||||
flexible::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]) {
|
||||
return {obj};
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
flexible::MaxSize<T> maxSize(T& obj, size_t max) {
|
||||
@@ -78,21 +78,21 @@ namespace bitsery {
|
||||
template<typename S, typename T, typename std::enable_if<details::IsFundamentalType<T>::value>::type * = nullptr>
|
||||
void serialize(S &s, T &v) {
|
||||
s.template value<sizeof(T)>(v);
|
||||
};
|
||||
}
|
||||
|
||||
//define serialization for c-style container
|
||||
|
||||
//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 &s, T (&obj)[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);
|
||||
};
|
||||
}
|
||||
|
||||
//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>
|
||||
@@ -103,7 +103,7 @@ namespace bitsery {
|
||||
static_assert(sizeof(long) == TLong, "");
|
||||
static_assert(sizeof(long long) == TLongLong, "");
|
||||
//for completion we also need pointer type size, but serializer doesn't support pointer serialization.
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
45
include/bitsery/flexible/memory.h
Normal file
45
include/bitsery/flexible/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_FLEXIBLE_TYPE_STD_MEMORY_H
|
||||
#define BITSERY_FLEXIBLE_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_FLEXIBLE_TYPE_STD_MEMORY_H
|
||||
@@ -30,21 +30,25 @@
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
template<typename TAdapterWriter>
|
||||
template<typename TAdapterWriter, typename TContext = void>
|
||||
class BasicSerializer {
|
||||
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>;
|
||||
TAdapterWriter, AdapterWriterBitPackingWrapper<TAdapterWriter>>::type, TContext>;
|
||||
|
||||
static_assert(details::IsSpecializationOf<typename TWriter::TConfig::InternalContext, std::tuple>::value,
|
||||
"Config::InternalContext must be std::tuple");
|
||||
|
||||
template <typename WriterParam>
|
||||
explicit BasicSerializer(WriterParam&& w, void* context = nullptr)
|
||||
explicit BasicSerializer(WriterParam&& w, TContext* context = nullptr)
|
||||
: _writer{std::forward<WriterParam>(w)},
|
||||
_context{context}
|
||||
_context{context},
|
||||
_internalContext{}
|
||||
{
|
||||
};
|
||||
}
|
||||
|
||||
//copying disabled
|
||||
BasicSerializer(const BasicSerializer&) = delete;
|
||||
@@ -58,10 +62,20 @@ namespace bitsery {
|
||||
* get serialization context.
|
||||
* this is optional, but might be required for some specific serialization flows.
|
||||
*/
|
||||
void* context() {
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* object function
|
||||
*/
|
||||
@@ -73,7 +87,7 @@ namespace bitsery {
|
||||
template<typename T, typename Fnc>
|
||||
void object(const T &obj, Fnc &&fnc) {
|
||||
fnc(const_cast<T& >(obj));
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* functionality, that enables simpler serialization syntax, by including additional header
|
||||
@@ -114,7 +128,7 @@ namespace bitsery {
|
||||
static_assert(traits::ExtensionTraits<Ext,T>::SupportLambdaOverload,
|
||||
"extension doesn't support overload with lambda");
|
||||
extension.serialize(*this, _writer, obj, std::forward<Fnc>(fnc));
|
||||
};
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, typename Ext>
|
||||
void ext(const T &obj, const Ext &extension) {
|
||||
@@ -124,7 +138,7 @@ namespace bitsery {
|
||||
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); });
|
||||
};
|
||||
}
|
||||
|
||||
template<typename T, typename Ext>
|
||||
void ext(const T &obj, const Ext &extension) {
|
||||
@@ -134,7 +148,7 @@ namespace bitsery {
|
||||
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); });
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* boolValue
|
||||
@@ -259,16 +273,16 @@ namespace bitsery {
|
||||
void value8b(T &&v) { value<8>(std::forward<T>(v)); }
|
||||
|
||||
template<typename T, typename Ext>
|
||||
void ext1b(const T &v, Ext &&extension) { ext<1, T, Ext>(v, std::forward<Ext>(extension)); };
|
||||
void ext1b(const T &v, Ext &&extension) { ext<1, T, Ext>(v, std::forward<Ext>(extension)); }
|
||||
|
||||
template<typename T, typename Ext>
|
||||
void ext2b(const T &v, Ext &&extension) { ext<2, T, Ext>(v, std::forward<Ext>(extension)); };
|
||||
void ext2b(const T &v, Ext &&extension) { ext<2, T, Ext>(v, std::forward<Ext>(extension)); }
|
||||
|
||||
template<typename T, typename Ext>
|
||||
void ext4b(const T &v, Ext &&extension) { ext<4, T, Ext>(v, std::forward<Ext>(extension)); };
|
||||
void ext4b(const T &v, Ext &&extension) { ext<4, T, Ext>(v, std::forward<Ext>(extension)); }
|
||||
|
||||
template<typename T, typename Ext>
|
||||
void ext8b(const T &v, Ext &&extension) { ext<8, T, Ext>(v, std::forward<Ext>(extension)); };
|
||||
void ext8b(const T &v, Ext &&extension) { ext<8, T, Ext>(v, std::forward<Ext>(extension)); }
|
||||
|
||||
template<typename T>
|
||||
void text1b(const T &str, size_t maxSize) { text<1>(str, maxSize); }
|
||||
@@ -316,7 +330,8 @@ namespace bitsery {
|
||||
friend AdapterAccess;
|
||||
|
||||
TAdapterWriter _writer;
|
||||
void* _context;
|
||||
TContext* _context;
|
||||
typename TWriter::TConfig::InternalContext _internalContext;
|
||||
|
||||
//process value types
|
||||
//false_type means that we must process all elements individually
|
||||
@@ -324,7 +339,7 @@ namespace bitsery {
|
||||
void procContainer(It first, It last, std::false_type) {
|
||||
for (; first != last; ++first)
|
||||
value<VSIZE>(*first);
|
||||
};
|
||||
}
|
||||
|
||||
//process value types
|
||||
//true_type means, that we can copy whole buffer
|
||||
@@ -335,7 +350,7 @@ namespace bitsery {
|
||||
if (first != last)
|
||||
_writer.template writeBuffer<VSIZE>(reinterpret_cast<const TIntegral*>(&(*first)),
|
||||
static_cast<size_t>(std::distance(first, last)));
|
||||
};
|
||||
}
|
||||
|
||||
//process by calling functions
|
||||
template<typename It, typename Fnc>
|
||||
@@ -344,7 +359,7 @@ namespace bitsery {
|
||||
for (; first != last; ++first) {
|
||||
fnc(const_cast<TValue&>(*first));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//process text,
|
||||
template<size_t VSIZE, typename T>
|
||||
@@ -354,14 +369,14 @@ namespace bitsery {
|
||||
details::writeSize(_writer, length);
|
||||
auto begin = std::begin(str);
|
||||
procContainer<VSIZE>(begin, std::next(begin, length), std::integral_constant<bool, traits::ContainerTraits<T>::isContiguous>{});
|
||||
};
|
||||
}
|
||||
|
||||
//process object types
|
||||
template<typename It>
|
||||
void procContainer(It first, It last) {
|
||||
for (; first != last; ++first)
|
||||
object(*first);
|
||||
};
|
||||
}
|
||||
|
||||
//proc bool writing bit or byte, depending on if BitPackingEnabled or not
|
||||
void procBoolValue(bool v, std::true_type) {
|
||||
@@ -381,7 +396,7 @@ namespace bitsery {
|
||||
template <typename Fnc>
|
||||
void procEnableBitPacking(const Fnc& fnc, std::false_type) {
|
||||
//create serializer using bitpacking wrapper
|
||||
BasicSerializer<AdapterWriterBitPackingWrapper<TAdapterWriter>> tmp(_writer, _context);
|
||||
BPEnabledType tmp(_writer, _context);
|
||||
fnc(tmp);
|
||||
}
|
||||
|
||||
@@ -413,7 +428,7 @@ namespace bitsery {
|
||||
auto& w = AdapterAccess::getWriter(ser);
|
||||
w.flush();
|
||||
return w.writtenBytesCount();
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
size_t quickMeasureSize(const T& value) {
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#ifndef BITSERY_TRAITS_HELPER_STD_DEFAULTS_H
|
||||
#define BITSERY_TRAITS_HELPER_STD_DEFAULTS_H
|
||||
#ifndef BITSERY_TRAITS_CORE_STD_DEFAULTS_H
|
||||
#define BITSERY_TRAITS_CORE_STD_DEFAULTS_H
|
||||
|
||||
#include "traits.h"
|
||||
#include <iostream>
|
||||
@@ -82,4 +82,4 @@ namespace bitsery {
|
||||
}
|
||||
}
|
||||
|
||||
#endif //BITSERY_TRAITS_HELPER_STD_DEFAULTS_H
|
||||
#endif //BITSERY_TRAITS_CORE_STD_DEFAULTS_H
|
||||
|
||||
@@ -62,12 +62,12 @@ namespace bitsery {
|
||||
//contiguous hopefully will be available in c++20
|
||||
static constexpr bool isContiguous = false;
|
||||
//resize function, called only if container is resizable
|
||||
static void resize(T& container, size_t size) {
|
||||
static void resize(T& , size_t ) {
|
||||
static_assert(std::is_void<T>::value,
|
||||
"Define ContainerTraits or include from <bitsery/traits/...> to use as container");
|
||||
}
|
||||
//get container size
|
||||
static size_t size(const T& container) {
|
||||
static size_t size(const T& ) {
|
||||
static_assert(std::is_void<T>::value,
|
||||
"Define ContainerTraits or include from <bitsery/traits/...> to use as container");
|
||||
return 0u;
|
||||
@@ -80,12 +80,13 @@ namespace bitsery {
|
||||
using TValue = T;
|
||||
static constexpr bool isResizable = false;
|
||||
static constexpr bool isContiguous = true;
|
||||
static size_t size(const T (&container)[N]) {
|
||||
static size_t size(const T (&)[N]) {
|
||||
return N;
|
||||
}
|
||||
};
|
||||
|
||||
//specialization for initializer list, even though it cannot be deserialized to.
|
||||
//specialization for initializer list.
|
||||
//only serializer can use it
|
||||
template<typename T>
|
||||
struct ContainerTraits<std::initializer_list<T>> {
|
||||
using TValue = T;
|
||||
@@ -96,6 +97,30 @@ namespace bitsery {
|
||||
}
|
||||
};
|
||||
|
||||
//specialization for pointer type buffer
|
||||
//only deserializer can use it
|
||||
template <typename T>
|
||||
struct ContainerTraits<const T*> {
|
||||
using TValue = T;
|
||||
static constexpr bool isResizable = false;
|
||||
static constexpr bool isContiguous = true;
|
||||
static size_t size(const T* ) {
|
||||
static_assert(std::is_void<T>::value, "cannot get size for container of type T*");
|
||||
return 0u;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct ContainerTraits<T*> {
|
||||
using TValue = T;
|
||||
static constexpr bool isResizable = false;
|
||||
static constexpr bool isContiguous = true;
|
||||
static size_t size(const T* ) {
|
||||
static_assert(std::is_void<T>::value, "cannot get size for container of type T*");
|
||||
return 0u;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
//traits for text, default adds null-terminated character at the end
|
||||
@@ -106,7 +131,7 @@ namespace bitsery {
|
||||
static constexpr bool addNUL = true;
|
||||
|
||||
//get length of null terminated container
|
||||
static size_t length(const T& container) {
|
||||
static size_t length(const T& ) {
|
||||
static_assert(std::is_void<T>::value,
|
||||
"Define TextTraits or include from <bitsery/traits/...> to use as text");
|
||||
return 0u;
|
||||
@@ -124,7 +149,7 @@ namespace bitsery {
|
||||
//instead of using back_insert_iterator to append each byte to buffer.
|
||||
//thats why Writer return range iterators
|
||||
|
||||
static void increaseBufferSize(T& container) {
|
||||
static void increaseBufferSize(T& ) {
|
||||
static_assert(std::is_void<T>::value,
|
||||
"Define BufferAdapterTraits or include from <bitsery/traits/...> to use as buffer adapter container");
|
||||
}
|
||||
|
||||
7
scripts/CTestConfig.cmake
Normal file
7
scripts/CTestConfig.cmake
Normal file
@@ -0,0 +1,7 @@
|
||||
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)
|
||||
|
||||
20
scripts/build.bitsery.cmake
Normal file
20
scripts/build.bitsery.cmake
Normal file
@@ -0,0 +1,20 @@
|
||||
# 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_USE_LAUNCHERS 1)
|
||||
|
||||
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
|
||||
xdg-open $TESTS_BUILD_DIR/coverage_web/index.html
|
||||
@@ -20,25 +20,14 @@
|
||||
#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.5)
|
||||
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)
|
||||
|
||||
@@ -47,28 +36,31 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
|
||||
list(REMOVE_ITEM TestSourceFiles ${CMAKE_CURRENT_SOURCE_DIR}/serialization_ext_std_optional.cpp)
|
||||
endif()
|
||||
|
||||
include(${ExtCMakeFilesDir}/LinkTestLib.cmake)
|
||||
enable_testing()
|
||||
|
||||
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++)
|
||||
endif()
|
||||
|
||||
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})
|
||||
|
||||
|
||||
|
||||
@@ -20,10 +20,14 @@
|
||||
//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 <gmock/gmock.h>
|
||||
#include <sstream>
|
||||
|
||||
//some helper types
|
||||
@@ -33,21 +37,13 @@ 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, WrittenBytesCountReturns0) {
|
||||
//setup data
|
||||
uint8_t t1 = 111;
|
||||
|
||||
Stream buf{};
|
||||
Writer w{{buf}};
|
||||
w.writeBytes<1>(t1);
|
||||
w.flush();
|
||||
|
||||
EXPECT_THAT(buf.str().size(), Eq(1));
|
||||
EXPECT_THAT(w.writtenBytesCount(), Eq(0));
|
||||
}
|
||||
|
||||
TEST(AdapterIOStream, CorrectlyReturnsIsCompletedSuccessfully) {
|
||||
//setup data
|
||||
uint8_t t1 = 111;
|
||||
@@ -84,7 +80,7 @@ TEST(AdapterIOStream, ReadingMoreThanAvailableReturnsZero) {
|
||||
}
|
||||
|
||||
//this is strange, but probably stringstream doesnt use any of the base methods that sets io_base::iostate flags
|
||||
TEST(AdapterIOStream, WhenReadingStringStreamThenErrorCodeAlwaysReturnsNoError) {
|
||||
TEST(AdapterIOStream, WhenReadingMoreThanAvailableThenDataOverflow) {
|
||||
//setup data
|
||||
uint8_t t1 = 111;
|
||||
|
||||
@@ -103,7 +99,64 @@ TEST(AdapterIOStream, WhenReadingStringStreamThenErrorCodeAlwaysReturnsNoError)
|
||||
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));
|
||||
//should by overflow error, but it all iostate flags are set to false...
|
||||
EXPECT_THAT(r.error(), Eq(bitsery::ReaderError::NoError));
|
||||
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));
|
||||
}
|
||||
@@ -21,11 +21,11 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <bitsery/adapter_writer.h>
|
||||
#include <bitsery/adapter_reader.h>
|
||||
#include <bitsery/ext/value_range.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
using testing::Eq;
|
||||
using testing::ContainerEq;
|
||||
@@ -56,19 +56,19 @@ using InverseReader = bitsery::AdapterReader<InputAdapter, InverseEndiannessConf
|
||||
TEST(DataEndianness, WhenWriteBytesThenBytesAreSwapped) {
|
||||
//fill initial values
|
||||
IntegralTypes src{};
|
||||
src.a = 0x1122334455667788;
|
||||
src.a = static_cast<int64_t>(0x1122334455667788);
|
||||
src.b = 0xBBCCDDEE;
|
||||
src.c = 0xCCDD;
|
||||
src.d = 0xDD;
|
||||
src.e = 0xEE;
|
||||
src.c = static_cast<int16_t>(0xCCDD);
|
||||
src.d = static_cast<uint8_t>(0xDD);
|
||||
src.e = static_cast<int8_t>(0xEE);
|
||||
|
||||
//fill expected result after swap
|
||||
IntegralTypes resInv{};
|
||||
resInv.a = 0x8877665544332211;
|
||||
resInv.a = static_cast<int64_t>(0x8877665544332211);
|
||||
resInv.b = 0xEEDDCCBB;
|
||||
resInv.c = 0xDDCC;
|
||||
resInv.d = 0xDD;
|
||||
resInv.e = 0xEE;
|
||||
resInv.c = static_cast<int16_t>(0xDDCC);
|
||||
resInv.d = static_cast<uint8_t>(0xDD);
|
||||
resInv.e = static_cast<int8_t>(0xEE);
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
@@ -136,7 +136,7 @@ TEST(DataEndianness, WhenWriteMoreThan1ByteValuesThenValuesAreSwapped) {
|
||||
template <typename T>
|
||||
constexpr size_t getBits(T v) {
|
||||
return bitsery::details::calcRequiredBits<T>({}, v);
|
||||
};
|
||||
}
|
||||
|
||||
struct IntegralUnsignedTypes {
|
||||
uint64_t a;
|
||||
|
||||
@@ -21,9 +21,10 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#include <bitsery/ext/value_range.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/ext/value_range.h>
|
||||
|
||||
|
||||
using testing::Eq;
|
||||
using testing::ContainerEq;
|
||||
@@ -43,7 +44,7 @@ struct IntegralUnsignedTypes {
|
||||
template <typename T>
|
||||
constexpr size_t getBits(T v) {
|
||||
return bitsery::details::calcRequiredBits<T>({}, v);
|
||||
};
|
||||
}
|
||||
|
||||
// *** bits operations
|
||||
|
||||
@@ -285,6 +286,53 @@ TEST(DataBitsAndBytesOperations, WriteAndReadBytes) {
|
||||
|
||||
}
|
||||
|
||||
TEST(DataBitsAndBytesOperations, WriteAndReadBytesWithBitPackingWrapper) {
|
||||
//setup data
|
||||
IntegralTypes data;
|
||||
data.a = -4894541654564;
|
||||
data.b = 94545646;
|
||||
data.c = -8778;
|
||||
data.d = 200;
|
||||
data.e = -98;
|
||||
data.f[0] = 43;
|
||||
data.f[1] = -45;
|
||||
|
||||
//create and write to buffer
|
||||
Buffer buf{};
|
||||
Writer bw{buf};
|
||||
AdapterBitPackingWriter bpw{bw};
|
||||
bpw.writeBytes<4>(data.b);
|
||||
bpw.writeBytes<2>(data.c);
|
||||
bpw.writeBytes<1>(data.d);
|
||||
bpw.writeBytes<8>(data.a);
|
||||
bpw.writeBytes<1>(data.e);
|
||||
bpw.writeBuffer<1>(data.f, 2);
|
||||
bpw.flush();
|
||||
auto writtenSize = bpw.writtenBytesCount();
|
||||
|
||||
EXPECT_THAT(writtenSize, Eq(18));
|
||||
//read from buffer
|
||||
Reader br{InputAdapter{buf.begin(), writtenSize}};
|
||||
AdapterBitPackingReader bpr{br};
|
||||
IntegralTypes res{};
|
||||
bpr.readBytes<4>(res.b);
|
||||
bpr.readBytes<2>(res.c);
|
||||
bpr.readBytes<1>(res.d);
|
||||
bpr.readBytes<8>(res.a);
|
||||
bpr.readBytes<1>(res.e);
|
||||
bpr.readBuffer<1>(res.f, 2);
|
||||
EXPECT_THAT(bpr.error(), Eq(bitsery::ReaderError::NoError));
|
||||
//assert results
|
||||
|
||||
EXPECT_THAT(data.a, Eq(res.a));
|
||||
EXPECT_THAT(data.b, Eq(res.b));
|
||||
EXPECT_THAT(data.c, Eq(res.c));
|
||||
EXPECT_THAT(data.d, Eq(res.d));
|
||||
EXPECT_THAT(data.e, Eq(res.e));
|
||||
EXPECT_THAT(data.f, ContainerEq(res.f));
|
||||
|
||||
}
|
||||
|
||||
TEST(DataBitsAndBytesOperations, ReadWriteFncCanAcceptSignedData) {
|
||||
//setup data
|
||||
constexpr size_t DATA_SIZE = 3;
|
||||
|
||||
@@ -20,9 +20,11 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/traits/string.h>
|
||||
|
||||
|
||||
using testing::Eq;
|
||||
using SessionsEnabledWriter = bitsery::AdapterWriter<OutputAdapter, SessionsEnabledConfig>;
|
||||
@@ -114,8 +116,22 @@ TEST(DataReadingErrors, WhenInitializingSessionsWhereSessionsDataOffsetIsCorrupt
|
||||
SessionsEnabledWriter bw{buf};
|
||||
bw.writeBytes<1>(uint8_t{1});
|
||||
bw.writeBytes<1>(uint8_t{1});
|
||||
bw.writeBytes<2>(uint16_t{10});
|
||||
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;
|
||||
|
||||
@@ -21,10 +21,7 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/flexible.h>
|
||||
|
||||
#include <bitsery/flexible/string.h>
|
||||
#include <bitsery/flexible/array.h>
|
||||
#include <bitsery/flexible/vector.h>
|
||||
@@ -37,6 +34,10 @@
|
||||
#include <bitsery/flexible/unordered_map.h>
|
||||
#include <bitsery/flexible/set.h>
|
||||
#include <bitsery/flexible/unordered_set.h>
|
||||
#include <bitsery/flexible/memory.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
@@ -44,18 +45,18 @@ TEST(FlexibleSyntax, FundamentalTypesAndBool) {
|
||||
int ti = 8745;
|
||||
MyEnumClass te = MyEnumClass::E4;
|
||||
float tf = 485.042f;
|
||||
double_t td = -454184.48445;
|
||||
bool tb=true;
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().archive(ti,te,tf,td,tb);
|
||||
double td = -454184.48445;
|
||||
bool tb = true;
|
||||
SerializationContext ctx{};
|
||||
ctx.createSerializer().archive(ti, te, tf, td, tb);
|
||||
|
||||
//result
|
||||
int ri{};
|
||||
MyEnumClass re{};
|
||||
float rf{};
|
||||
double_t rd{};
|
||||
double rd{};
|
||||
bool rb{};
|
||||
ctx.createDeserializer().archive(ri,re,rf,rd,rb);
|
||||
ctx.createDeserializer().archive(ri, re, rf, rd, rb);
|
||||
|
||||
//test
|
||||
EXPECT_THAT(ri, Eq(ti));
|
||||
@@ -69,10 +70,10 @@ TEST(FlexibleSyntax, UseObjectFncInsteadOfValueN) {
|
||||
int ti = 8745;
|
||||
MyEnumClass te = MyEnumClass::E4;
|
||||
float tf = 485.042f;
|
||||
double_t td = -454184.48445;
|
||||
bool tb=true;
|
||||
double td = -454184.48445;
|
||||
bool tb = true;
|
||||
SerializationContext ctx;
|
||||
auto& ser = ctx.createSerializer();
|
||||
auto &ser = ctx.createSerializer();
|
||||
ser.object(ti);
|
||||
ser.object(te);
|
||||
ser.object(tf);
|
||||
@@ -83,9 +84,9 @@ TEST(FlexibleSyntax, UseObjectFncInsteadOfValueN) {
|
||||
int ri{};
|
||||
MyEnumClass re{};
|
||||
float rf{};
|
||||
double_t rd{};
|
||||
double rd{};
|
||||
bool rb{};
|
||||
auto& des = ctx.createDeserializer();
|
||||
auto &des = ctx.createDeserializer();
|
||||
des.object(ri);
|
||||
des.object(re);
|
||||
des.object(rf);
|
||||
@@ -104,10 +105,10 @@ TEST(FlexibleSyntax, MixDifferentSyntax) {
|
||||
int ti = 8745;
|
||||
MyEnumClass te = MyEnumClass::E4;
|
||||
float tf = 485.042f;
|
||||
double_t td = -454184.48445;
|
||||
bool tb=true;
|
||||
double td = -454184.48445;
|
||||
bool tb = true;
|
||||
SerializationContext ctx;
|
||||
auto& ser = ctx.createSerializer();
|
||||
auto &ser = ctx.createSerializer();
|
||||
ser.value<sizeof(ti)>(ti);
|
||||
ser.archive(te, tf, td);
|
||||
ser.object(tb);
|
||||
@@ -116,9 +117,9 @@ TEST(FlexibleSyntax, MixDifferentSyntax) {
|
||||
int ri{};
|
||||
MyEnumClass re{};
|
||||
float rf{};
|
||||
double_t rd{};
|
||||
double rd{};
|
||||
bool rb{};
|
||||
auto& des = ctx.createDeserializer();
|
||||
auto &des = ctx.createDeserializer();
|
||||
des.archive(ri, re, rf);
|
||||
des.value8b(rd);
|
||||
des.object(rb);
|
||||
@@ -131,28 +132,27 @@ TEST(FlexibleSyntax, MixDifferentSyntax) {
|
||||
EXPECT_THAT(rb, Eq(tb));
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
T procArchive(const T& testData) {
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().archive(testData);
|
||||
T res;
|
||||
T res{};
|
||||
ctx.createDeserializer().archive(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
T procArchiveWithMaxSize(const T& testData) {
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().archive(bitsery::maxSize(testData, 100));
|
||||
T res;
|
||||
T res{};
|
||||
ctx.createDeserializer().archive(bitsery::maxSize(res, 100));
|
||||
return res;
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, CStyleArrayForValueTypesAsContainer) {
|
||||
const int t1[3]{8748,-484,45};
|
||||
int r1[3]{0,0,0};
|
||||
const int t1[3]{8748, -484, 45};
|
||||
int r1[3]{0, 0, 0};
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().archive(bitsery::asContainer(t1));
|
||||
@@ -163,7 +163,7 @@ TEST(FlexibleSyntax, CStyleArrayForValueTypesAsContainer) {
|
||||
|
||||
TEST(FlexibleSyntax, CStyleArrayForIntegralTypesAsText) {
|
||||
const char t1[3]{"hi"};
|
||||
char r1[3]{0,0,0};
|
||||
char r1[3]{0, 0, 0};
|
||||
|
||||
SerializationContext ctx;
|
||||
ctx.createSerializer().archive(bitsery::asText(t1));
|
||||
@@ -183,7 +183,6 @@ TEST(FlexibleSyntax, CStyleArray) {
|
||||
EXPECT_THAT(r1, ::testing::ContainerEq(t1));
|
||||
}
|
||||
|
||||
|
||||
TEST(FlexibleSyntax, StdString) {
|
||||
std::string t1{"my nice string"};
|
||||
std::string t2{};
|
||||
@@ -195,56 +194,51 @@ TEST(FlexibleSyntax, StdString) {
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdArray) {
|
||||
std::array<int, 3> t1{8748,-484,45};
|
||||
std::array<int, 3> t1{8748, -484, 45};
|
||||
std::array<int, 0> t2{};
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchive(t2), Eq(t2));
|
||||
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdVector) {
|
||||
std::vector<int> t1{8748,-484,45};
|
||||
std::vector<float> t2{5.f,0.198f};
|
||||
std::vector<int> t1{8748, -484, 45};
|
||||
std::vector<float> t2{5.f, 0.198f};
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchive(t2), Eq(t2));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t2), Eq(t2));
|
||||
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdList) {
|
||||
std::list<int> t1{8748,-484,45};
|
||||
std::list<float> t2{5.f,0.198f};
|
||||
std::list<int> t1{8748, -484, 45};
|
||||
std::list<float> t2{5.f, 0.198f};
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchive(t2), Eq(t2));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t2), Eq(t2));
|
||||
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdForwardList) {
|
||||
std::forward_list<int> t1{8748,-484,45};
|
||||
std::forward_list<float> t2{5.f,0.198f};
|
||||
std::forward_list<int> t1{8748, -484, 45};
|
||||
std::forward_list<float> t2{5.f, 0.198f};
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchive(t2), Eq(t2));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t2), Eq(t2));
|
||||
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdDeque) {
|
||||
std::deque<int> t1{8748,-484,45};
|
||||
std::deque<float> t2{5.f,0.198f};
|
||||
std::deque<int> t1{8748, -484, 45};
|
||||
std::deque<float> t2{5.f, 0.198f};
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchive(t2), Eq(t2));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t2), Eq(t2));
|
||||
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdQueue) {
|
||||
@@ -254,7 +248,6 @@ TEST(FlexibleSyntax, StdQueue) {
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdPriorityQueue) {
|
||||
@@ -272,7 +265,6 @@ TEST(FlexibleSyntax, StdPriorityQueue) {
|
||||
r1.pop();
|
||||
t1.pop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdStack) {
|
||||
@@ -282,13 +274,12 @@ TEST(FlexibleSyntax, StdStack) {
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdUnorderedMap) {
|
||||
std::unordered_map<int, int> t1;
|
||||
t1.emplace(3423,624);
|
||||
t1.emplace(-5484,-845);
|
||||
t1.emplace(3423, 624);
|
||||
t1.emplace(-5484, -845);
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
@@ -296,9 +287,9 @@ TEST(FlexibleSyntax, StdUnorderedMap) {
|
||||
|
||||
TEST(FlexibleSyntax, StdUnorderedMultiMap) {
|
||||
std::unordered_multimap<std::string, int> t1;
|
||||
t1.emplace("one",624);
|
||||
t1.emplace("two",-845);
|
||||
t1.emplace("one",897);
|
||||
t1.emplace("one", 624);
|
||||
t1.emplace("two", -845);
|
||||
t1.emplace("one", 897);
|
||||
|
||||
EXPECT_TRUE(procArchive(t1) == t1);
|
||||
EXPECT_TRUE(procArchiveWithMaxSize(t1) == t1);
|
||||
@@ -306,8 +297,8 @@ TEST(FlexibleSyntax, StdUnorderedMultiMap) {
|
||||
|
||||
TEST(FlexibleSyntax, StdMap) {
|
||||
std::map<int, int> t1;
|
||||
t1.emplace(3423,624);
|
||||
t1.emplace(-5484,-845);
|
||||
t1.emplace(3423, 624);
|
||||
t1.emplace(-5484, -845);
|
||||
|
||||
EXPECT_THAT(procArchive(t1), Eq(t1));
|
||||
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
|
||||
@@ -315,9 +306,9 @@ TEST(FlexibleSyntax, StdMap) {
|
||||
|
||||
TEST(FlexibleSyntax, StdMultiMap) {
|
||||
std::multimap<std::string, int> t1;
|
||||
t1.emplace("one",624);
|
||||
t1.emplace("two",-845);
|
||||
t1.emplace("one",897);
|
||||
t1.emplace("one", 624);
|
||||
t1.emplace("two", -845);
|
||||
t1.emplace("one", 897);
|
||||
|
||||
auto res = procArchive(t1);
|
||||
//same key values is not ordered, and operator == compares each element at same position
|
||||
@@ -375,6 +366,27 @@ TEST(FlexibleSyntax, StdMultiSet) {
|
||||
EXPECT_TRUE(procArchiveWithMaxSize(t1) == t1);
|
||||
}
|
||||
|
||||
TEST(FlexibleSyntax, StdSmartPtr) {
|
||||
std::shared_ptr<int> dataShared1(new int{4});
|
||||
std::weak_ptr<int> dataWeak1(dataShared1);
|
||||
std::unique_ptr<std::string> dataUnique1{new std::string{"hello world"}};
|
||||
|
||||
bitsery::ext::PointerLinkingContext plctx1{};
|
||||
BasicSerializationContext<bitsery::DefaultConfig, bitsery::ext::PointerLinkingContext> ctx;
|
||||
ctx.createSerializer(&plctx1).archive(dataShared1, dataWeak1, dataUnique1);
|
||||
|
||||
std::shared_ptr<int> resShared1{};
|
||||
std::weak_ptr<int> resWeak1{};
|
||||
std::unique_ptr<std::string> resUnique1{};
|
||||
ctx.createDeserializer(&plctx1).archive(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(FlexibleSyntax, NestedTypes) {
|
||||
std::unordered_map<std::string, std::vector<std::string>> t1;
|
||||
|
||||
@@ -33,7 +33,7 @@ using Deserializer = bitsery::BasicDeserializer<bitsery::AdapterReaderBitPacking
|
||||
|
||||
TEST(SerializeBooleans, BoolAsBit) {
|
||||
|
||||
SerializationContext ctx;
|
||||
SerializationContext ctx{};
|
||||
bool t1{true};
|
||||
bool t2{false};
|
||||
bool res1;
|
||||
|
||||
@@ -22,16 +22,14 @@
|
||||
|
||||
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/traits/array.h>
|
||||
#include <bitsery/traits/list.h>
|
||||
#include <bitsery/traits/deque.h>
|
||||
#include <bitsery/traits/forward_list.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
using testing::ContainerEq;
|
||||
using testing::Eq;
|
||||
|
||||
@@ -163,7 +161,7 @@ TYPED_TEST(SerializeContainerDynamicSizeCompositeTypes, CustomFunctionThatDoNoth
|
||||
SerializationContext ctx{};
|
||||
using TValue = typename TestFixture::TValue;
|
||||
|
||||
auto emptyFnc = [](TValue &v) {};
|
||||
auto emptyFnc = [](TValue &) {};
|
||||
ctx.createSerializer().container(this->src, 1000, emptyFnc);
|
||||
ctx.createDeserializer().container(this->res, 1000, emptyFnc);
|
||||
|
||||
@@ -217,7 +215,7 @@ TYPED_TEST(SerializeContainerFixedSizeCompositeTypes, DefaultSerializationFuncti
|
||||
Container src{MyStruct1{0, 1}, MyStruct1{8, 9}, MyStruct1{11, 34}, MyStruct1{5134, 1532}};
|
||||
Container res{};
|
||||
|
||||
SerializationContext ctx;
|
||||
SerializationContext ctx{};
|
||||
ctx.createSerializer().container(src);
|
||||
ctx.createDeserializer().container(res);
|
||||
|
||||
@@ -232,7 +230,7 @@ TYPED_TEST(SerializeContainerFixedSizeCompositeTypes, CustomFunctionThatSerializ
|
||||
|
||||
using TValue = decltype(*std::begin(res));
|
||||
|
||||
SerializationContext ctx;
|
||||
SerializationContext ctx{};
|
||||
auto& ser = ctx.createSerializer();
|
||||
ser.container(src, [&ser](TValue &v) {
|
||||
char tmp{};
|
||||
@@ -250,4 +248,22 @@ TYPED_TEST(SerializeContainerFixedSizeCompositeTypes, CustomFunctionThatSerializ
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
}
|
||||
|
||||
class SerializeContainer : public ::testing::TestWithParam<size_t> {
|
||||
};
|
||||
|
||||
TEST_P(SerializeContainer, SizeHasVariableLength) {
|
||||
SerializationContext ctx{};
|
||||
auto emptyFnc = [](uint8_t &) {};
|
||||
|
||||
std::vector<uint8_t > src(GetParam());
|
||||
std::vector<uint8_t > res{};
|
||||
ctx.createSerializer().container(src, std::numeric_limits<size_t>::max(), emptyFnc);
|
||||
ctx.createDeserializer().container(res, std::numeric_limits<size_t>::max(), emptyFnc);
|
||||
|
||||
EXPECT_THAT(res.size(), Eq(src.size()));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(ctx.containerSizeSerializedBytesCount(src.size())));
|
||||
}
|
||||
|
||||
//last comma is to suppress error that otherwise can be suppressed by clang/gcc with -Wgnu-zero-variadic-macro-arguments
|
||||
INSTANTIATE_TEST_CASE_P(LargeContainerSize, SerializeContainer, ::testing::Values(0x01, 0x80, 0x4000),);
|
||||
|
||||
|
||||
133
tests/serialization_context.cpp
Normal file
133
tests/serialization_context.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
//MIT License
|
||||
//
|
||||
//Copyright (c) 2017 Mindaugas Vinkelis
|
||||
//
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
//of this software and associated documentation files (the "Software"), to deal
|
||||
//in the Software without restriction, including without limitation the rights
|
||||
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
//copies of the Software, and to permit persons to whom the Software is
|
||||
//furnished to do so, subject to the following conditions:
|
||||
//
|
||||
//The above copyright notice and this permission notice shall be included in all
|
||||
//copies or substantial portions of the Software.
|
||||
//
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
template <typename ... Args>
|
||||
struct ConfigWithContext: bitsery::DefaultConfig {
|
||||
using InternalContext = std::tuple<Args...>;
|
||||
};
|
||||
|
||||
template <typename Context, typename ... Args>
|
||||
using SerializerConfigWithContext = bitsery::BasicSerializer<
|
||||
bitsery::AdapterWriter<bitsery::OutputBufferAdapter<Buffer>, ConfigWithContext<Args...>>, Context>;
|
||||
|
||||
template <typename Context, typename ... Args>
|
||||
using DeserializerConfigWithContext = bitsery::BasicDeserializer<
|
||||
bitsery::AdapterReader<bitsery::InputBufferAdapter<Buffer>, ConfigWithContext<Args...>>, Context>;
|
||||
|
||||
template <typename Context>
|
||||
using MySerializer = bitsery::BasicSerializer<Writer, Context>;
|
||||
|
||||
template <typename Context>
|
||||
using MyDeserializer = bitsery::BasicDeserializer<Reader, Context>;
|
||||
|
||||
using SingleTypeContext = int;
|
||||
using MultipleTypesContext = std::tuple<int, float, char>;
|
||||
|
||||
TEST(SerializationContext, WhenUsingContextThenReturnsUnderlyingPointerOrNull) {
|
||||
Buffer buf{};
|
||||
MySerializer<SingleTypeContext> ser1{buf, nullptr};
|
||||
EXPECT_THAT(ser1.context(), ::testing::IsNull());
|
||||
|
||||
MySerializer<MultipleTypesContext> ser2{buf, nullptr};
|
||||
EXPECT_THAT(ser2.context(), ::testing::IsNull());
|
||||
|
||||
SingleTypeContext sctx{};
|
||||
MyDeserializer<SingleTypeContext> des1{InputAdapter{buf.begin(), 1}, &sctx};
|
||||
EXPECT_THAT(des1.context(), Eq(&sctx));
|
||||
|
||||
MultipleTypesContext mctx{};
|
||||
MyDeserializer<MultipleTypesContext> des2{InputAdapter{buf.begin(), 1}, &mctx};
|
||||
EXPECT_THAT(des2.context(), Eq(&mctx));
|
||||
|
||||
}
|
||||
|
||||
TEST(SerializationContext, WhenContextIsTupleThenContextCastOverloadCastsToIndividualTupleTypes) {
|
||||
Buffer buf{};
|
||||
MySerializer<MultipleTypesContext> ser1{buf, nullptr};
|
||||
EXPECT_THAT(ser1.context<int>(), ::testing::IsNull());
|
||||
EXPECT_THAT(ser1.context<float>(), ::testing::IsNull());
|
||||
EXPECT_THAT(ser1.context<char>(), ::testing::IsNull());
|
||||
}
|
||||
|
||||
TEST(SerializationContext, WhenContextIsNotTupleThenContextCastOverloadReturnSameType) {
|
||||
Buffer buf{};
|
||||
SingleTypeContext ctx{};
|
||||
MySerializer<SingleTypeContext> ser1{buf, &ctx};
|
||||
EXPECT_THAT(ser1.context<SingleTypeContext>(), Eq(&ctx));
|
||||
}
|
||||
|
||||
TEST(SerializationContext, SerializerDeserializerCanHaveInternalContextViaConfig) {
|
||||
Buffer buf{};
|
||||
SerializerConfigWithContext<void, float, int> ser{buf};
|
||||
EXPECT_THAT(ser.context<int>(), ::testing::NotNull());
|
||||
EXPECT_THAT(*ser.context<int>(), Eq(0));
|
||||
*ser.context<int>() = 10;
|
||||
EXPECT_THAT(*ser.context<int>(), Eq(10));
|
||||
|
||||
DeserializerConfigWithContext<void, char> des{InputAdapter{buf.begin(), 1}};
|
||||
EXPECT_THAT(des.context<char>(), ::testing::NotNull());
|
||||
EXPECT_THAT(*des.context<char>(), Eq(0));
|
||||
*des.context<char>() = 10;
|
||||
EXPECT_THAT(*des.context<char>(), Eq(10));
|
||||
|
||||
//new instance has new context
|
||||
SerializerConfigWithContext<void, float, int> ser2{buf};
|
||||
EXPECT_THAT(ser2.context<int>(), ::testing::NotNull());
|
||||
EXPECT_THAT(*ser2.context<int>(), Eq(0));
|
||||
}
|
||||
|
||||
TEST(SerializationContext, WhenInternalAndExternalContextIsTheSamePriorityGoesToInternalContext) {
|
||||
Buffer buf{};
|
||||
int externalCtx = 5;
|
||||
|
||||
SerializerConfigWithContext<int, float, int> ser{buf, &externalCtx};
|
||||
EXPECT_THAT(ser.context<int>(), ::testing::NotNull());
|
||||
EXPECT_THAT(*ser.context<int>(), Eq(0));
|
||||
*ser.context<int>() = 2;
|
||||
|
||||
DeserializerConfigWithContext<int, int, char> des{InputAdapter{buf.begin(), 1}, &externalCtx};
|
||||
EXPECT_THAT(des.context<char>(), ::testing::NotNull());
|
||||
EXPECT_THAT(*des.context<char>(), Eq(0));
|
||||
*des.context<int>() = 3;
|
||||
|
||||
EXPECT_THAT(externalCtx, Eq(5));
|
||||
}
|
||||
|
||||
TEST(SerializationContext, ContextIfExistsReturnsNullWhenTypeDoesntExists) {
|
||||
Buffer buf{};
|
||||
std::tuple<double, short> extCtx1{};
|
||||
|
||||
SerializerConfigWithContext<std::tuple<double, short>, float, int> ser{buf, &extCtx1};
|
||||
EXPECT_THAT(ser.contextOrNull<int>(), ::testing::NotNull());
|
||||
EXPECT_THAT(ser.contextOrNull<char>(), ::testing::IsNull());
|
||||
|
||||
double extCtx2{};
|
||||
DeserializerConfigWithContext<double, int, char> des{InputAdapter{buf.begin(), 1}, &extCtx2};
|
||||
EXPECT_THAT(des.contextOrNull<double>(), ::testing::NotNull());
|
||||
EXPECT_THAT(des.contextOrNull<float>(), ::testing::IsNull());
|
||||
}
|
||||
@@ -21,11 +21,12 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/ext/entropy.h>
|
||||
#include <bitsery/traits/list.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
using namespace testing;
|
||||
|
||||
using bitsery::ext::Entropy;
|
||||
@@ -37,9 +38,8 @@ using BPDes = bitsery::BasicDeserializer<bitsery::AdapterReaderBitPackingWrapper
|
||||
TEST(SerializeExtensionEntropy, WhenEntropyEncodedThenOnlyWriteIndexUsingMinRequiredBits) {
|
||||
int32_t v = 4849;
|
||||
int32_t res;
|
||||
constexpr size_t N = 3;
|
||||
int32_t values[3] = {485,4849,89};
|
||||
SerializationContext ctx;
|
||||
SerializationContext ctx{};
|
||||
ctx.createSerializer().enableBitPacking([&v, &values](BPSer& ser) {
|
||||
ser.ext4b(v, Entropy<int32_t[3]>{values});
|
||||
});
|
||||
@@ -50,12 +50,12 @@ TEST(SerializeExtensionEntropy, WhenEntropyEncodedThenOnlyWriteIndexUsingMinRequ
|
||||
EXPECT_THAT(res, Eq(v));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
|
||||
|
||||
SerializationContext ctx1;
|
||||
SerializationContext ctx1{};
|
||||
ctx1.createSerializer().enableBitPacking([&v, &values](BPSer& ser) {
|
||||
ser.ext4b(v, Entropy<int32_t[3]>{values});
|
||||
});
|
||||
ctx1.createDeserializer().enableBitPacking([&res](BPDes& des) {
|
||||
des.ext(res, bitsery::ext::ValueRange<int32_t>{0, static_cast<int32_t>(N + 1)});
|
||||
des.ext(res, bitsery::ext::ValueRange<int32_t>{0, static_cast<int32_t>(3 + 1)});
|
||||
});
|
||||
EXPECT_THAT(res, Eq(2));
|
||||
}
|
||||
@@ -64,7 +64,7 @@ TEST(SerializeExtensionEntropy, WhenNoEntropyEncodedThenWriteZeroBitsAndValueOrO
|
||||
int16_t v = 8945;
|
||||
int16_t res;
|
||||
std::initializer_list<int> values{485,4849,89};
|
||||
SerializationContext ctx;
|
||||
SerializationContext ctx{};
|
||||
ctx.createSerializer().enableBitPacking([&v, &values](BPSer& ser) {
|
||||
ser.ext2b(v, Entropy<std::initializer_list<int>>{values});
|
||||
});
|
||||
@@ -84,12 +84,12 @@ TEST(SerializeExtensionEntropy, CustomTypeEntropyEncoded) {
|
||||
MyStruct1 values[N]{
|
||||
MyStruct1{12, 10}, MyStruct1{485, 454},
|
||||
MyStruct1{4849, 89}, MyStruct1{0, 1}};
|
||||
SerializationContext ctx;
|
||||
SerializationContext ctx{};
|
||||
ctx.createSerializer().enableBitPacking([&v, &values](BPSer& ser) {
|
||||
ser.ext(v, Entropy<MyStruct1[N]>{values});
|
||||
ser.ext(v, Entropy<MyStruct1[4]>{values});
|
||||
});
|
||||
ctx.createDeserializer().enableBitPacking([&res, &values](BPDes& des) {
|
||||
des.ext(res, Entropy<MyStruct1[N]>{values});
|
||||
des.ext(res, Entropy<MyStruct1[4]>{values});
|
||||
});
|
||||
EXPECT_THAT(res, Eq(v));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
|
||||
@@ -102,7 +102,7 @@ TEST(SerializeExtensionEntropy, CustomTypeNotEntropyEncoded) {
|
||||
std::initializer_list<MyStruct1> values {
|
||||
MyStruct1{12,10}, MyStruct1{485, 454},
|
||||
MyStruct1{4849,89}, MyStruct1{0,1}};
|
||||
SerializationContext ctx;
|
||||
SerializationContext ctx{};
|
||||
ctx.createSerializer().enableBitPacking([&v, &values](BPSer& ser) {
|
||||
ser.ext(v, Entropy<std::initializer_list<MyStruct1>>{values});
|
||||
});
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#include <bitsery/ext/growable.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/ext/growable.h>
|
||||
|
||||
using namespace testing;
|
||||
|
||||
@@ -45,16 +45,21 @@ struct DataV3 {
|
||||
|
||||
|
||||
TEST(SerializeExtensionGrowable, WriteSessionsDataAtBufferEndAfterFlush) {
|
||||
BasicSerializationContext<SessionsEnabledConfig> ctx;
|
||||
ctx.createSerializer().ext(int8_t{}, Growable{}, [] (int8_t& v) { });
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(0));
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
auto& ser = ctx.createSerializer();
|
||||
//session cannot be empty
|
||||
ser.ext(int8_t{}, Growable{}, [&ser] (int8_t& v) {
|
||||
ser.value1b(v);
|
||||
});
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(1));
|
||||
ctx.bw->flush();
|
||||
EXPECT_THAT(ctx.getBufferSize(), Gt(0));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Gt(1));
|
||||
}
|
||||
|
||||
|
||||
TEST(SerializeExtensionGrowable, SessionDataConsistOfSessionsEndPosAnd4BytesSessionsDataOffset) {
|
||||
BasicSerializationContext<SessionsEnabledConfig> ctx;
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
|
||||
|
||||
constexpr size_t DATA_SIZE = 4;
|
||||
@@ -84,7 +89,7 @@ TEST(SerializeExtensionGrowable, SessionDataConsistOfSessionsEndPosAnd4BytesSess
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, WhenNestedSessionsThenStoreEachDepthAndSize) {
|
||||
BasicSerializationContext<SessionsEnabledConfig> ctx;
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
DataV3 data{19457,846, 498418};
|
||||
ctx.createSerializer();
|
||||
ctx.bw->beginSession();
|
||||
@@ -116,7 +121,7 @@ TEST(SerializeExtensionGrowable, WhenNestedSessionsThenStoreEachDepthAndSize) {
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleSessionsReadSameVersionData) {
|
||||
BasicSerializationContext<SessionsEnabledConfig> ctx;
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
DataV2 data{8454,987451};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
@@ -142,7 +147,7 @@ TEST(SerializeExtensionGrowable, MultipleSessionsReadSameVersionData) {
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleSessionsReadNewerVersionData) {
|
||||
BasicSerializationContext<SessionsEnabledConfig> ctx;
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
DataV3 data{8454,987451,54};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
@@ -169,7 +174,7 @@ TEST(SerializeExtensionGrowable, MultipleSessionsReadNewerVersionData) {
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleSessionsReadOlderVersionData) {
|
||||
BasicSerializationContext<SessionsEnabledConfig> ctx;
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
DataV2 data{8454,987451};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
@@ -197,7 +202,7 @@ TEST(SerializeExtensionGrowable, MultipleSessionsReadOlderVersionData) {
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadSameVersionData) {
|
||||
BasicSerializationContext<SessionsEnabledConfig> ctx;
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
DataV2 data{8454,987451};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
@@ -233,7 +238,7 @@ TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadSameVersionData) {
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadOlderVersionData) {
|
||||
BasicSerializationContext<SessionsEnabledConfig> ctx;
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
DataV2 data{8454,987451};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
@@ -273,7 +278,7 @@ TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadOlderVersionData) {
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadNewerVersionData1) {
|
||||
BasicSerializationContext<SessionsEnabledConfig> ctx;
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
DataV3 data{8454,987451,54};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
@@ -329,7 +334,7 @@ TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadNewerVersionData1) {
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadNewerVersionData2) {
|
||||
BasicSerializationContext<SessionsEnabledConfig> ctx;
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
DataV3 data{8454,987451,54};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
@@ -391,7 +396,7 @@ TEST(SerializeExtensionGrowable, MultipleNestedSessionsReadNewerVersionData2) {
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionGrowable, SessionsStartsAtEndOfSerialization) {
|
||||
BasicSerializationContext<SessionsEnabledConfig> ctx;
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
DataV2 data{8454,987451};
|
||||
ctx.createSerializer();
|
||||
auto& bw = (*ctx.bw);
|
||||
|
||||
355
tests/serialization_ext_inheritance.cpp
Normal file
355
tests/serialization_ext_inheritance.cpp
Normal file
@@ -0,0 +1,355 @@
|
||||
//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/ext/inheritance.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
using bitsery::ext::BaseClass;
|
||||
using bitsery::ext::VirtualBaseClass;
|
||||
|
||||
struct ConfigWithInheritanceCtx:bitsery::DefaultConfig {
|
||||
using InternalContext = std::tuple<bitsery::ext::InheritanceContext>;
|
||||
};
|
||||
|
||||
using SerContext = BasicSerializationContext<ConfigWithInheritanceCtx, void>;
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
/*
|
||||
* base class
|
||||
*/
|
||||
struct Base {
|
||||
uint8_t x{};
|
||||
virtual ~Base() = default;
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, Base& o) {
|
||||
s.value1b(o.x);
|
||||
}
|
||||
|
||||
/*
|
||||
* non virtual inheritance from base
|
||||
*/
|
||||
struct Derive1NonVirtually:Base {
|
||||
uint8_t y1{};
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, Derive1NonVirtually& o) {
|
||||
s.ext(o, BaseClass<Base>{});
|
||||
s.value1b(o.y1);
|
||||
}
|
||||
|
||||
struct Derive2NonVirtually:Base {
|
||||
uint8_t y2{};
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, Derive2NonVirtually& o) {
|
||||
//use lambda to serialize base
|
||||
s.ext(o, BaseClass<Base>{}, [&s](Base& b) {
|
||||
s.object(b);
|
||||
});
|
||||
s.value1b(o.y2);
|
||||
}
|
||||
|
||||
struct MultipleInheritanceNonVirtualBase: Derive1NonVirtually, Derive2NonVirtually {
|
||||
uint8_t z{};
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, MultipleInheritanceNonVirtualBase& o) {
|
||||
s.ext(o, BaseClass<Derive1NonVirtually>{});
|
||||
s.ext(o, BaseClass<Derive2NonVirtually>{});
|
||||
s.value1b(o.z);
|
||||
}
|
||||
|
||||
/*
|
||||
* virtual inheritance from base
|
||||
*/
|
||||
struct Derive1Virtually:virtual Base {
|
||||
uint8_t y1{};
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, Derive1Virtually& o) {
|
||||
s.ext(o, VirtualBaseClass<Base>{});
|
||||
s.value1b(o.y1);
|
||||
}
|
||||
|
||||
struct Derive2Virtually:virtual Base {
|
||||
uint8_t y2{};
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, Derive2Virtually& o) {
|
||||
s.ext(o, VirtualBaseClass<Base>{});
|
||||
s.value1b(o.y2);
|
||||
}
|
||||
|
||||
struct MultipleInheritanceVirtualBase: Derive1Virtually, Derive2Virtually {
|
||||
uint8_t z{};
|
||||
MultipleInheritanceVirtualBase() = default;
|
||||
|
||||
MultipleInheritanceVirtualBase(uint8_t x_, uint8_t y1_, uint8_t y2_, uint8_t z_) {
|
||||
x = x_;
|
||||
y1 = y1_;
|
||||
y2 = y2_;
|
||||
z = z_;
|
||||
}
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.ext(*this, BaseClass<Derive1Virtually>{});
|
||||
s.ext(*this, BaseClass<Derive2Virtually>{});
|
||||
s.value1b(z);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
bool operator == (const MultipleInheritanceVirtualBase& lhs, const MultipleInheritanceVirtualBase& rhs) {
|
||||
return std::tie(lhs.x, lhs.y1, lhs.y2, lhs.z) == std::tie(rhs.x, rhs.y1, rhs.y2, rhs.z);
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionInheritance, BaseClass) {
|
||||
|
||||
Derive1NonVirtually d1{};
|
||||
d1.x = 187;
|
||||
d1.y1 = 74;
|
||||
Derive1NonVirtually rd1{};
|
||||
|
||||
SerContext ctx{};
|
||||
ctx.createSerializer().object(d1);
|
||||
ctx.createDeserializer().object(rd1);
|
||||
|
||||
EXPECT_THAT(rd1.x, Eq(d1.x));
|
||||
EXPECT_THAT(rd1.y1, Eq(d1.y1));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(2));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionInheritance, VirtualBaseClass) {
|
||||
Derive1Virtually d1{};
|
||||
d1.x = 15;
|
||||
d1.y1 = 87;
|
||||
Derive1Virtually rd1{};
|
||||
|
||||
SerContext ctx{};
|
||||
ctx.createSerializer().object(d1);
|
||||
ctx.createDeserializer().object(rd1);
|
||||
|
||||
EXPECT_THAT(rd1.x, Eq(d1.x));
|
||||
EXPECT_THAT(rd1.y1, Eq(d1.y1));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(2));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionInheritance, MultipleBasesWithoutVirtualInheritance) {
|
||||
MultipleInheritanceNonVirtualBase md{};
|
||||
//x is ambiguous because we don't derive virtually
|
||||
static_cast<Derive1NonVirtually&>(md).x = 1;
|
||||
static_cast<Derive2NonVirtually&>(md).x = 2;
|
||||
md.y1 = 4;
|
||||
md.z = 5;
|
||||
md.y2 = 6;
|
||||
MultipleInheritanceNonVirtualBase res{};
|
||||
|
||||
SerContext ctx{};
|
||||
ctx.createSerializer().object(md);
|
||||
ctx.createDeserializer().object(res);
|
||||
|
||||
EXPECT_THAT(static_cast<Derive1NonVirtually&>(res).x, Eq(static_cast<Derive1NonVirtually&>(md).x));
|
||||
EXPECT_THAT(static_cast<Derive2NonVirtually&>(res).x, Eq(static_cast<Derive2NonVirtually&>(md).x));
|
||||
EXPECT_THAT(res.y1, Eq(md.y1));
|
||||
EXPECT_THAT(res.y2, Eq(md.y2));
|
||||
EXPECT_THAT(res.z, Eq(md.z));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(5)); //5 because two bases
|
||||
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionInheritance, WhenNoVirtualInheritanceExistsThenInheritanceContextIsNotRequired) {
|
||||
MultipleInheritanceNonVirtualBase md{};
|
||||
//x is ambiguous because we don't derive virtually
|
||||
static_cast<Derive1NonVirtually&>(md).x = 1;
|
||||
static_cast<Derive2NonVirtually&>(md).x = 2;
|
||||
md.y1 = 4;
|
||||
md.z = 5;
|
||||
md.y2 = 6;
|
||||
MultipleInheritanceNonVirtualBase res{};
|
||||
|
||||
//without InheritanceContext
|
||||
SerializationContext ctx{};
|
||||
ctx.createSerializer().object(md);
|
||||
ctx.createDeserializer().object(res);
|
||||
|
||||
EXPECT_THAT(static_cast<Derive1NonVirtually&>(res).x, Eq(static_cast<Derive1NonVirtually&>(md).x));
|
||||
EXPECT_THAT(static_cast<Derive2NonVirtually&>(res).x, Eq(static_cast<Derive2NonVirtually&>(md).x));
|
||||
EXPECT_THAT(res.y1, Eq(md.y1));
|
||||
EXPECT_THAT(res.y2, Eq(md.y2));
|
||||
EXPECT_THAT(res.z, Eq(md.z));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(5)); //5 because two bases
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
TEST(SerializeExtensionInheritance, MultipleBasesWithVirtualInheritance) {
|
||||
MultipleInheritanceVirtualBase md{3,7,5,15};
|
||||
MultipleInheritanceVirtualBase res{};
|
||||
|
||||
SerContext ctx{};
|
||||
ctx.createSerializer().object(md);
|
||||
ctx.createDeserializer().object(res);
|
||||
EXPECT_THAT(res, Eq(md));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(4)); //4 because virtual base
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionInheritance, MultipleBasesWithVirtualInheritanceMultipleObjects) {
|
||||
std::vector<MultipleInheritanceVirtualBase> data;
|
||||
data.emplace_back(4,8,7,9);
|
||||
data.emplace_back(1,2,3,4);
|
||||
data.emplace_back(8,7,15,97);
|
||||
data.emplace_back(54,132,45,84);
|
||||
data.emplace_back(27,85,41,2);
|
||||
std::vector<MultipleInheritanceVirtualBase> res{};
|
||||
|
||||
SerContext ctx{};
|
||||
ctx.createSerializer().container(data, 10);
|
||||
ctx.createDeserializer().container(res, 10);
|
||||
EXPECT_THAT(res, ::testing::ContainerEq(data));
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(1 + 4 * data.size())); //1 container size + 4 because virtual base * elements
|
||||
}
|
||||
|
||||
//
|
||||
class BasePrivateSerialize {
|
||||
public:
|
||||
explicit BasePrivateSerialize(uint8_t v):_v{v} {}
|
||||
uint8_t getX() const { return _v; }
|
||||
private:
|
||||
uint8_t _v;
|
||||
|
||||
friend bitsery::Access;
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value1b(_v);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class DerivedPrivateBase: public BasePrivateSerialize {
|
||||
public:
|
||||
explicit DerivedPrivateBase(uint8_t v) : BasePrivateSerialize(v) {}
|
||||
uint8_t z{};
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, DerivedPrivateBase& o) {
|
||||
//use lambda for base serialization
|
||||
s.ext(o, BaseClass<BasePrivateSerialize>{}, [&s](BasePrivateSerialize& b) {
|
||||
s.object(b);
|
||||
});
|
||||
s.value1b(o.z);
|
||||
}
|
||||
|
||||
struct BaseNonMemberSerialize {
|
||||
uint8_t x{};
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s, BaseNonMemberSerialize& o) {
|
||||
s.value1b(o.x);
|
||||
}
|
||||
|
||||
|
||||
struct DerivedMemberSerialize: public BaseNonMemberSerialize {
|
||||
uint8_t z{};
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.ext(*this, BaseClass<BaseNonMemberSerialize>{});
|
||||
s.value1b(z);
|
||||
}
|
||||
};
|
||||
|
||||
//explicitly select serialize functions, for types that has ambiguous serialize functions
|
||||
namespace bitsery {
|
||||
template <>
|
||||
struct SelectSerializeFnc<DerivedPrivateBase>:UseNonMemberFnc {};
|
||||
|
||||
template <>
|
||||
struct SelectSerializeFnc<DerivedMemberSerialize>:UseMemberFnc {};
|
||||
}
|
||||
|
||||
|
||||
TEST(SerializeExtensionInheritance, WhenDerivedClassHasAmbiguousSerializeFunctionThenExplicitlySelectSpecialization) {
|
||||
DerivedPrivateBase data1{43};
|
||||
data1.z = 87;
|
||||
DerivedMemberSerialize data2{};
|
||||
data2.x = 71;
|
||||
data2.z = 22;
|
||||
DerivedPrivateBase res1{0};
|
||||
DerivedMemberSerialize res2{};
|
||||
|
||||
SerContext ctx{};
|
||||
ctx.createSerializer().object(data1);
|
||||
ctx.createSerializer().object(data2);
|
||||
ctx.createDeserializer().object(res1);
|
||||
ctx.createDeserializer().object(res2);
|
||||
EXPECT_THAT(res1.getX(), Eq(data1.getX()));
|
||||
EXPECT_THAT(res1.z, Eq(data1.z));
|
||||
EXPECT_THAT(res2.x, Eq(data2.x));
|
||||
EXPECT_THAT(res2.z, Eq(data2.z));
|
||||
}
|
||||
|
||||
struct AbstractBase {
|
||||
uint8_t x{};
|
||||
virtual void exec() = 0;
|
||||
virtual ~AbstractBase() = default;
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.value1b(x);
|
||||
}
|
||||
};
|
||||
|
||||
struct ImplementedBase:AbstractBase {
|
||||
uint8_t y{};
|
||||
void exec() override {}
|
||||
|
||||
template <typename S>
|
||||
void serialize(S& s) {
|
||||
s.ext(*this, BaseClass<AbstractBase>{});
|
||||
s.value1b(y);
|
||||
}
|
||||
};
|
||||
|
||||
TEST(SerializeExtensionInheritance, CanSerializeAbstractClass) {
|
||||
ImplementedBase data{};
|
||||
data.x = 4;
|
||||
data.y = 2;
|
||||
data.exec();
|
||||
ImplementedBase res{};
|
||||
SerContext ctx{};
|
||||
ctx.createSerializer().object(data);
|
||||
ctx.createDeserializer().object(res);
|
||||
EXPECT_THAT(res.x, Eq(data.x));
|
||||
EXPECT_THAT(res.y, Eq(data.y));
|
||||
}
|
||||
543
tests/serialization_ext_pointer.cpp
Normal file
543
tests/serialization_ext_pointer.cpp
Normal file
@@ -0,0 +1,543 @@
|
||||
//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/ext/pointer.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
using bitsery::ext::PointerOwner;
|
||||
using bitsery::ext::PointerObserver;
|
||||
using bitsery::ext::ReferencedByPointer;
|
||||
using bitsery::ext::PointerLinkingContext;
|
||||
using bitsery::ext::PointerType;
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
using SerContext = BasicSerializationContext<bitsery::DefaultConfig, PointerLinkingContext>;
|
||||
|
||||
class SerializeExtensionPointerSerialization : public testing::Test {
|
||||
public:
|
||||
//data used for serialization
|
||||
int16_t d1{1597};
|
||||
int16_t *pd1 = &d1;
|
||||
MyEnumClass d2{MyEnumClass::E2};
|
||||
MyEnumClass *pd2 = &d2;
|
||||
MyStruct1 d3{184, 897};
|
||||
MyStruct1 *pd3 = &d3;
|
||||
|
||||
//data used for deserialization
|
||||
int16_t r1{-84};
|
||||
int16_t *pr1 = &r1;
|
||||
MyEnumClass r2{MyEnumClass::E4};
|
||||
MyEnumClass *pr2 = &r2;
|
||||
MyStruct1 r3{-4984, -14597};
|
||||
MyStruct1 *pr3 = &r3;
|
||||
|
||||
//null pointers
|
||||
int16_t *p1null = nullptr;
|
||||
MyEnumClass *p2null = nullptr;
|
||||
MyStruct1 *p3null = nullptr;
|
||||
|
||||
PointerLinkingContext plctx1{};
|
||||
SerContext sctx1{};
|
||||
|
||||
typename SerContext::TSerializer &createSerializer() {
|
||||
return sctx1.createSerializer(&plctx1);
|
||||
}
|
||||
|
||||
bool isPointerContextValid() {
|
||||
return plctx1.isValid();
|
||||
}
|
||||
};
|
||||
|
||||
TEST(SerializeExtensionPointer, RequiresPointerLinkingContext) {
|
||||
MyStruct1 *data = nullptr;
|
||||
//linking context
|
||||
PointerLinkingContext plctx1{};
|
||||
SerContext sctx1;
|
||||
sctx1.createSerializer(&plctx1).ext(data, PointerOwner{});
|
||||
sctx1.createDeserializer(&plctx1).ext(data, PointerOwner{});
|
||||
|
||||
//linking context in tuple
|
||||
using ContextInTuple = std::tuple<int, PointerLinkingContext, float, char>;
|
||||
ContextInTuple plctx2(0, PointerLinkingContext{}, 0.0f, 'a');
|
||||
BasicSerializationContext<SessionsEnabledConfig, ContextInTuple> sctx2;
|
||||
sctx2.createSerializer(&plctx2).ext(data, PointerObserver{});
|
||||
sctx2.createDeserializer(&plctx2).ext(data, PointerObserver{});
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionPointer, PointerLinkingContextAcceptsMultipleSharedOwnersAndReturnSameId) {
|
||||
MyStruct1 data{};
|
||||
//pretend that this is shared ptr
|
||||
MyStruct1 *sharedPtr = &data;
|
||||
//linking context
|
||||
PointerLinkingContext plctx1{};
|
||||
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr, bitsery::ext::PointerOwnershipType::SharedOwner).id, Eq(1));
|
||||
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr, bitsery::ext::PointerOwnershipType::SharedObserver).id, Eq(1));
|
||||
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr, bitsery::ext::PointerOwnershipType::SharedOwner).id, Eq(1));
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionPointer, WhenOnlySharedObserverThenPointerLinkingContextIsInvalid) {
|
||||
MyStruct1 data1{};
|
||||
MyStruct1 data2{};
|
||||
//pretend that this is shared ptr
|
||||
MyStruct1 *sharedPtr1 = &data1;
|
||||
MyStruct1 *sharedPtr2 = &data2;
|
||||
//linking context
|
||||
PointerLinkingContext plctx1{};
|
||||
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr1, bitsery::ext::PointerOwnershipType::SharedObserver).id, Eq(1));
|
||||
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr2, bitsery::ext::PointerOwnershipType::SharedObserver).id, Eq(2));
|
||||
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr1, bitsery::ext::PointerOwnershipType::SharedObserver).id, Eq(1));
|
||||
EXPECT_FALSE(plctx1.isValid());
|
||||
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr1, bitsery::ext::PointerOwnershipType::SharedOwner).id, Eq(1));
|
||||
EXPECT_THAT(plctx1.getInfoByPtr(sharedPtr2, bitsery::ext::PointerOwnershipType::SharedOwner).id, Eq(2));
|
||||
EXPECT_TRUE(plctx1.isValid());
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerSerialization, WhenPointersAreNullThenIsValid) {
|
||||
|
||||
auto &ser = createSerializer();
|
||||
ser.ext2b(p1null, PointerOwner{});
|
||||
ser.ext2b(p1null, PointerObserver{});
|
||||
ser.ext(p3null, PointerOwner{});
|
||||
ser.ext(p3null, PointerObserver{});
|
||||
sctx1.createDeserializer();
|
||||
EXPECT_THAT(sctx1.bw->writtenBytesCount(), Eq(4));
|
||||
|
||||
EXPECT_THAT(plctx1.isValid(), Eq(true));
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
TEST(SerializeExtensionPointer, WhenPointerLinkingContextIsNullAndPointerIsNotNullThenAssert) {
|
||||
MyStruct1 tmp;
|
||||
MyStruct1 *data = &tmp;
|
||||
//linking context
|
||||
PointerLinkingContext plctx1{};
|
||||
SerContext sctx1;
|
||||
EXPECT_DEATH(sctx1.createSerializer(nullptr).ext(data, PointerOwner{}), "");
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerSerialization, WhenPointerOwnerIsNotUniqueThenAssert) {
|
||||
|
||||
auto &ser = createSerializer();
|
||||
ser.ext2b(p1null, PointerOwner{});
|
||||
ser.ext2b(pd1, PointerOwner{});
|
||||
ser.ext4b(pd2, PointerOwner{});
|
||||
ser.ext2b(p1null, PointerOwner{});
|
||||
//dublicating pointer
|
||||
EXPECT_DEATH(ser.ext2b(pd1, PointerOwner{}), "");
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerSerialization, WhenRererencedByPointerIsSameAsPointerOwnerThenAssert1) {
|
||||
auto &ser1 = createSerializer();
|
||||
ser1.ext4b(pd2, PointerOwner{});
|
||||
ser1.ext(d3, ReferencedByPointer{});
|
||||
|
||||
EXPECT_DEATH(ser1.ext(pd3, PointerOwner{}), "");
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerSerialization, WhenRererencedByPointerIsSameAsPointerOwnerThenAssert2) {
|
||||
auto &ser1 = createSerializer();
|
||||
ser1.ext2b(pd1, PointerOwner{});
|
||||
ser1.ext4b(d2, ReferencedByPointer{});
|
||||
EXPECT_DEATH(ser1.ext2b(d1, ReferencedByPointer{}), "");
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerSerialization, WhenNonNullPointerIsNullThenAssert) {
|
||||
auto &ser1 = createSerializer();
|
||||
EXPECT_DEATH(ser1.ext2b(p1null, PointerOwner{PointerType::NotNull}), "");
|
||||
EXPECT_DEATH(ser1.ext2b(p1null, PointerObserver{PointerType::NotNull}), "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_F(SerializeExtensionPointerSerialization, WhenPointerObserverPointsToOwnerThenIsValid) {
|
||||
auto &ser1 = createSerializer();
|
||||
ser1.ext2b(pd1, PointerOwner{});
|
||||
ser1.ext2b(p1null, PointerObserver{});
|
||||
EXPECT_THAT(plctx1.isValid(), Eq(true));
|
||||
ser1.ext4b(pd2, PointerObserver{});//points to d2, and d2 is not still marked as owner
|
||||
EXPECT_THAT(plctx1.isValid(), Eq(false));
|
||||
ser1.ext4b(pd2, PointerOwner{});//now d2 is owning pointer
|
||||
ser1.ext4b(pd2, PointerObserver{});//points to d2, but this time d2 has owner
|
||||
ser1.ext2b(p1null, PointerObserver{});
|
||||
EXPECT_THAT(plctx1.isValid(), Eq(true));
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerSerialization, ReferenceTypeCanAlsoBeReferencedByPointerObservers) {
|
||||
auto &ser1 = createSerializer();
|
||||
ser1.ext2b(p1null, PointerObserver{});
|
||||
EXPECT_THAT(plctx1.isValid(), Eq(true));
|
||||
ser1.ext4b(pd2, PointerObserver{});//points to d2, and d2 is not still marked as owner
|
||||
EXPECT_THAT(plctx1.isValid(), Eq(false));
|
||||
ser1.ext4b(d2, ReferencedByPointer{});//now d2 is marked by marked as owning pointer
|
||||
ser1.ext4b(pd2, PointerObserver{});//points to d2, but this time d2 has owner
|
||||
ser1.ext(p3null, PointerObserver{});
|
||||
EXPECT_THAT(plctx1.isValid(), Eq(true));
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerSerialization, WhenPointerIsNullThenPointerIdIsZero) {
|
||||
auto &ser1 = createSerializer();
|
||||
ser1.ext(p3null, PointerOwner{});
|
||||
ser1.ext2b(p1null, PointerObserver{});
|
||||
sctx1.createDeserializer();
|
||||
EXPECT_THAT(sctx1.bw->writtenBytesCount(), Eq(2));
|
||||
size_t res;
|
||||
bitsery::details::readSize(*sctx1.br, res, 10000u);
|
||||
EXPECT_THAT(res, Eq(0));
|
||||
bitsery::details::readSize(*sctx1.br, res, 10000u);
|
||||
EXPECT_THAT(res, Eq(0));
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerSerialization, PointerIdsStartsFromOne) {
|
||||
auto &ser1 = createSerializer();
|
||||
ser1.ext2b(pd1, PointerObserver{});
|
||||
ser1.ext4b(pd2, PointerObserver{});
|
||||
ser1.ext4b(pd2, PointerObserver{});
|
||||
ser1.ext2b(p1null, PointerObserver{});
|
||||
sctx1.createDeserializer();
|
||||
EXPECT_THAT(sctx1.bw->writtenBytesCount(), Eq(4));
|
||||
size_t res;
|
||||
bitsery::details::readSize(*sctx1.br, res, 10000u);
|
||||
EXPECT_THAT(res, Eq(1));
|
||||
bitsery::details::readSize(*sctx1.br, res, 10000u);
|
||||
EXPECT_THAT(res, Eq(2));
|
||||
bitsery::details::readSize(*sctx1.br, res, 10000u);
|
||||
EXPECT_THAT(res, Eq(2));
|
||||
bitsery::details::readSize(*sctx1.br, res, 10000u);
|
||||
EXPECT_THAT(res, Eq(0));
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerSerialization, PointerObserversDoesntSerializeObject) {
|
||||
auto &ser1 = createSerializer();
|
||||
ser1.ext2b(pd1, PointerObserver{});
|
||||
ser1.ext4b(pd2, PointerObserver{});
|
||||
ser1.ext4b(pd2, PointerObserver{});
|
||||
sctx1.createDeserializer();
|
||||
EXPECT_THAT(sctx1.bw->writtenBytesCount(), Eq(3));
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerSerialization, ReferencedByPointerSerializesIdAndObject) {
|
||||
auto &ser1 = createSerializer();
|
||||
ser1.ext2b(d1, ReferencedByPointer{});
|
||||
ser1.ext4b(d2, ReferencedByPointer{});
|
||||
ser1.ext4b(pd2, PointerObserver{});
|
||||
auto &des = sctx1.createDeserializer();
|
||||
EXPECT_THAT(sctx1.bw->writtenBytesCount(), Eq(3 + 6));
|
||||
size_t id{};
|
||||
bitsery::details::readSize(*sctx1.br, id, 10000u);
|
||||
EXPECT_THAT(id, Eq(1));
|
||||
des.value2b(r1);
|
||||
EXPECT_THAT(r1, Eq(d1));
|
||||
bitsery::details::readSize(*sctx1.br, id, 10000u);
|
||||
EXPECT_THAT(id, Eq(2));
|
||||
des.value4b(r2);
|
||||
EXPECT_THAT(r2, Eq(d2));
|
||||
bitsery::details::readSize(*sctx1.br, id, 10000u);
|
||||
EXPECT_THAT(id, Eq(2));
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerSerialization, PointerOwnerSerializesIdAndObject) {
|
||||
auto &ser1 = createSerializer();
|
||||
ser1.ext4b(pd2, PointerOwner{});
|
||||
ser1.ext(pd3, PointerOwner{});
|
||||
auto &des1 = sctx1.createDeserializer();
|
||||
//2x ids + int32_t + MyStruct1
|
||||
EXPECT_THAT(sctx1.bw->writtenBytesCount(), Eq(2 + 4 + MyStruct1::SIZE));
|
||||
size_t id;
|
||||
bitsery::details::readSize(*sctx1.br, id, 10000u);
|
||||
des1.value4b(r2);
|
||||
EXPECT_THAT(r2, Eq(*pd2));
|
||||
bitsery::details::readSize(*sctx1.br, id, 10000u);
|
||||
des1.object(r3);
|
||||
EXPECT_THAT(r3, Eq(*pd3));
|
||||
}
|
||||
|
||||
class SerializeExtensionPointerDeserialization : public SerializeExtensionPointerSerialization {
|
||||
public:
|
||||
|
||||
typename SerContext::TSerializer &createSerializer() {
|
||||
return sctx1.createSerializer(&plctx1);
|
||||
}
|
||||
|
||||
typename SerContext::TDeserializer &createDeserializer() {
|
||||
return sctx1.createDeserializer(&plctx1);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
TEST_F(SerializeExtensionPointerDeserialization, ReferencedByPointer) {
|
||||
auto &ser = createSerializer();
|
||||
ser.ext2b(d1, ReferencedByPointer{});
|
||||
ser.ext4b(d2, ReferencedByPointer{});
|
||||
ser.ext(d3, ReferencedByPointer{});
|
||||
auto &des = createDeserializer();
|
||||
des.ext2b(r1, ReferencedByPointer{});
|
||||
des.ext4b(r2, ReferencedByPointer{});
|
||||
des.ext(r3, ReferencedByPointer{});
|
||||
|
||||
EXPECT_THAT(r1, Eq(d1));
|
||||
EXPECT_THAT(r2, Eq(d2));
|
||||
EXPECT_THAT(r3, Eq(d3));
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerDeserialization, WhenReferencedByPointerReadsNullPointerThenInvalidPointerError) {
|
||||
auto &ser = createSerializer();
|
||||
bitsery::details::writeSize(*sctx1.bw, 0u);
|
||||
ser.ext2b(d1, ReferencedByPointer{});
|
||||
auto &des = createDeserializer();
|
||||
des.ext2b(r1, ReferencedByPointer{});
|
||||
EXPECT_THAT(sctx1.br->error(), Eq(bitsery::ReaderError::InvalidPointer));
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerDeserialization, WhenNonNullPointerIsNullThenInvalidPointerError) {
|
||||
createSerializer();
|
||||
bitsery::details::writeSize(*sctx1.bw, 0u);
|
||||
auto &des1 = createDeserializer();
|
||||
des1.ext2b(p1null, PointerOwner{PointerType::NotNull});
|
||||
EXPECT_THAT(sctx1.br->error(), Eq(bitsery::ReaderError::InvalidPointer));
|
||||
|
||||
auto &des2 = createDeserializer();
|
||||
des2.ext2b(p1null, PointerObserver{PointerType::NotNull});
|
||||
EXPECT_THAT(sctx1.br->error(), Eq(bitsery::ReaderError::InvalidPointer));
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerDeserialization, PointerOwnerCreatesObjects) {
|
||||
auto &ser = createSerializer();
|
||||
ser.ext2b(pd1, PointerOwner{});
|
||||
ser.ext4b(pd2, PointerOwner{});
|
||||
ser.ext(pd3, PointerOwner{});
|
||||
auto &des = createDeserializer();
|
||||
des.ext2b(p1null, PointerOwner{});
|
||||
des.ext4b(p2null, PointerOwner{});
|
||||
des.ext(p3null, PointerOwner{});
|
||||
|
||||
EXPECT_THAT(isPointerContextValid(), Eq(true));
|
||||
EXPECT_THAT(p1null, ::testing::NotNull());
|
||||
EXPECT_THAT(p2null, ::testing::NotNull());
|
||||
EXPECT_THAT(p3null, ::testing::NotNull());
|
||||
EXPECT_THAT(*p1null, Eq(*pd1));
|
||||
EXPECT_THAT(*p2null, Eq(*pd2));
|
||||
EXPECT_THAT(*p3null, Eq(*pd3));
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerDeserialization, PointerOwnerDestroysObjects) {
|
||||
auto &ser = createSerializer();
|
||||
ser.ext2b(p1null, PointerOwner{});
|
||||
ser.ext4b(p2null, PointerOwner{});
|
||||
ser.ext(p3null, PointerOwner{});
|
||||
auto &des = createDeserializer();
|
||||
//pr cannot link to local variables, need to allocate them separately
|
||||
pr1 = new int16_t{};
|
||||
pr2 = new MyEnumClass{};
|
||||
pr3 = new MyStruct1{3, 4};
|
||||
des.ext2b(pr1, PointerOwner{});
|
||||
des.ext4b(pr2, PointerOwner{});
|
||||
des.ext(pr3, PointerOwner{});
|
||||
|
||||
EXPECT_THAT(isPointerContextValid(), Eq(true));
|
||||
EXPECT_THAT(pr1, ::testing::IsNull());
|
||||
EXPECT_THAT(pr2, ::testing::IsNull());
|
||||
EXPECT_THAT(pr3, ::testing::IsNull());
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerDeserialization, PointerObserver) {
|
||||
auto &ser = createSerializer();
|
||||
//first owner, than observer
|
||||
ser.ext4b(d2, ReferencedByPointer{});
|
||||
ser.ext2b(p1null, PointerObserver{});
|
||||
ser.ext4b(pd2, PointerObserver{});
|
||||
//first observer, than owner
|
||||
ser.ext(pd3, PointerObserver{});
|
||||
ser.ext(pd3, PointerOwner{});
|
||||
auto &des = createDeserializer();
|
||||
des.ext4b(r2, ReferencedByPointer{});
|
||||
des.ext2b(pr1, PointerObserver{});
|
||||
des.ext4b(p2null, PointerObserver{});
|
||||
des.ext(pr3, PointerObserver{});
|
||||
des.ext(pr3, PointerOwner{});
|
||||
|
||||
EXPECT_THAT(isPointerContextValid(), Eq(true));
|
||||
//serialize null, override non-null
|
||||
EXPECT_THAT(pr1, Eq(p1null));
|
||||
//serialize non-null, override null
|
||||
EXPECT_THAT(*p2null, Eq(*pd2));
|
||||
EXPECT_THAT(p2null, Eq(&r2));
|
||||
//serialize non-null override non-null
|
||||
EXPECT_THAT(*pr3, Eq(*pd3));
|
||||
EXPECT_THAT(pr3, Eq(&r3));
|
||||
}
|
||||
|
||||
struct Test1Data {
|
||||
std::vector<MyStruct1> vdata;
|
||||
std::vector<MyStruct1 *> vptr;
|
||||
MyStruct1 o1;
|
||||
MyStruct1 *po1;
|
||||
int32_t i1;
|
||||
int32_t *pi1;
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s) {
|
||||
//set container elements to be candidates for non-owning pointers
|
||||
s.container(vdata, 100, [&s](MyStruct1 &d) {
|
||||
s.ext(d, ReferencedByPointer{});
|
||||
});
|
||||
//contains non owning pointers
|
||||
//
|
||||
//IMPORTANT !!!
|
||||
// ALWAYS ACCEPT BY REFERENCE like this: T* (&obj)
|
||||
//
|
||||
s.container(vptr, 100, [&s](MyStruct1 *(&d)) {
|
||||
s.ext(d, PointerObserver{});
|
||||
});
|
||||
//just a regular fields
|
||||
s.object(o1);
|
||||
s.value4b(i1);
|
||||
//observer
|
||||
s.ext(po1, PointerObserver{});
|
||||
//owner
|
||||
s.ext4b(pi1, PointerOwner{});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
TEST(SerializeExtensionPointer, IntegrationTest) {
|
||||
|
||||
Test1Data data{};
|
||||
data.vdata.push_back({165, -45});
|
||||
data.vdata.push_back({7895, -1576});
|
||||
data.vdata.push_back({5987, -798});
|
||||
//container of non owning pointers (observers)
|
||||
data.vptr.push_back(nullptr);
|
||||
data.vptr.push_back(std::addressof(data.vdata[0]));
|
||||
data.vptr.push_back(std::addressof(data.vdata[2]));
|
||||
//regular fields
|
||||
data.o1 = MyStruct1{145, 948};
|
||||
data.i1 = 945415;
|
||||
//observer
|
||||
data.po1 = std::addressof(data.vdata[1]);
|
||||
//owning pointer
|
||||
data.pi1 = new int32_t{};
|
||||
|
||||
Test1Data res{};
|
||||
|
||||
PointerLinkingContext plctx1{};
|
||||
SerContext sctx1;
|
||||
sctx1.createSerializer(&plctx1).object(data);
|
||||
sctx1.createDeserializer(&plctx1).object(res);
|
||||
|
||||
EXPECT_THAT(plctx1.isValid(), Eq(true));
|
||||
//check regular fields
|
||||
EXPECT_THAT(res.i1, Eq(data.i1));
|
||||
EXPECT_THAT(res.o1, Eq(data.o1));
|
||||
//check data container
|
||||
EXPECT_THAT(res.vdata, ::testing::ContainerEq(data.vdata));
|
||||
//check owning pointers
|
||||
EXPECT_THAT(*res.pi1, Eq(*data.pi1));
|
||||
EXPECT_THAT(res.pi1, ::testing::Ne(data.pi1));
|
||||
//check if observers points to correct data
|
||||
EXPECT_THAT(res.po1, Eq(std::addressof(res.vdata[1])));
|
||||
EXPECT_THAT(res.vptr[0], ::testing::IsNull());
|
||||
EXPECT_THAT(res.vptr[1], Eq(std::addressof(res.vdata[0])));
|
||||
EXPECT_THAT(res.vptr[2], Eq(std::addressof(res.vdata[2])));
|
||||
|
||||
//free owning raw pointers
|
||||
delete data.pi1;
|
||||
delete res.pi1;
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionPointer, PointerOwnerWithNonPolymorphicTypeCanUseLambdaOverload) {
|
||||
const int32_t NEW_VALUE = 2;
|
||||
const int32_t OLD_VALUE = 1;
|
||||
MyStruct1 *data = new MyStruct1{NEW_VALUE, NEW_VALUE};
|
||||
MyStruct1 *res = new MyStruct1{OLD_VALUE, OLD_VALUE};
|
||||
//linking context
|
||||
PointerLinkingContext plctx1{};
|
||||
SerContext sctx1;
|
||||
auto &ser = sctx1.createSerializer(&plctx1);
|
||||
ser.ext(data, PointerOwner{}, [&ser](MyStruct1 &o) {
|
||||
//serialize only one field
|
||||
ser.value4b(o.i1);
|
||||
});
|
||||
auto &des = sctx1.createDeserializer(&plctx1);
|
||||
des.ext(res, PointerOwner{}, [&des](MyStruct1 &o) {
|
||||
//deserialize only one field
|
||||
des.value4b(o.i1);
|
||||
});
|
||||
|
||||
EXPECT_THAT(res->i1, Eq(NEW_VALUE));
|
||||
EXPECT_THAT(res->i2, Eq(OLD_VALUE));//we didn't serialized that
|
||||
|
||||
delete data;
|
||||
delete res;
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionPointer, ReferencedByPointerCanUseLambdaOverload) {
|
||||
const int32_t NEW_VALUE = 2;
|
||||
const int32_t OLD_VALUE = 1;
|
||||
MyStruct1 data = MyStruct1{NEW_VALUE, NEW_VALUE};
|
||||
MyStruct1 res = MyStruct1{OLD_VALUE, OLD_VALUE};
|
||||
//linking context
|
||||
PointerLinkingContext plctx1{};
|
||||
SerContext sctx1;
|
||||
auto &ser = sctx1.createSerializer(&plctx1);
|
||||
ser.ext(data, ReferencedByPointer{}, [&ser](MyStruct1 &o) {
|
||||
//serialize only one field
|
||||
ser.value4b(o.i1);
|
||||
});
|
||||
auto &des = sctx1.createDeserializer(&plctx1);
|
||||
des.ext(res, ReferencedByPointer{}, [&des](MyStruct1 &o) {
|
||||
//deserialize only one field
|
||||
des.value4b(o.i1);
|
||||
});
|
||||
|
||||
EXPECT_THAT(res.i1, Eq(NEW_VALUE));
|
||||
EXPECT_THAT(res.i2, Eq(OLD_VALUE));//we didn't serialized that
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionPointer, PointerOwnerCanUseValueOverload) {
|
||||
auto *data = new int64_t{49845894};
|
||||
auto *res = new int64_t{-78548415};
|
||||
|
||||
PointerLinkingContext plctx1{};
|
||||
SerContext sctx1;
|
||||
sctx1.createSerializer(&plctx1).ext8b(data, PointerOwner{});
|
||||
sctx1.createDeserializer(&plctx1).ext8b(res, PointerOwner{});
|
||||
|
||||
EXPECT_THAT(*res, Eq(*data));
|
||||
|
||||
delete data;
|
||||
delete res;
|
||||
}
|
||||
|
||||
TEST(SerializeExtensionPointer, ReferencedByPointerCanUseValueOverload) {
|
||||
int64_t data{49845894};
|
||||
int64_t res{-78548415};
|
||||
|
||||
PointerLinkingContext plctx1{};
|
||||
SerContext sctx1;
|
||||
sctx1.createSerializer(&plctx1).ext8b(data, ReferencedByPointer{});
|
||||
sctx1.createDeserializer(&plctx1).ext8b(res, ReferencedByPointer{});
|
||||
|
||||
EXPECT_THAT(res, Eq(data));
|
||||
}
|
||||
328
tests/serialization_ext_pointer_polymorphic_types.cpp
Normal file
328
tests/serialization_ext_pointer_polymorphic_types.cpp
Normal file
@@ -0,0 +1,328 @@
|
||||
//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/ext/inheritance.h>
|
||||
#include <bitsery/ext/pointer.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
using bitsery::ext::BaseClass;
|
||||
using bitsery::ext::VirtualBaseClass;
|
||||
|
||||
using bitsery::ext::InheritanceContext;
|
||||
using bitsery::ext::PointerLinkingContext;
|
||||
using bitsery::ext::PolymorphicContext;
|
||||
using bitsery::ext::StandardRTTI;
|
||||
|
||||
using bitsery::ext::PointerOwner;
|
||||
using bitsery::ext::PointerObserver;
|
||||
using bitsery::ext::ReferencedByPointer;
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
using TContext = std::tuple<PointerLinkingContext, InheritanceContext, PolymorphicContext<StandardRTTI>>;
|
||||
using SerContext = BasicSerializationContext<bitsery::DefaultConfig, TContext>;
|
||||
|
||||
//this is useful for PolymorphicContext to bind classes to serializer/deserializer
|
||||
using TSerializer = typename SerContext::TSerializer;
|
||||
using TDeserializer = typename SerContext::TDeserializer;
|
||||
|
||||
/*
|
||||
* base class
|
||||
*/
|
||||
struct Base {
|
||||
uint8_t x{};
|
||||
|
||||
virtual ~Base() = default;
|
||||
};
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, Base &o) {
|
||||
s.value1b(o.x);
|
||||
}
|
||||
|
||||
struct Derived1 : virtual Base {
|
||||
uint8_t y1{};
|
||||
};
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, Derived1 &o) {
|
||||
s.ext(o, VirtualBaseClass<Base>{});
|
||||
s.value1b(o.y1);
|
||||
}
|
||||
|
||||
struct Derived2 : virtual Base {
|
||||
uint8_t y2{};
|
||||
};
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, Derived2 &o) {
|
||||
s.ext(o, VirtualBaseClass<Base>{});
|
||||
s.value1b(o.y2);
|
||||
}
|
||||
|
||||
struct MultipleVirtualInheritance : Derived1, Derived2 {
|
||||
int8_t z{};
|
||||
|
||||
MultipleVirtualInheritance() = default;
|
||||
|
||||
MultipleVirtualInheritance(uint8_t x_, uint8_t y1_, uint8_t y2_, uint8_t z_) {
|
||||
x = x_;
|
||||
y1 = y1_;
|
||||
y2 = y2_;
|
||||
z = z_;
|
||||
}
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s) {
|
||||
s.ext(*this, BaseClass<Derived1>{});
|
||||
s.ext(*this, BaseClass<Derived2>{});
|
||||
s.value1b(z);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//this class has no relationships specified via PolymorphicBaseClass
|
||||
struct NoRelationshipSpecifiedDerived : Base {
|
||||
};
|
||||
|
||||
//these classes will be used to "cheat" a little bit when testing deserialization flows
|
||||
struct BaseClone {
|
||||
uint8_t x{};
|
||||
|
||||
virtual ~BaseClone() = default;
|
||||
};
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, BaseClone &o) {
|
||||
s.value1b(o.x);
|
||||
}
|
||||
|
||||
//define relationships between base class and derived classes for runtime polymorphism
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
template<>
|
||||
struct PolymorphicBaseClass<Base> : PolymorphicDerivedClasses<Derived1, Derived2> {
|
||||
};
|
||||
|
||||
// this is commented on purpose, to test scenario when base class is registered (Base)
|
||||
// but using instance of Derived1 which is not registered as base
|
||||
// template<>
|
||||
// struct PolymorphicBaseClass<Derived1> : PolymorphicDerivedClasses<MultipleVirtualInheritance> {
|
||||
// };
|
||||
|
||||
template<>
|
||||
struct PolymorphicBaseClass<Derived2> : PolymorphicDerivedClasses<MultipleVirtualInheritance> {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SerializeExtensionPointerPolymorphicTypes : public testing::Test {
|
||||
public:
|
||||
|
||||
TContext plctx{};
|
||||
SerContext sctx{};
|
||||
|
||||
typename SerContext::TSerializer &createSerializer() {
|
||||
auto &res = sctx.createSerializer(&plctx);
|
||||
std::get<2>(plctx).clear();
|
||||
//bind serializer with classes
|
||||
std::get<2>(plctx).registerBasesList(res, bitsery::ext::PolymorphicClassesList<Base>{});
|
||||
return res;
|
||||
}
|
||||
|
||||
typename SerContext::TDeserializer &createDeserializer() {
|
||||
auto &res = sctx.createDeserializer(&plctx);
|
||||
std::get<2>(plctx).clear();
|
||||
//bind deserializer with classes
|
||||
std::get<2>(plctx).registerBasesList(res, bitsery::ext::PolymorphicClassesList<Base>{});
|
||||
return res;
|
||||
}
|
||||
|
||||
bool isPointerContextValid() {
|
||||
return std::get<0>(plctx).isValid();
|
||||
}
|
||||
|
||||
virtual void TearDown() override {
|
||||
EXPECT_TRUE(isPointerContextValid());
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(SerializeExtensionPointerPolymorphicTypes, Data0Result0) {
|
||||
Base *baseData = nullptr;
|
||||
createSerializer().ext(baseData, PointerOwner{});
|
||||
Base *baseRes = nullptr;
|
||||
createDeserializer().ext(baseRes, PointerOwner{});
|
||||
|
||||
EXPECT_THAT(baseRes, ::testing::IsNull());
|
||||
EXPECT_THAT(baseData, ::testing::IsNull());
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerPolymorphicTypes, Data0Result1) {
|
||||
Base *baseData = nullptr;
|
||||
createSerializer().ext(baseData, PointerOwner{});
|
||||
|
||||
Base *baseRes = new Derived1{};
|
||||
createDeserializer().ext(baseRes, PointerOwner{});
|
||||
|
||||
EXPECT_THAT(baseRes, ::testing::IsNull());
|
||||
EXPECT_THAT(baseData, ::testing::IsNull());
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerPolymorphicTypes, Data1Result0) {
|
||||
Derived1 d1{};
|
||||
d1.x = 3;
|
||||
d1.y1 = 78;
|
||||
Base *baseData = &d1;
|
||||
createSerializer().ext(baseData, PointerOwner{});
|
||||
Base *baseRes = nullptr;
|
||||
createDeserializer().ext(baseRes, PointerOwner{});
|
||||
|
||||
auto *data = dynamic_cast<Derived1 *>(baseData);
|
||||
auto *res = dynamic_cast<Derived1 *>(baseRes);
|
||||
|
||||
EXPECT_THAT(baseRes, ::testing::NotNull());
|
||||
EXPECT_THAT(data, ::testing::NotNull());
|
||||
EXPECT_THAT(res, ::testing::NotNull());
|
||||
EXPECT_THAT(res->x, Eq(data->x));
|
||||
EXPECT_THAT(res->y1, Eq(data->y1));
|
||||
delete baseRes;
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerPolymorphicTypes, Data1Result1) {
|
||||
Derived1 d1{};
|
||||
d1.x = 3;
|
||||
d1.y1 = 78;
|
||||
Base *baseData = &d1;
|
||||
createSerializer().ext(baseData, PointerOwner{});
|
||||
Base *baseRes = &d1;
|
||||
createDeserializer().ext(baseRes, PointerOwner{});
|
||||
|
||||
auto *data = dynamic_cast<Derived1 *>(baseData);
|
||||
auto *res = dynamic_cast<Derived1 *>(baseRes);
|
||||
|
||||
EXPECT_THAT(baseRes, ::testing::NotNull());
|
||||
EXPECT_THAT(data, ::testing::NotNull());
|
||||
EXPECT_THAT(res, ::testing::NotNull());
|
||||
EXPECT_THAT(res->x, Eq(data->x));
|
||||
EXPECT_THAT(res->y1, Eq(data->y1));
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerPolymorphicTypes, ComplexTypeWithVirtualInheritanceData1Result0) {
|
||||
MultipleVirtualInheritance md1{};
|
||||
md1.x = 3;
|
||||
md1.y1 = 78;
|
||||
md1.y2 = 14;
|
||||
md1.z = -33;
|
||||
Base *baseData = &md1;
|
||||
createSerializer().ext(baseData, PointerOwner{});
|
||||
Base *baseRes = nullptr;
|
||||
createDeserializer().ext(baseRes, PointerOwner{});
|
||||
|
||||
auto *data = dynamic_cast<MultipleVirtualInheritance *>(baseData);
|
||||
auto *res = dynamic_cast<MultipleVirtualInheritance *>(baseRes);
|
||||
|
||||
EXPECT_THAT(baseRes, ::testing::NotNull());
|
||||
EXPECT_THAT(data, ::testing::NotNull());
|
||||
EXPECT_THAT(res, ::testing::NotNull());
|
||||
EXPECT_THAT(res->x, Eq(data->x));
|
||||
EXPECT_THAT(res->y1, Eq(data->y1));
|
||||
EXPECT_THAT(res->y2, Eq(data->y2));
|
||||
EXPECT_THAT(res->z, Eq(data->z));
|
||||
delete baseRes;
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerPolymorphicTypes, WhenResultIsDifferentTypeThenRecreate) {
|
||||
MultipleVirtualInheritance md1{};
|
||||
md1.x = 3;
|
||||
md1.y1 = 78;
|
||||
md1.y2 = 14;
|
||||
md1.z = -33;
|
||||
Base *baseData = &md1;
|
||||
createSerializer().ext(baseData, PointerOwner{});
|
||||
Base *baseRes = new Derived1{};
|
||||
EXPECT_THAT(dynamic_cast<MultipleVirtualInheritance *>(baseRes), ::testing::IsNull());
|
||||
createDeserializer().ext(baseRes, PointerOwner{});
|
||||
EXPECT_THAT(dynamic_cast<MultipleVirtualInheritance *>(baseRes), ::testing::NotNull());
|
||||
delete baseRes;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
TEST_F(SerializeExtensionPointerPolymorphicTypes,
|
||||
WhenSerializingDerivedTypeWithoutSpecifiedRelationshipsWithBaseThenAssert) {
|
||||
|
||||
NoRelationshipSpecifiedDerived md1;//this class has no relationships specified via PolymorphicBaseClass
|
||||
Base *baseData = &md1;
|
||||
EXPECT_DEATH(createSerializer().ext(baseData, PointerOwner{}), "");
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionPointerPolymorphicTypes,
|
||||
WhenDeserializingDerivedTypeNotRegisteredWithPolymorphicContextThenAssert) {
|
||||
|
||||
Derived1 d1{};
|
||||
Base *baseData = &d1;
|
||||
createSerializer().ext(baseData, PointerOwner{});
|
||||
|
||||
BaseClone *baseRes = nullptr; //this class is not registered
|
||||
EXPECT_DEATH(createDeserializer().ext(baseRes, PointerOwner{}), "");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
TEST_F(SerializeExtensionPointerPolymorphicTypes,
|
||||
CompileTimeTypeIsDerivedAndReachableFromBaseRegisteredWithPolymorphicContext) {
|
||||
|
||||
MultipleVirtualInheritance md;
|
||||
Derived2 *derivedData = &md;//this class is not registered via PolymorphicContext
|
||||
|
||||
createSerializer().ext(derivedData, PointerOwner{});
|
||||
Derived2 *derivedRes = new Derived2{};
|
||||
EXPECT_THAT(dynamic_cast<MultipleVirtualInheritance *>(derivedRes), ::testing::IsNull());
|
||||
createDeserializer().ext(derivedRes, PointerOwner{});
|
||||
EXPECT_THAT(dynamic_cast<MultipleVirtualInheritance *>(derivedRes), ::testing::NotNull());
|
||||
delete derivedRes;
|
||||
|
||||
}
|
||||
|
||||
|
||||
TEST_F(SerializeExtensionPointerPolymorphicTypes,
|
||||
WhenPolymorphicTypeNotFoundDuringDeserializionThenInvalidPointerError) {
|
||||
|
||||
Derived1 d1{};
|
||||
Base *baseData = &d1;
|
||||
createSerializer().ext(baseData, PointerOwner{});
|
||||
|
||||
BaseClone *baseRes = nullptr; //this class will be registered, but it doesn't have relationships specified via PolymorphicBaseClass
|
||||
auto &des = sctx.createDeserializer(&plctx);
|
||||
auto &pc = std::get<2>(plctx);
|
||||
pc.clear();
|
||||
pc.registerBasesList(des, bitsery::ext::PolymorphicClassesList<BaseClone>{});
|
||||
des.ext(baseRes, PointerOwner{});
|
||||
EXPECT_THAT(sctx.br->error(), Eq(bitsery::ReaderError::InvalidPointer));
|
||||
}
|
||||
@@ -20,14 +20,14 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
#include <bitsery/ext/std_map.h>
|
||||
#include <bitsery/ext/entropy.h>
|
||||
#include <unordered_map>
|
||||
#include <bitsery/traits/string.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
using StdMap = bitsery::ext::StdMap;
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
@@ -20,9 +20,10 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#include <bitsery/ext/std_queue.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/ext/std_queue.h>
|
||||
|
||||
using StdQueue = bitsery::ext::StdQueue;
|
||||
|
||||
@@ -56,7 +57,7 @@ TEST(SerializeExtensionStdQueue, QueueDefaultContainer) {
|
||||
t1.push(-4854);
|
||||
std::queue<int32_t> r1{};
|
||||
|
||||
SerializationContext ctx1;
|
||||
SerializationContext ctx1{};
|
||||
test(ctx1,t1, r1);
|
||||
EXPECT_THAT(t1, Eq(r1));
|
||||
}
|
||||
@@ -67,7 +68,7 @@ TEST(SerializeExtensionStdQueue, QueueVectorContainer) {
|
||||
t1.push(-4854);
|
||||
std::queue<int32_t, std::vector<int32_t>> r1{};
|
||||
|
||||
SerializationContext ctx1;
|
||||
SerializationContext ctx1{};
|
||||
test(ctx1,t1, r1);
|
||||
EXPECT_THAT(t1, Eq(r1));
|
||||
}
|
||||
@@ -78,7 +79,7 @@ TEST(SerializeExtensionStdQueue, PriorityQueueDefaultContainer) {
|
||||
t1.push(-4854);
|
||||
std::priority_queue<int32_t> r1{};
|
||||
|
||||
SerializationContext ctx1;
|
||||
SerializationContext ctx1{};
|
||||
test(ctx1,t1, r1);
|
||||
auto & ct1 = PriorityQueueCnt<int32_t, std::vector<int32_t>>::getContainer(t1);
|
||||
auto & cr1 = PriorityQueueCnt<int32_t, std::vector<int32_t>>::getContainer(r1);
|
||||
@@ -91,7 +92,7 @@ TEST(SerializeExtensionStdQueue, PriorityQueueDequeContainer) {
|
||||
t1.push(-44);
|
||||
std::priority_queue<int32_t, std::deque<int32_t>> r1{};
|
||||
|
||||
SerializationContext ctx1;
|
||||
SerializationContext ctx1{};
|
||||
test(ctx1,t1, r1);
|
||||
auto & ct1 = PriorityQueueCnt<int32_t, std::deque<int32_t>>::getContainer(t1);
|
||||
auto & cr1 = PriorityQueueCnt<int32_t, std::deque<int32_t>>::getContainer(r1);
|
||||
|
||||
@@ -20,12 +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/ext/std_set.h>
|
||||
#include <set>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
using StdSet = bitsery::ext::StdSet;
|
||||
|
||||
using testing::Eq;
|
||||
|
||||
672
tests/serialization_ext_std_smart_ptr.cpp
Normal file
672
tests/serialization_ext_std_smart_ptr.cpp
Normal file
@@ -0,0 +1,672 @@
|
||||
//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/ext/inheritance.h>
|
||||
#include <bitsery/ext/pointer.h>
|
||||
#include <bitsery/ext/std_smart_ptr.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
using bitsery::ext::BaseClass;
|
||||
using bitsery::ext::VirtualBaseClass;
|
||||
|
||||
using bitsery::ext::InheritanceContext;
|
||||
using bitsery::ext::PointerLinkingContext;
|
||||
using bitsery::ext::PolymorphicContext;
|
||||
using bitsery::ext::StandardRTTI;
|
||||
using bitsery::ext::PointerType;
|
||||
|
||||
using bitsery::ext::StdSmartPtr;
|
||||
using bitsery::ext::PointerObserver;
|
||||
|
||||
using testing::Eq;
|
||||
using testing::Ne;
|
||||
|
||||
struct Base {
|
||||
uint8_t x{};
|
||||
|
||||
virtual ~Base() = default;
|
||||
};
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, Base &o) {
|
||||
s.value1b(o.x);
|
||||
}
|
||||
|
||||
struct Derived : virtual Base {
|
||||
uint8_t y{};
|
||||
|
||||
Derived() = default;
|
||||
|
||||
Derived(uint8_t x_, uint8_t y_) {
|
||||
x = x_;
|
||||
y = y_;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, Derived &o) {
|
||||
s.ext(o, VirtualBaseClass<Base>{});
|
||||
s.value1b(o.y);
|
||||
}
|
||||
|
||||
struct MoreDerived : Derived {
|
||||
int8_t z{};
|
||||
|
||||
MoreDerived() = default;
|
||||
|
||||
MoreDerived(uint8_t x_, uint8_t y_, uint8_t z_) : Derived(x_, y_) {
|
||||
z = z_;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s, MoreDerived &o) {
|
||||
s.ext(o, BaseClass<Derived>{});
|
||||
s.value1b(o.z);
|
||||
}
|
||||
|
||||
//define relationships between base class and derived classes for runtime polymorphism
|
||||
|
||||
namespace bitsery {
|
||||
namespace ext {
|
||||
|
||||
template<>
|
||||
struct PolymorphicBaseClass<Base> : PolymorphicDerivedClasses<Derived> {
|
||||
};
|
||||
|
||||
template<>
|
||||
struct PolymorphicBaseClass<Derived> : PolymorphicDerivedClasses<MoreDerived> {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
class SerializeExtensionStdSmartPtrNonPolymorphicType : public testing::Test {
|
||||
public:
|
||||
template<typename U>
|
||||
using TPtr = typename T::template TData<U>;
|
||||
using TExt = typename T::TExt;
|
||||
|
||||
using TContext = std::tuple<PointerLinkingContext>;
|
||||
using SerContext = BasicSerializationContext<bitsery::DefaultConfig, TContext>;
|
||||
|
||||
//this is useful for PolymorphicContext to bind classes to serializer/deserializer
|
||||
using TSerializer = typename SerContext::TSerializer;
|
||||
using TDeserializer = typename SerContext::TDeserializer;
|
||||
|
||||
TContext plctx{};
|
||||
SerContext sctx{};
|
||||
|
||||
typename SerContext::TSerializer &createSerializer() {
|
||||
auto &res = sctx.createSerializer(&plctx);
|
||||
return res;
|
||||
}
|
||||
|
||||
typename SerContext::TDeserializer &createDeserializer() {
|
||||
auto &res = sctx.createDeserializer(&plctx);
|
||||
return res;
|
||||
}
|
||||
|
||||
bool isPointerContextValid() {
|
||||
return std::get<0>(plctx).isValid();
|
||||
}
|
||||
|
||||
virtual void TearDown() override {
|
||||
EXPECT_TRUE(isPointerContextValid());
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class SerializeExtensionStdSmartPtrPolymorphicType : public testing::Test {
|
||||
public:
|
||||
template<typename U>
|
||||
using TPtr = typename T::template TData<U>;
|
||||
using TExt = typename T::TExt;
|
||||
|
||||
using TContext = std::tuple<PointerLinkingContext, InheritanceContext, PolymorphicContext<StandardRTTI>>;
|
||||
using SerContext = BasicSerializationContext<bitsery::DefaultConfig, TContext>;
|
||||
|
||||
//this is useful for PolymorphicContext to bind classes to serializer/deserializer
|
||||
using TSerializer = typename SerContext::TSerializer;
|
||||
using TDeserializer = typename SerContext::TDeserializer;
|
||||
|
||||
TContext plctx{};
|
||||
SerContext sctx{};
|
||||
|
||||
typename SerContext::TSerializer &createSerializer() {
|
||||
auto &res = sctx.createSerializer(&plctx);
|
||||
std::get<2>(plctx).clear();
|
||||
//bind serializer with classes
|
||||
std::get<2>(plctx).registerBasesList(res, bitsery::ext::PolymorphicClassesList<Base>{});
|
||||
return res;
|
||||
}
|
||||
|
||||
typename SerContext::TDeserializer &createDeserializer() {
|
||||
auto &res = sctx.createDeserializer(&plctx);
|
||||
std::get<2>(plctx).clear();
|
||||
//bind deserializer with classes
|
||||
std::get<2>(plctx).registerBasesList(res, bitsery::ext::PolymorphicClassesList<Base>{});
|
||||
return res;
|
||||
}
|
||||
|
||||
bool isPointerContextValid() {
|
||||
return std::get<0>(plctx).isValid();
|
||||
}
|
||||
|
||||
virtual void TearDown() override {
|
||||
EXPECT_TRUE(isPointerContextValid());
|
||||
}
|
||||
};
|
||||
|
||||
struct UniquePtrTest {
|
||||
template<typename T>
|
||||
using TData = std::unique_ptr<T>;
|
||||
using TExt = StdSmartPtr;
|
||||
};
|
||||
|
||||
struct SharedPtrTest {
|
||||
template<typename T>
|
||||
using TData = std::shared_ptr<T>;
|
||||
using TExt = StdSmartPtr;
|
||||
};
|
||||
|
||||
using TestingWithNonPolymorphicTypes = ::testing::Types<
|
||||
UniquePtrTest,
|
||||
SharedPtrTest>;
|
||||
|
||||
TYPED_TEST_CASE(SerializeExtensionStdSmartPtrNonPolymorphicType, TestingWithNonPolymorphicTypes);
|
||||
|
||||
using TestingWithPolymorphicTypes = ::testing::Types<
|
||||
UniquePtrTest,
|
||||
SharedPtrTest>;
|
||||
|
||||
TYPED_TEST_CASE(SerializeExtensionStdSmartPtrPolymorphicType, TestingWithPolymorphicTypes);
|
||||
|
||||
TYPED_TEST(SerializeExtensionStdSmartPtrNonPolymorphicType, Data0Result0) {
|
||||
using Ptr = typename TestFixture::template TPtr<MyStruct1>;
|
||||
using Ext = typename TestFixture::TExt;
|
||||
|
||||
Ptr data{};
|
||||
this->createSerializer().ext(data, Ext{});
|
||||
Ptr res{};
|
||||
this->createDeserializer().ext(res, Ext{});
|
||||
|
||||
EXPECT_THAT(data.get(), ::testing::IsNull());
|
||||
EXPECT_THAT(res.get(), ::testing::IsNull());
|
||||
}
|
||||
|
||||
TYPED_TEST(SerializeExtensionStdSmartPtrNonPolymorphicType, Data0Result1) {
|
||||
using Ptr = typename TestFixture::template TPtr<MyStruct1>;
|
||||
using Ext = typename TestFixture::TExt;
|
||||
|
||||
Ptr data{};
|
||||
this->createSerializer().ext(data, Ext{});
|
||||
Ptr res{new MyStruct1{}};
|
||||
this->createDeserializer().ext(res, Ext{});
|
||||
|
||||
EXPECT_THAT(data.get(), ::testing::IsNull());
|
||||
EXPECT_THAT(res.get(), ::testing::IsNull());
|
||||
}
|
||||
|
||||
TYPED_TEST(SerializeExtensionStdSmartPtrNonPolymorphicType, Data1Result0) {
|
||||
using Ptr = typename TestFixture::template TPtr<MyStruct1>;
|
||||
using Ext = typename TestFixture::TExt;
|
||||
|
||||
Ptr data{new MyStruct1{3, 78}};
|
||||
this->createSerializer().ext(data, Ext{});
|
||||
Ptr res{};
|
||||
this->createDeserializer().ext(res, Ext{});
|
||||
|
||||
EXPECT_THAT(data.get(), ::testing::NotNull());
|
||||
EXPECT_THAT(res.get(), ::testing::NotNull());
|
||||
EXPECT_THAT(res->i1, Eq(data->i1));
|
||||
EXPECT_THAT(res->i2, Eq(data->i2));
|
||||
}
|
||||
|
||||
TYPED_TEST(SerializeExtensionStdSmartPtrNonPolymorphicType, Data1Result1) {
|
||||
using Ptr = typename TestFixture::template TPtr<MyStruct1>;
|
||||
using Ext = typename TestFixture::TExt;
|
||||
|
||||
Ptr data{new MyStruct1{3, 78}};
|
||||
this->createSerializer().ext(data, Ext{});
|
||||
Ptr res{new MyStruct1{}};
|
||||
this->createDeserializer().ext(res, Ext{});
|
||||
|
||||
EXPECT_THAT(data.get(), ::testing::NotNull());
|
||||
EXPECT_THAT(res.get(), ::testing::NotNull());
|
||||
EXPECT_THAT(res->i1, Eq(data->i1));
|
||||
EXPECT_THAT(res->i2, Eq(data->i2));
|
||||
}
|
||||
|
||||
TYPED_TEST(SerializeExtensionStdSmartPtrNonPolymorphicType, CanUseLambdaOverload) {
|
||||
using Ptr = typename TestFixture::template TPtr<MyStruct1>;
|
||||
using Ext = typename TestFixture::TExt;
|
||||
|
||||
Ptr data{new MyStruct1{3, 78}};
|
||||
auto &ser = this->createSerializer();
|
||||
ser.ext(data, Ext{}, [&ser](MyStruct1 &o) {
|
||||
//serialize only one field
|
||||
ser.value4b(o.i1);
|
||||
});
|
||||
Ptr res{new MyStruct1{97, 12}};
|
||||
auto &des = this->createDeserializer();
|
||||
des.ext(res, Ext{}, [&des](MyStruct1 &o) {
|
||||
des.value4b(o.i1);
|
||||
});
|
||||
|
||||
EXPECT_THAT(res->i1, Eq(data->i1));
|
||||
EXPECT_THAT(res->i2, Ne(data->i2));
|
||||
}
|
||||
|
||||
TYPED_TEST(SerializeExtensionStdSmartPtrNonPolymorphicType, CanUseValueOverload) {
|
||||
using Ptr = typename TestFixture::template TPtr<uint16_t>;
|
||||
using Ext = typename TestFixture::TExt;
|
||||
|
||||
Ptr data{new uint16_t{3}};
|
||||
this->createSerializer().ext2b(data, Ext{});
|
||||
Ptr res{};
|
||||
this->createDeserializer().ext2b(res, Ext{});
|
||||
EXPECT_THAT(*res, Eq(*data));
|
||||
}
|
||||
|
||||
TYPED_TEST(SerializeExtensionStdSmartPtrNonPolymorphicType, FirstPtrThenPointerObserver) {
|
||||
using Ptr = typename TestFixture::template TPtr<uint16_t>;
|
||||
using Ext = typename TestFixture::TExt;
|
||||
|
||||
Ptr data{new uint16_t{3}};
|
||||
uint16_t *dataObs = data.get();
|
||||
auto &ser = this->createSerializer();
|
||||
ser.ext2b(data, Ext{});
|
||||
ser.ext2b(dataObs, PointerObserver{});
|
||||
Ptr res{};
|
||||
uint16_t *resObs = nullptr;
|
||||
auto &des = this->createDeserializer();
|
||||
des.ext2b(res, Ext{});
|
||||
des.ext2b(resObs, PointerObserver{});
|
||||
|
||||
EXPECT_THAT(resObs, Eq(res.get()));
|
||||
}
|
||||
|
||||
TYPED_TEST(SerializeExtensionStdSmartPtrNonPolymorphicType, FirstPointerObserverThenPtr) {
|
||||
using Ptr = typename TestFixture::template TPtr<uint16_t>;
|
||||
using Ext = typename TestFixture::TExt;
|
||||
|
||||
Ptr data{new uint16_t{3}};
|
||||
uint16_t *dataObs = data.get();
|
||||
auto &ser = this->createSerializer();
|
||||
ser.ext2b(dataObs, PointerObserver{});
|
||||
ser.ext2b(data, Ext{});
|
||||
Ptr res{};
|
||||
uint16_t *resObs = nullptr;
|
||||
auto &des = this->createDeserializer();
|
||||
des.ext2b(resObs, PointerObserver{});
|
||||
des.ext2b(res, Ext{});
|
||||
EXPECT_THAT(resObs, Eq(res.get()));
|
||||
}
|
||||
|
||||
TYPED_TEST(SerializeExtensionStdSmartPtrPolymorphicType, Data0Result0) {
|
||||
using Ptr = typename TestFixture::template TPtr<Base>;
|
||||
using Ext = typename TestFixture::TExt;
|
||||
|
||||
Ptr baseData{};
|
||||
this->createSerializer().ext(baseData, Ext{});
|
||||
Ptr baseRes{};
|
||||
this->createDeserializer().ext(baseRes, Ext{});
|
||||
|
||||
EXPECT_THAT(baseRes.get(), ::testing::IsNull());
|
||||
EXPECT_THAT(baseData.get(), ::testing::IsNull());
|
||||
}
|
||||
|
||||
TYPED_TEST(SerializeExtensionStdSmartPtrPolymorphicType, Data0Result1) {
|
||||
using Ptr = typename TestFixture::template TPtr<Base>;
|
||||
using Ext = typename TestFixture::TExt;
|
||||
|
||||
Ptr baseData{};
|
||||
this->createSerializer().ext(baseData, Ext{});
|
||||
|
||||
Ptr baseRes{new Derived{}};
|
||||
this->createDeserializer().ext(baseRes, Ext{});
|
||||
|
||||
EXPECT_THAT(baseRes.get(), ::testing::IsNull());
|
||||
EXPECT_THAT(baseData.get(), ::testing::IsNull());
|
||||
}
|
||||
|
||||
TYPED_TEST(SerializeExtensionStdSmartPtrPolymorphicType, Data1Result0) {
|
||||
using Ptr = typename TestFixture::template TPtr<Base>;
|
||||
using Ext = typename TestFixture::TExt;
|
||||
|
||||
Ptr baseData{new Derived{3, 78}};
|
||||
this->createSerializer().ext(baseData, Ext{});
|
||||
Ptr baseRes{};
|
||||
this->createDeserializer().ext(baseRes, Ext{});
|
||||
|
||||
auto *data = dynamic_cast<Derived *>(baseData.get());
|
||||
auto *res = dynamic_cast<Derived *>(baseRes.get());
|
||||
|
||||
EXPECT_THAT(data, ::testing::NotNull());
|
||||
EXPECT_THAT(res, ::testing::NotNull());
|
||||
EXPECT_THAT(res->x, Eq(data->x));
|
||||
EXPECT_THAT(res->y, Eq(data->y));
|
||||
}
|
||||
|
||||
TYPED_TEST(SerializeExtensionStdSmartPtrPolymorphicType, DataAndResultWithDifferentRuntimeTypes) {
|
||||
using Ptr = typename TestFixture::template TPtr<Base>;
|
||||
using Ext = typename TestFixture::TExt;
|
||||
|
||||
Ptr baseData{new Derived{3, 78}};
|
||||
this->createSerializer().ext(baseData, Ext{});
|
||||
Ptr baseRes{new Base{}};
|
||||
this->createDeserializer().ext(baseRes, Ext{});
|
||||
|
||||
auto *data = dynamic_cast<Derived *>(baseData.get());
|
||||
auto *res = dynamic_cast<Derived *>(baseRes.get());
|
||||
|
||||
EXPECT_THAT(data, ::testing::NotNull());
|
||||
EXPECT_THAT(res, ::testing::NotNull());
|
||||
EXPECT_THAT(res->x, Eq(data->x));
|
||||
EXPECT_THAT(res->y, Eq(data->y));
|
||||
}
|
||||
|
||||
class SerializeExtensionStdSmartSharedPtr : public testing::Test {
|
||||
public:
|
||||
|
||||
using TContext = std::tuple<PointerLinkingContext, InheritanceContext, PolymorphicContext<StandardRTTI>>;
|
||||
using SerContext = BasicSerializationContext<bitsery::DefaultConfig, TContext>;
|
||||
|
||||
//this is useful for PolymorphicContext to bind classes to serializer/deserializer
|
||||
using TSerializer = typename SerContext::TSerializer;
|
||||
using TDeserializer = typename SerContext::TDeserializer;
|
||||
|
||||
TContext plctx{};
|
||||
SerContext sctx{};
|
||||
|
||||
typename SerContext::TSerializer &createSerializer() {
|
||||
auto &res = sctx.createSerializer(&plctx);
|
||||
std::get<2>(plctx).clear();
|
||||
//bind serializer with classes
|
||||
std::get<2>(plctx).registerBasesList(res, bitsery::ext::PolymorphicClassesList<Base>{});
|
||||
return res;
|
||||
}
|
||||
|
||||
typename SerContext::TDeserializer &createDeserializer() {
|
||||
auto &res = sctx.createDeserializer(&plctx);
|
||||
std::get<2>(plctx).clear();
|
||||
//bind deserializer with classes
|
||||
std::get<2>(plctx).registerBasesList(res, bitsery::ext::PolymorphicClassesList<Base>{});
|
||||
return res;
|
||||
}
|
||||
|
||||
size_t getBufferSize() const {
|
||||
return sctx.getBufferSize();
|
||||
}
|
||||
|
||||
bool isPointerContextValid() {
|
||||
return std::get<0>(plctx).isValid();
|
||||
}
|
||||
|
||||
void clearSharedState() {
|
||||
return std::get<0>(plctx).clearSharedState();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(SerializeExtensionStdSmartSharedPtr, SameSharedObjectIsSerializedOnce) {
|
||||
|
||||
std::shared_ptr<Base> baseData1{new Derived{3, 78}};
|
||||
std::shared_ptr<Base> baseData2{baseData1};
|
||||
auto &ser = createSerializer();
|
||||
ser.ext(baseData1, StdSmartPtr{});
|
||||
ser.ext(baseData1, StdSmartPtr{});
|
||||
auto &des = createDeserializer();
|
||||
|
||||
//1b linking context (for 1st time)
|
||||
//1b dynamic type info
|
||||
//2b Derived object
|
||||
//1b linking context (for 2nd time)
|
||||
EXPECT_THAT(getBufferSize(), Eq(5));
|
||||
EXPECT_TRUE(isPointerContextValid());
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionStdSmartSharedPtr, PointerLinkingContextCorrectlyClearSharedState) {
|
||||
|
||||
std::shared_ptr<Base> baseData1{new Derived{3, 78}};
|
||||
|
||||
auto &ser = createSerializer();
|
||||
ser.ext(baseData1, StdSmartPtr{});
|
||||
std::shared_ptr<Base> baseRes1{};
|
||||
auto &des = createDeserializer();
|
||||
des.ext(baseRes1, StdSmartPtr{});
|
||||
EXPECT_THAT(baseRes1.use_count(), Eq(2));
|
||||
clearSharedState();
|
||||
EXPECT_THAT(baseRes1.use_count(), Eq(1));
|
||||
EXPECT_TRUE(isPointerContextValid());
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionStdSmartSharedPtr, CorrectlyManagesSameSharedObject) {
|
||||
|
||||
std::shared_ptr<Base> baseData1{new Derived{3, 78}};
|
||||
std::shared_ptr<Base> baseData2{new Derived{55, 11}};
|
||||
std::shared_ptr<Base> baseData21{baseData2};
|
||||
auto &ser = createSerializer();
|
||||
ser.ext(baseData1, StdSmartPtr{});
|
||||
ser.ext(baseData2, StdSmartPtr{});
|
||||
ser.ext(baseData21, StdSmartPtr{});
|
||||
|
||||
std::shared_ptr<Base> baseRes1{};
|
||||
std::shared_ptr<Base> baseRes2{};
|
||||
std::shared_ptr<Base> baseRes21{};
|
||||
auto &des = createDeserializer();
|
||||
des.ext(baseRes1, StdSmartPtr{});
|
||||
des.ext(baseRes2, StdSmartPtr{});
|
||||
des.ext(baseRes21, StdSmartPtr{});
|
||||
|
||||
auto *data = dynamic_cast<Derived *>(baseRes1.get());
|
||||
EXPECT_THAT(data, ::testing::NotNull());
|
||||
|
||||
clearSharedState();
|
||||
|
||||
EXPECT_THAT(baseRes1.use_count(), Eq(1));
|
||||
EXPECT_THAT(baseRes2.use_count(), Eq(2));
|
||||
EXPECT_THAT(baseRes21.use_count(), Eq(2));
|
||||
baseRes2.reset();
|
||||
EXPECT_THAT(baseRes21.use_count(), Eq(1));
|
||||
EXPECT_TRUE(isPointerContextValid());
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionStdSmartSharedPtr, FirstSharedThenWeakPtr) {
|
||||
|
||||
std::shared_ptr<Base> baseData1{new Derived{3, 78}};
|
||||
std::weak_ptr<Base> baseData11{baseData1};
|
||||
std::weak_ptr<Base> baseData12{baseData11};
|
||||
auto &ser = createSerializer();
|
||||
ser.ext(baseData1, StdSmartPtr{});
|
||||
ser.ext(baseData11, StdSmartPtr{});
|
||||
ser.ext(baseData12, StdSmartPtr{});
|
||||
|
||||
std::shared_ptr<Base> baseRes1{};
|
||||
std::weak_ptr<Base> baseRes11{};
|
||||
std::weak_ptr<Base> baseRes12{};
|
||||
auto &des = createDeserializer();
|
||||
des.ext(baseRes1, StdSmartPtr{});
|
||||
des.ext(baseRes11, StdSmartPtr{});
|
||||
des.ext(baseRes12, StdSmartPtr{});
|
||||
|
||||
auto *data = dynamic_cast<Derived *>(baseRes1.get());
|
||||
EXPECT_THAT(data, ::testing::NotNull());
|
||||
|
||||
clearSharedState();
|
||||
|
||||
EXPECT_THAT(baseRes1.use_count(), Eq(1));
|
||||
EXPECT_THAT(baseRes11.use_count(), Eq(1));
|
||||
EXPECT_THAT(baseRes12.use_count(), Eq(1));
|
||||
baseRes1.reset();
|
||||
EXPECT_THAT(baseRes11.use_count(), Eq(0));
|
||||
EXPECT_TRUE(isPointerContextValid());
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionStdSmartSharedPtr, FirstWeakThenSharedPtr) {
|
||||
|
||||
std::shared_ptr<Base> baseData1{new Derived{3, 78}};
|
||||
std::weak_ptr<Base> baseData11{baseData1};
|
||||
std::weak_ptr<Base> baseData2{};
|
||||
auto &ser = createSerializer();
|
||||
ser.ext(baseData2, StdSmartPtr{});
|
||||
ser.ext(baseData11, StdSmartPtr{});
|
||||
ser.ext(baseData1, StdSmartPtr{});
|
||||
|
||||
std::shared_ptr<Base> baseRes1{};
|
||||
std::weak_ptr<Base> baseRes11{};
|
||||
std::weak_ptr<Base> baseRes2{};
|
||||
auto &des = createDeserializer();
|
||||
des.ext(baseRes2, StdSmartPtr{});
|
||||
des.ext(baseRes11, StdSmartPtr{});
|
||||
des.ext(baseRes1, StdSmartPtr{});
|
||||
|
||||
auto *data = dynamic_cast<Derived *>(baseRes1.get());
|
||||
EXPECT_THAT(data, ::testing::NotNull());
|
||||
|
||||
clearSharedState();
|
||||
|
||||
EXPECT_THAT(baseRes1.use_count(), Eq(1));
|
||||
EXPECT_THAT(baseRes2.use_count(), Eq(0));
|
||||
EXPECT_THAT(baseRes11.use_count(), Eq(1));
|
||||
baseRes1.reset();
|
||||
EXPECT_THAT(baseRes11.use_count(), Eq(0));
|
||||
EXPECT_TRUE(isPointerContextValid());
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionStdSmartSharedPtr, FewPtrsAreEmpty) {
|
||||
|
||||
std::shared_ptr<Base> baseData1{new Derived{3, 78}};
|
||||
std::shared_ptr<Base> baseData2{};
|
||||
std::weak_ptr<Base> baseData3{};
|
||||
std::weak_ptr<Base> baseData11{baseData1};
|
||||
auto &ser = createSerializer();
|
||||
ser.ext(baseData1, StdSmartPtr{});
|
||||
ser.ext(baseData2, StdSmartPtr{});
|
||||
ser.ext(baseData3, StdSmartPtr{});
|
||||
ser.ext(baseData11, StdSmartPtr{});
|
||||
|
||||
std::shared_ptr<Base> baseRes1{};
|
||||
std::shared_ptr<Base> baseRes2{new Derived{3, 78}};
|
||||
std::weak_ptr<Base> baseRes3{baseRes2};
|
||||
std::weak_ptr<Base> baseRes11{};
|
||||
auto &des = createDeserializer();
|
||||
des.ext(baseRes1, StdSmartPtr{});
|
||||
des.ext(baseRes2, StdSmartPtr{});
|
||||
des.ext(baseRes3, StdSmartPtr{});
|
||||
des.ext(baseRes11, StdSmartPtr{});
|
||||
|
||||
clearSharedState();
|
||||
|
||||
EXPECT_THAT(baseRes1.use_count(), Eq(1));
|
||||
EXPECT_THAT(baseRes2.use_count(), Eq(0));
|
||||
EXPECT_THAT(baseRes3.use_count(), Eq(0));
|
||||
EXPECT_THAT(baseRes11.use_count(), Eq(1));
|
||||
baseRes1.reset();
|
||||
EXPECT_THAT(baseRes11.use_count(), Eq(0));
|
||||
EXPECT_TRUE(isPointerContextValid());
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionStdSmartSharedPtr, WhenResultObjectExistsSameType) {
|
||||
|
||||
std::shared_ptr<Base> baseData1{new Derived{3, 78}};
|
||||
auto &ser = createSerializer();
|
||||
ser.ext(baseData1, StdSmartPtr{});
|
||||
|
||||
std::shared_ptr<Base> baseRes1{new Derived{0, 0}};
|
||||
auto &des = createDeserializer();
|
||||
des.ext(baseRes1, StdSmartPtr{});
|
||||
|
||||
clearSharedState();
|
||||
|
||||
EXPECT_THAT(baseRes1.use_count(), Eq(1));
|
||||
EXPECT_THAT(baseRes1->x, Eq(baseData1->x));
|
||||
EXPECT_TRUE(isPointerContextValid());
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionStdSmartSharedPtr, WhenResultObjectExistsDifferentType) {
|
||||
|
||||
std::shared_ptr<Base> baseData1{new Derived{3, 78}};
|
||||
auto &ser = createSerializer();
|
||||
ser.ext(baseData1, StdSmartPtr{});
|
||||
|
||||
std::shared_ptr<Base> baseRes1{new Base{}};
|
||||
auto &des = createDeserializer();
|
||||
des.ext(baseRes1, StdSmartPtr{});
|
||||
|
||||
clearSharedState();
|
||||
|
||||
EXPECT_THAT(baseRes1.use_count(), Eq(1));
|
||||
EXPECT_THAT(baseRes1->x, Eq(baseData1->x));
|
||||
EXPECT_THAT(dynamic_cast<Derived *>(baseRes1.get()), ::testing::NotNull());
|
||||
EXPECT_TRUE(isPointerContextValid());
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionStdSmartSharedPtr, WhenOnlyWeakPtrIsSerializedThenPointerCointextIsInvalid) {
|
||||
std::shared_ptr<Base> tmp{new Derived{3, 78}};
|
||||
std::weak_ptr<Base> baseData1{tmp};
|
||||
auto &ser = createSerializer();
|
||||
ser.ext(baseData1, StdSmartPtr{});
|
||||
|
||||
EXPECT_FALSE(isPointerContextValid());
|
||||
}
|
||||
|
||||
TEST_F(SerializeExtensionStdSmartSharedPtr, WhenOnlyWeakPtrIsDeserializedThenPointerCointextIsInvalid) {
|
||||
std::shared_ptr<Base> baseData1{new Derived{3, 78}};
|
||||
auto &ser = createSerializer();
|
||||
ser.ext(baseData1, StdSmartPtr{});
|
||||
|
||||
std::weak_ptr<Base> baseRes1{};
|
||||
auto &des = createDeserializer();
|
||||
des.ext(baseRes1, StdSmartPtr{});
|
||||
|
||||
EXPECT_FALSE(isPointerContextValid());
|
||||
|
||||
EXPECT_THAT(baseRes1.use_count(), Eq(1));
|
||||
clearSharedState();
|
||||
EXPECT_THAT(baseRes1.use_count(), Eq(0));
|
||||
|
||||
}
|
||||
|
||||
struct TestSharedFromThis : public std::enable_shared_from_this<TestSharedFromThis> {
|
||||
float x{};
|
||||
|
||||
template<typename S>
|
||||
void serialize(S &s) {
|
||||
s.value4b(x);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(SerializeExtensionStdSmartSharedPtr, EnableSharedFromThis) {
|
||||
std::shared_ptr<TestSharedFromThis> dataPtr(new TestSharedFromThis{});
|
||||
std::shared_ptr<TestSharedFromThis> resPtr{};
|
||||
createSerializer().ext(dataPtr, StdSmartPtr{});
|
||||
createDeserializer().ext(resPtr, StdSmartPtr{});
|
||||
clearSharedState();
|
||||
auto resPtr2 = resPtr->shared_from_this();
|
||||
EXPECT_THAT(resPtr->x, Eq(dataPtr->x));
|
||||
EXPECT_THAT(resPtr2.use_count(), Eq(2));
|
||||
}
|
||||
@@ -20,9 +20,10 @@
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#include <bitsery/ext/std_stack.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/ext/std_stack.h>
|
||||
|
||||
|
||||
using StdStack = bitsery::ext::StdStack;
|
||||
|
||||
|
||||
@@ -21,15 +21,16 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#include <bitsery/ext/value_range.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/ext/value_range.h>
|
||||
|
||||
using namespace testing;
|
||||
using bitsery::details::RangeSpec;
|
||||
using bitsery::ext::BitsConstraint;
|
||||
using bitsery::ext::ValueRange;
|
||||
|
||||
#if __cplusplus > 201402L
|
||||
#if __cplusplus > 201103L
|
||||
|
||||
TEST(SerializeExtensionValueRange, RequiredBitsIsConstexpr) {
|
||||
constexpr RangeSpec<int> r1{0, 31};
|
||||
|
||||
@@ -21,11 +21,12 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <bitsery/traits/array.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
|
||||
using testing::Eq;
|
||||
using testing::StrEq;
|
||||
using testing::ContainerEq;
|
||||
@@ -43,10 +44,10 @@ struct X {
|
||||
|
||||
struct Y {
|
||||
int y{};
|
||||
int carr[3];
|
||||
std::array<int, 3> arr;
|
||||
std::vector<X> vx;
|
||||
std::string s;
|
||||
int carr[3]{};
|
||||
std::array<int, 3> arr{};
|
||||
std::vector<X> vx{};
|
||||
std::string s{};
|
||||
};
|
||||
struct Z { X x{}; Y y{}; };
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ bool SerializeDeserializeContainerSize(SerializationContext& ctx, const size_t s
|
||||
}
|
||||
|
||||
TEST(SerializeSize, WhenLengthLessThan128Then1Byte) {
|
||||
SerializationContext ctx1;
|
||||
SerializationContext ctx1{};
|
||||
EXPECT_TRUE(SerializeDeserializeContainerSize(ctx1, 127));
|
||||
EXPECT_THAT(ctx1.getBufferSize(), Eq(1));
|
||||
SerializationContext ctx2;
|
||||
|
||||
@@ -38,8 +38,8 @@ struct MyStruct1 {
|
||||
|
||||
MyStruct1() : MyStruct1{0, 0} {}
|
||||
|
||||
int i1;
|
||||
int i2;
|
||||
int32_t i1;
|
||||
int32_t i2;
|
||||
|
||||
bool operator==(const MyStruct1 &rhs) const {
|
||||
return i1 == rhs.i1 && i2 == rhs.i2;
|
||||
@@ -97,30 +97,33 @@ using Writer = bitsery::AdapterWriter<OutputAdapter, bitsery::DefaultConfig>;
|
||||
using Reader = bitsery::AdapterReader<InputAdapter, bitsery::DefaultConfig>;
|
||||
|
||||
|
||||
template <typename Config = bitsery::DefaultConfig>
|
||||
template <typename Config, typename Context>
|
||||
class BasicSerializationContext {
|
||||
public:
|
||||
Buffer buf;
|
||||
using TWriter = bitsery::AdapterWriter<OutputAdapter, Config>;
|
||||
using TReader = bitsery::AdapterReader<InputAdapter, Config>;
|
||||
std::unique_ptr<bitsery::BasicSerializer<TWriter>> ser;
|
||||
std::unique_ptr<bitsery::BasicDeserializer<TReader>> des;
|
||||
TWriter* bw;
|
||||
TReader* br;
|
||||
using TSerializer = bitsery::BasicSerializer<TWriter, Context>;
|
||||
using TDeserializer = bitsery::BasicDeserializer<TReader, Context>;
|
||||
|
||||
bitsery::BasicSerializer<TWriter>& createSerializer() {
|
||||
Buffer buf{};
|
||||
std::unique_ptr<TSerializer> ser{};
|
||||
std::unique_ptr<bitsery::BasicDeserializer<TReader, Context>> des{};
|
||||
TWriter* bw{};
|
||||
TReader* br{};
|
||||
|
||||
TSerializer& createSerializer(Context* ctx = nullptr) {
|
||||
if (!ser) {
|
||||
ser = std::unique_ptr<bitsery::BasicSerializer<TWriter>>(new bitsery::BasicSerializer<TWriter>(OutputAdapter{buf}));
|
||||
ser = std::unique_ptr<TSerializer>(new TSerializer(OutputAdapter{buf}, ctx));
|
||||
bw = &bitsery::AdapterAccess::getWriter(*ser);
|
||||
}
|
||||
return *ser;
|
||||
};
|
||||
|
||||
bitsery::BasicDeserializer<bitsery::AdapterReader<InputAdapter, Config>>& createDeserializer() {
|
||||
TDeserializer & createDeserializer(Context* ctx = nullptr) {
|
||||
bw->flush();
|
||||
if (!des) {
|
||||
des = std::unique_ptr<bitsery::BasicDeserializer<TReader>>(
|
||||
new bitsery::BasicDeserializer<TReader>(InputAdapter{buf.begin(), bw->writtenBytesCount()}));
|
||||
des = std::unique_ptr<TDeserializer>(
|
||||
new TDeserializer(InputAdapter{buf.begin(), bw->writtenBytesCount()}, ctx));
|
||||
br = &bitsery::AdapterAccess::getReader(*des);
|
||||
}
|
||||
return *des;
|
||||
@@ -143,6 +146,6 @@ public:
|
||||
};
|
||||
|
||||
//helper type
|
||||
using SerializationContext = BasicSerializationContext<bitsery::DefaultConfig>;
|
||||
using SerializationContext = BasicSerializationContext<bitsery::DefaultConfig, void>;
|
||||
|
||||
#endif //BITSERY_SERIALIZER_TEST_UTILS_H
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
//SOFTWARE.
|
||||
|
||||
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/traits/string.h>
|
||||
|
||||
using namespace testing;
|
||||
|
||||
@@ -110,10 +110,12 @@ TEST(SerializeText, CArraySerializesTextLength) {
|
||||
EXPECT_THAT(r1, ContainerEq(t1));
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
TEST(SerializeText, WhenCArrayNotNullterminatedThenAssert) {
|
||||
SerializationContext ctx;
|
||||
char16_t t1[CARR_LENGTH]{u"some text"};
|
||||
//make last character not nullterminated
|
||||
t1[CARR_LENGTH-1] = 'x';
|
||||
EXPECT_DEATH(ctx.createSerializer().text<2>(t1), "");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user