29 Commits

Author SHA1 Message Date
Mindaugas Vinkelis
03f2c3c8b5 bugfix in enableBitPacking 2019-06-27 10:08:48 +03:00
Luli2020
aca3139600 Added serializer/deserializer "cereal like" interface
Added interface BasicSerializer &operator()(T &&head, TArgs &&... tail) and BasicDeserializer &operator()(T &&head, TArgs &&... tail), which are cereal like intefaces so such format of serialization functions were possible:

template<class Archive>
void serialize(Archive & ar, DataType& d){
    ar(d.data1, d.data2....);
}
2019-06-27 08:07:57 +03:00
Mindaugas Vinkelis
c1ae593fb4 travis-ci-update 2019-06-27 08:08:05 +03:00
Mindaugas Vinkelis
ddca8e4ad0 extensions for tuple, variant and chrono types 2019-03-12 14:54:04 +02:00
Mindaugas Vinkelis
1fe2b398fc Merge pull request #12 from ArekSredzki/fix-quickMeasureSize
Fix quickMeasureSize<T>() compilation.
2019-01-30 11:49:37 +02:00
Arek Sredzki
574ec69cca Fix quickMeasureSize<T>() compilation. 2019-01-30 01:44:34 -08:00
Mindaugas Vinkelis
8e94596a6f Merge pull request #11 from ArekSredzki/misc-documentation-improvements
Fix various grammatical and spelling mistakes within the docs
2019-01-18 14:37:59 +02:00
Arek Sredzki
fac2c8a7ce Fix various grammatical and spelling mistakes within the docs 2019-01-17 10:31:58 -08:00
Mindaugas
a6dad0885f visual studio variadic templates issues 2019-01-16 11:27:56 +02:00
Mindaugas
65f90637df input buffer adapter accepts const buffer 2019-01-10 20:48:03 +02:00
Mindaugas
b10f86da00 non default constructible types 2019-01-10 19:08:15 +02:00
Mindaugas
6c3e1aee43 removed anonymous namespace from PolymorphicBaseClass as it only works
on clang, and is not standard compliant
2019-01-08 16:00:02 +02:00
Mindaugas
e5f8d5742f Merge branch 'master' of https://github.com/fraillt/bitsery 2019-01-08 15:08:27 +02:00
Mindaugas
a2ecf8d7b0 polymorphism improvements and new CompactValue extension 2019-01-08 15:06:29 +02:00
Mindaugas Vinkelis
670130397b Merge pull request #6 from AJIOB/master
VS 2017.5.6 example project compilation fix
2018-09-17 09:47:36 +03:00
AJIOB
4a0b3cae98 VS 2017.5.6 example compilation fix 2018-09-15 08:37:56 +03:00
Mindaugas Vinkelis
b3b32ab393 Merge pull request #5 from YarikTH/patch-1
Update smart_pointers_with_polymorphism.cpp
2018-08-27 07:24:22 +03:00
Yaroslav
6ebdb9915b Update smart_pointers_with_polymorphism.cpp
Fix Color::operator == in smart_pointers_with_polymorphism example
2018-08-24 10:09:17 +03:00
Mindaugas
2e62bd08e3 cleanup 2018-08-23 14:57:48 +03:00
Mindaugas
54f69a5eea polymorphism and smart pointers 2018-08-23 14:44:58 +03:00
Mindaugas Vinkelis
275c4138ee polymorphism in progress 2018-08-20 13:10:10 +03:00
Mindaugas Vinkelis
1ca45aab79 updated travis script 2018-03-09 22:03:14 +02:00
fraillt
952635ff70 improved configuration to follow modern cmake practices 2018-03-09 15:59:28 +02:00
fraillt
507b5ae01d added polymorphism support for raw pointers 2018-02-28 16:06:03 +02:00
fraillt
f6d02aba38 added inheritance extension, and ability to have internal contexts within serializer/deserializer 2017-11-12 12:09:12 +02:00
fraillt
be9ccf08d9 added NotNull pointer to to pointer extensions 2017-10-30 09:01:08 +02:00
fraillt
5b1dc3bcfa raw pointers support, without polymorphism. 2017-10-27 08:14:01 +03:00
fraillt
bdc24eb3c2 compile warnings, and usage improvements for VisualStudio 2017-10-18 11:43:53 +03:00
fraillt
1acb9af188 added bitpacking and context usage examples 2017-10-16 07:52:00 +03:00
115 changed files with 7696 additions and 993 deletions

4
.gitignore vendored
View File

@@ -1,4 +1,6 @@
.idea/
.vs/
build/
cmake-build-debug/
cmake-build-*
CTestConfig.cmake
Testing/

View File

@@ -1,23 +1,70 @@
dist: xenial
language: cpp
compiler:
  - gcc
  # clang
# CXX_COMPILER and CC_COMPILER is defined, because travis will override CC and CXX environment variables
# We'll need to override them back "before_install"
matrix:
include:
- addons:
apt:
packages:
- g++-5
env:
- CXXSTD=11
- CXX_COMPILER=g++-5
- CC_COMPILER=gcc-5
- addons:
apt:
packages:
- clang-3.9
env:
- CXXSTD=11
- CXX_COMPILER=clang++-3.9
- CC_COMPILER=clang-3.9
- addons:
apt:
packages:
- g++-7
sources:
- ubuntu-toolchain-r-test
env:
- CXXSTD=17
- CXX_COMPILER=g++-7
- CC_COMPILER=gcc-7
- addons:
apt:
packages:
- libstdc++-7-dev
- clang-8
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-xenial-8
- sourceline: 'deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-8 main'
key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key'
env:
- CXXSTD=17
- CXX_COMPILER=clang++-8
- CC_COMPILER=clang-8
before_install:
# C++14
- sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
- sudo apt-get update -qq
- export CXX=$CXX_COMPILER
- export CC=$CC_COMPILER
install:
- sudo apt-get install -qq g++-5 lcov
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-5 90
- sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 90
install:
- wget https://github.com/google/googletest/archive/release-1.8.0.tar.gz
- tar xf release-1.8.0.tar.gz
- cd googletest-release-1.8.0
- cmake -DBUILD_SHARED_LIBS=ON .
- make
- sudo make install
- cd ..
before_script:
  - mkdir build
  - cd build
  - cmake ..
- mkdir build
- cd build
  - cmake -DBITSERY_BUILD_TESTS=ON -DCMAKE_CXX_STANDARD=$CXXSTD ..
script: make && make test
script:
- make
- cd tests
- ctest

View File

@@ -1,3 +1,184 @@
# [4.6.1](https://github.com/fraillt/bitsery/compare/v4.6.0...v4.6.1) (2019-06-27)
### Features
* flexible syntax now also supports `cereal` like serialization interface (thanks to [Luli2020](https://github.com/Luli2020))
### Bug fixes
* when using `enableBitPacking`, internal context (i.e. context<T>()) in serializer/deserializer was not copied to/from new bitpacking enabled instance.
# [4.6.0](https://github.com/fraillt/bitsery/compare/v4.5.1...v4.6.0) (2019-03-12)
### Features
* new extensions **StdTuple** and **StdVariant** for `std::tuple` and `std::variant`. These are the first extensions that requires C++17, or higher, standard enabled.
Although `std::tuple` is C++11 type, but from usage perspective it has exactly the same requirements as `std::variant` and relies heavily on having class template argument deduction guides to make it convenient to use.
You can easily use `std::tuple` without any extension at all, so the main motivation was to create convenient interface for **StdVariant** and use the same interface for **StdTuple** as well.
* instead of providing custom lambda to overload each type in tuple or variant, there was added several helper callable objects.
**OverloadValue** wrapper around `s.value<N>(o)`, **OverloadExtValue** wrapper around `s.ext<N>(o, Ext{})` and **OverloadExtObject** wrapper around `s.ext(o, Ext{})`.
* new extensions **StdDuration** and **StdTimePoint** for `std::chrono::duration` and `std::chrono::time_point`.
### Improvements
tests now uses `gtest_discover_tests` function, to automatically discover tests, which requires CMake 3.10.
# [4.5.1](https://github.com/fraillt/bitsery/compare/v4.5.0...v4.5.1) (2019-01-16)
### Improvements
* template specializations, where possible, was changed to avoid using variadics, some Visual Studio compilers has [issues](https://developercommunity.visualstudio.com/content/problem/3437/error-with-c11-variadics.html) with variadic templates.
* reduced compile warnings for VisualStudio:
* added explicit casts
* renamed `struct` to `class` where class is used as friend. e.g. `friend class bitsery::Access`, because it is more conventional usage.
# [4.5.0](https://github.com/fraillt/bitsery/compare/v4.4.0...v4.5.0) (2019-01-10)
### Features
* ability to create non default constructible objects, by defining private default constructor and making `friend class bitsery::Access;` to access it.
It is not necessary to enforce class invariant immediately, because internal object representation will be overriden anyway.
### Improvements
* `StdSmartPtr` supports `std::unique_ptr` with custom deleter.
* `*InputBufferAdapter`(all) can also accept const buffer;
### Bug fixes
* fixed deserialization in `bitsery/ext/std_map{set}` when target container is not empty.
* added missing template parameters for specializations on `std` containers in multiple files in `bitsery/ext/*`.
# [4.4.0](https://github.com/fraillt/bitsery/compare/v4.3.0...v4.4.0) (2019-01-08)
### Features
* new extensions **CompactValue** and **CompactValueAsObject**, stores integral values in less space if possible. This is useful when you're working with mostly small values, that in rare cases can be large.
E.g. `int64_t money = 8000;` will only use 2 bytes, instead of 8. **CompactValueAsObject** allows to use `ext()` overload, without specifying size of underlying type and sets BUFFER_OVERFLOW error if value doesn't fit in underlying type during deserialization.
### Improvements
* improved **PolymorphicContext**, allows to extend already registered hierarchy in one translation unit, using different type other than `PolymorphicBaseClass` to avoid symbol collision between translation units or libraries.
`registerBasesList` was modified, so that it could accept user defined type (instead of `PolymorphicBaseClass`) that is used to declare hierarchy, by default it is `PolymorphicBaseClass`.
This introduced breaking change, for those who used this syntax (`registerBasesList<MySerializer, Shape>({})`) during registration.
It is encouraged to define helper type, that could be used for registering hierarchy for serialization and deserialization [example](examples/smart_pointers_with_polymorphism.cpp).
`This is only relevant then you want to use **PolymorphicContext** between different translation units or libraries`.
```cpp
//libA
namespace bitsery {
namespace ext {
template<>
struct PolymorphicBaseClass<Shape> : PolymorphicDerivedClasses<Circle, Rectangle> {};
}
}
using MyPolymorphicClassesForRegistering = bitsery::ext::PolymorphicClassesList<Shape>;
...
ctx.registerBasesList<MySerializer>(MyPolymorphicClassesForRegistering{}).
//otherLib
struct MySquare: Shape {...}
//now it must define different type (exactly the same as PolymorphicBaseClass) to declare hierarchy
template<typename TBase>
struct MyHierarchy {
using Childs = PolymorphicClassesList<>;
};
template <>
struct MyHierarchy<Shape>: bitsery::ext::bitsery::ext::PolymorphicClassesList<MySquare> {};
...
//notice that we pass MyHierarchy as second argument
ctx.registerBasesList<MySerializer, MyHierarchy>(MyPolymorphicClassesForRegistering{}).
```
* **PolymorphicContext** also get optional method `registerSingleBaseBranch`, that allows manually register hierarchies, this might be more convenient when using you need to register in different translation units (or libraries), but it is error-prone.
# [4.3.0](https://github.com/fraillt/bitsery/compare/v4.2.1...v4.3.0) (2018-08-23)
### Features
* added runtime polymorphism support for pointer like types (raw and smart pointers).
In order to enable polymorphism new **PolymorphicContext** was created. It provides capability to register classes with serializer/deserializer.
* runtime polymorphism can be customized, by replacing **StandardRTTI** from <bitsery/ext/utils/rtti_utils.h> header.
* added smart pointers support for std::unique_ptr, std::shared_ptr and std::weak_ptr via **StdSmartPtr** extension.
* new **UnsafeInputBufferAdapter** doesn't check for buffer size on deserialization, on some compilers can improve deserialization performance up to ~40%.
### Improvements
* creatly improved interface for extending/implementing support for pointer like types. Now all pointer like types extends from **PointerObjectExtensionBase** and implements/configures required details.
* reimplemented **PointerOwner**, **PointerObserver**, **ReferencedByPointer**.
* reimplemented **PointerLinkingContext** to properly support shared objects and runtime polymorphism, pointer ownership for shared objects now has two states: SharedOwner e.g. std::shared_ptr and SharedObserver std::weak_ptr.
### Other notes
There is one *minor?* issue/limitation for pointer like types that uses virtual inheritance. When several pointers points to same object through different static type. it will not work correctly e.g.:
```cpp
struct Derived: virtual Base {...};
struct MyData {
std::shared_ptr<Derived> sptr;
std::weak_ptr<Base> wptddr;
}
```
In this example wptr and sptr have different static type, and *Derived* is virtually inherited from *Base*, so I get different pointer address for different types.
# [4.2.1](https://github.com/fraillt/bitsery/compare/v4.2.0...v4.2.1) (2018-03-09)
### Improvements
* changed CMake structure, to follow **Modern CMake** principles.
* bitsery now has *install* target and **find_package(Bitsery)** exports **Bitsery::bitsery** target.
* *GTest* no longer downloads as external application, but tries to find via *find_package*.
* removed *ext* folder, and instead added *scripts* folder that contains few helper scripts for development, currently tested on Ubuntu.
* fixed/added few tests cases.
### Other notes
* some work was done on polymorphism support, but current solution, although working, but has many design and performance issues, and interfaces for extensibility might change drastically.
# [4.2.0](https://github.com/fraillt/bitsery/compare/v4.1.0...v4.2.0) (2017-11-12)
### Features
* 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.
@@ -34,7 +215,7 @@ Be careful when using deserializing untrusted data and make sure to enforce fund
### Features
* refactored interface, now works with C++11 compiler.
* new new extension **Growable**, that allows to have forward/backward compatability within this functions serialization flow. It only allows to append new data at the end of to existing flow without breaking old consumers.
* new extension **Growable**, that allows to have forward/backward compatability within this functions serialization flow. It only allows to append new data at the end of to existing flow without breaking old consumers.
* old consumer: correctly read old interfce and ignore new data.
* new consumer: get defaults (zero values) for new fields, when reading old data.
* added new extension for associative *map* containers **ContainerMap**.

View File

@@ -1,12 +1,57 @@
cmake_minimum_required(VERSION 3.2)
cmake_minimum_required(VERSION 3.1)
project(bitsery
LANGUAGES CXX
VERSION 4.6.1)
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()

View File

@@ -8,20 +8,34 @@ you contribute:
1. Fork the repository.
2. Create new branch based on the *master* branch (`git checkout -b your_branch master`). If your contribution is a bug fix, you should name your branch `bugfix/xxx`; for a feature, it should be `feature/xxx`. Otherwise, just use your good judgment. Consistent naming of branches is appreciated since it makes the output of `git branch` easier to understand with a single glance.
3. Do your modifications on that branch. Except for special cases, your contribution should include proper unit tests and documentation.
4. Make sure your modifications did not break anything by building, running tests and checking code coverage (test coverage should not be less than 100%):
4. Make sure your modifications did not break anything by building, running tests:
```shell
mkdir build
cd build
cmake ..
cmake -DBITSERY_BUILD_TESTS=ON ..
make
ctest
make tests_coverage
x-www-browser ./coverage/index.html
(cd tests; ctest)
```
or run CTest scripts and view code coverage (scripts tested on ubuntu, requires lcov for coverage):
```shell
cd scripts
ctest -S build.bitsery.cmake
./show_coverage.sh build
```
5. Commit your changes, and push to your fork (`git push origin your_branch`). Commit message should be one line short description. When applicable, please squash adjacent *wip* commits into a single *logical* commit.
6. Open a pull request against Bitsery *master* branch. Currently ongoing development is on *master*. At some point an integration branch will be set-up, and pull-requests should target that, but for now its all against master. You may see feature branches come and go, too.
If you're working with visual studio, there is how to build and run all tests from command line
```shell
mkdir build
cd build
cmake -DBITSERY_BUILD_TESTS=ON -DGTEST_ROOT="<PATH to GTEST>" -DCMAKE_CXX_FLAGS_RELEASE=/MT ..
cmake --build . --config Release
(cd tests && ctest -C Release && cd ..)
```
/MT option might be optional, depending on how gtest was built.
## Style guide
Just use your own judgment and stick to the style of the surrounding code.
Just use your own judgment and stick to the style of the surrounding code.

View File

@@ -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

View File

@@ -14,15 +14,16 @@ 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.
* Configurable endianess support.
* Allows flexible or/and verbose syntax for better serialization control.
* Configurable endianness support.
* No macros.
## Why to use bitsery
@@ -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)*
@@ -95,6 +96,8 @@ This documentation comprises these parts:
Works with C++11 compiler, no additional dependencies, include `<bitsery/bitsery.h>` and you're done.
> some **bitsery** extensions might require higher C++ standard (e.g. `StdVariant`)
## Platforms
This library was tested on
@@ -104,4 +107,4 @@ This library was tested on
## License
**bitsery** is licensed under the [MIT license](LICENSE).
**bitsery** is licensed under the [MIT license](LICENSE).

View File

@@ -9,27 +9,46 @@ 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`
* `operator()` (4.6.1) (when flexible syntax is enabled)
* `align` (1.0.0)
* `archive` (4.0.0) (when flexible syntax is enabled)
* `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)
* `CompactValue` (4.4.0)
* `CompactValueAsObject` (4.4.0)
* `Entropy` (3.0.0)
* `Growable` (3.0.0)
* `PointerOwner` (4.1.0)
* `PointerObserver` (4.1.0)
* `ReferencedByPointer` (4.1.0)
* `StdDuration` (4.6.0)
* `StdMap` (3.0.0)
* `StdOptional` (2.0.0)
* `StdQueue` (4.0.0)
* `StdSet` (4.0.0)
* `StdSmartPrt` (4.3.0)
* `StdStack` (4.0.0)
* `StdTimePoint` (4.6.0)
* `StdTuple` (4.6.0) (requires c++17)
* `StdVariant` (4.6.0) (requires c++17)
* `ValueRange` (3.0.0)
* `VirtualBaseClass` (4.2.0)
AdapterWriter/Reader functions:
* `writeBits/readBits`
@@ -52,14 +71,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)

View File

@@ -33,17 +33,17 @@ Now let's review features in more detail.
* **Cross-platform compatible.** if same code compiles on Android, PS3 console, and your PC either x64 or x86 architecture, you are 100% sure it works.
To achieve this, bitsery specifically defines size of underlying data, hence syntax is *value\<2\>* (alias function *value2b*) instead or *value*, or *container2b* for element type of 16bits, eg int16_t.
Bitsery also applies endianess transformation if nessesarry.
* **Flexible syntax.** if you don't like like writing code with explicitly specifying underlying type size, like *container2b* or *value8b* you can use flexible syntax.
Bitsery also applies endianness transformation if necessary.
* **Flexible syntax.** If you don't like like writing code with explicitly specifying underlying type size, like *container2b* or *value8b*, you can use flexible syntax.
Just include <bitsery/flexible.h> and can write like in [cereal](http://uscilab.github.io/cereal/).
But do it on your own risk, and static assert using *assertFundamentalTypeSizes* function if you're planing to use it accross multiple platforms.
But do it on your own risk, and static assert using *assertFundamentalTypeSizes* function if you're planing to use it across multiple platforms.
* **Optimized for speed and space.** library itself doesn't do any allocations (except if you use backward/forward compatibility) so data writing/reading is fast as memcpy to/from your buffer.
It also doesn't serialize any type information, all information needed is writen in your code!
It also doesn't serialize any type information, all information needed is written in your code!
* **No code generation required: no IDL or metadata** since it doesn't support any other formats except binary, it doesn't need any metadata.
* **Runtime error checking on deserialization** library designed to be save with untrusted network data, that's why all overloads that work on containers has *maxSize* value, unless container is static size like *std::array*, this way bitsery ensures that no malicious data crash you.
* **Supports forward/backward compatibility for your types** library has optional forward/backward compatibility for types implemented in *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.
@@ -65,7 +65,5 @@ Bitsery allows to use bit-level operations and has two extensions that use them:
You want to support your custom container, its fine there is *ContainerTraits* for this, only few methods required to implement.
To use same container for buffer writing/reading add specialization to *BufferAdapterTraits*.
You want to customize serialization flow - use extensions, only two methods to define, and *ExtensionTraits* to further customize usage.
* **Configurable endianess support.** default is *Little Endian*, but if your primary target is PowerPC architecture, eg. PlayStation3, just change your configuration to be *Big Endian*.
* **Configurable endianness support.** default is *Little Endian*, but if your primary target is PowerPC architecture, eg. PlayStation3, just change your configuration to be *Big Endian*.
* **No macros.** Not so much to say, if you are like me, then it's a feature :)
*project for performance benchmark will be added to separate github project, i'll give you a link to it when its done.*

View File

@@ -2,11 +2,11 @@
This is a quick guide to get **bitsery** up and running in a matter of minutes.
The only prerequisite for running bitsery is a modern C++11 compliant compiler, such as GCC 4.9.4, clang 3.4, MSVC 2015, or newer.
Older versions might work, but it is not tested.
Older versions might work, but they have not been tested.
## Get bitsery
bitsery can be directly included in your project or installed anywhere you can access header files.
**bitsery** can be directly included in your project or installed anywhere you can access header files.
Grab the latest version, and include directory `bitsery_base_dir/include/` to your project.
There's nothing to build or make - **bitsery** is header only.
@@ -27,11 +27,14 @@ using InputAdapter = InputBufferAdapter<Buffer>;
```
**bitsery** is very lightweight, so we need to explicitly include what we need.
* `<bitsery/bitsery.h>` is a core header, that includes our Serializer and Deserializer
* `<bitsery/adapter/buffer.h>` in order to write/read data we need specific adapter, depending on what underlying buffer will be. In this example we'll be using std::vector as our buffer, so we include buffer adapter.
* <bitsery/traits/...> traits tells library how efficiently serialize particular container.
create alias types for *InputAdapter* and *OutputAdapter* using our vector as buffer.
Include | Description
--|--
`<bitsery/bitsery.h>` | This is a core header, that includes our Serializer and Deserializer.
`<bitsery/adapter/buffer.h>` | In order to write/read data, we need a specific adapter, depending on what underlying buffer will be. In this example, we'll be using `std::vector` as our buffer, so we include the buffer adapter.
`<bitsery/traits/...>` | Traits tell the library how to efficiently serialize a particular container. Many common STL containers are supported out of the box.
Create alias types for *InputAdapter* and *OutputAdapter* using our vector as buffer.
## Add serialization method for your type
@@ -124,4 +127,4 @@ int main() {
}
```
**currently documentation and tutorial is progress, but for more usage examples see examples folder**
**currently documentation and tutorial is progress, but for more usage examples see examples folder**

View File

@@ -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()

View File

@@ -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
View 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);
}

View File

@@ -0,0 +1,115 @@
#include <bitsery/bitsery.h>
#include <bitsery/adapter/buffer.h>
#include <bitsery/traits/vector.h>
// include extensions to work with tuples and variants
// these extesions only work with C++17
#if __cplusplus > 201402L
#include <bitsery/ext/std_tuple.h>
#include <bitsery/ext/std_variant.h>
// let's include this extension to make it more interesting :)
#include <bitsery/ext/compact_value.h>
struct MyStruct {
std::vector<int32_t> v{};
float f{};
bool operator==(const MyStruct& rhs) const {
return v == rhs.v && f == rhs.f;
}
};
template<typename S>
void serialize(S& s, MyStruct& o) {
s.container4b(o.v, 1000);
s.value4b(o.f);
}
// this will be the type that we want to serialize/deserialize
using MyTuple = std::tuple<float, MyStruct>;
using MyVariant = std::variant<int64_t, MyTuple, MyStruct>;
// for convenience
using namespace bitsery;
// define default serialize function for MyVariant, so that we could use quickSerialization/Deserialization functions
template<typename S>
void serialize(S& s, MyVariant& o) {
// in order to serialize a variant, it needs to know how to do it for all types
// we can do this simply by providing any callable object, that accepts serializer and type as arguments
s.ext(o, ext::StdVariant{
// specify how to serialize tuple by creating a lambda
[](S& s, MyTuple& o) {
// StdTuple is used exactly the same as StdVariant
s.ext(o, ext::StdTuple{
// this is convenient callable object to specify integral value size
// it is different equivalent to lambda [](auto& s, float&o) { s.value4b(o);}
ext::OverloadValue<float, 4>{},
// it is not required to provide MyStruct overload, because it we have defined 'serialize' function for it
});
},
// this might also be useful if you want to overload using extension
ext::OverloadExtValue<int64_t, 8, ext::CompactValue>{},
// you can even go further and instead of writing lambda for MyTuple you can as well compose the same functionality
// with OverloadExtObject, like this:
// (comment out MyTuple lambda, and uncomment this)
// ext::OverloadExtObject<MyTuple, ext::StdTuple<ext::OverloadValue<float, 4>>>{},
// we can also override default 'serialize' function by creating an overloading for that type
[](S& s, MyStruct& o) {
s.value4b(o.f);
s.container(o.v, 1000, [&s](int32_t& v) {
s.ext4b(v, ext::CompactValue{});
});
},
// NOTE.
// it is possible to provide "auto" as type parameter
// this will allow you to override all default 'serialize' functions
// but in this case it will not be called, because we have explicitly provided overloads for all variant types
// also note, that first parameter (serializer) is also "auto", this is required, so that it would be least specialized case
// otherwise it will not compile if you any ext::Overload* helper defined, because it will have ambiguous definitions
// (ext::OverLoad* defines (templated_type& s, concrete_type& o) and lambda would be (concrete_type& s, templated_type& o))
[](auto& , auto& ) {
assert(false);
}
});
}
//some helper types
using Buffer = std::vector<uint8_t>;
using OutputAdapter = OutputBufferAdapter<Buffer>;
using InputAdapter = InputBufferAdapter<Buffer>;
int main() {
//set some random data
MyVariant data{MyTuple{-7549, {{-451, 2, 968, 75, 4, 156, 49}, 874.4f}}};
// MyVariant data{MyStruct{{-451, 2, 968, 75, 4, 156, 49}, 874.4f}};
MyVariant res{};
//create buffer to store data
Buffer buffer;
//use quick serialization function,
//it will use default configuration to setup all the nesessary steps
//and serialize data to container
auto writtenSize = quickSerialization<OutputAdapter>(buffer, data);
//same as serialization, but returns deserialization state as a pair
//first = error code, second = is buffer was successfully read from begin to the end.
auto state = quickDeserialization<InputAdapter>({buffer.begin(), writtenSize}, res);
assert(state.first == ReaderError::NoError && state.second);
assert(data == res);
}
#else
#if defined(_MSC_VER)
#pragma message("example works only on c++17")
#else
#warning "example works only on c++17"
#endif
int main() {
return 0;
}
#endif

109
examples/context_usage.cpp Normal file
View 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());
}

View File

@@ -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);

View File

@@ -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);
};
}
};

View File

@@ -19,11 +19,12 @@ struct MyStruct {
void serialize(S& s) {
//now we can use flexible syntax with
s.archive(i, e, fs);
};
// flexible syntax also supports `cereal` like serialization interface by calling operator()
// s(i, e, fs);
}
};
using namespace bitsery;
//some helper types

View File

@@ -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
View File

@@ -0,0 +1,117 @@
//
//this example covers all the corner cases that can happen using inheritance
//in reality virtual inherintance is usually avoided, so your code would look much simpler.
//
#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
}

View File

@@ -0,0 +1,65 @@
//
// example of how to deserialize non default constructible objects
//
#include <bitsery/bitsery.h>
#include <bitsery/adapter/buffer.h>
#include <bitsery/traits/vector.h>
class MyData {
//define your private data
float _x{0};
float _y{0};
//make bitsery:Access friend
friend class bitsery::Access;
//create default constructor, don't worry about class invariant, it will be restored in deserialization
MyData() = default;
//define serialize function
template <typename S>
void serialize(S& s) {
s.value4b(_x);
s.value4b(_y);
}
public:
//define non default public constructor
MyData(float x, float y):_x{x}, _y{y} {}
//this is for convenience
bool operator ==(const MyData&rhs) const {
return _x == rhs._x && _y == rhs._y;
}
};
using namespace bitsery;
//some helper types
using Buffer = std::vector<uint8_t>;
using OutputAdapter = OutputBufferAdapter<Buffer>;
using InputAdapter = InputBufferAdapter<Buffer>;
int main() {
//initialize our data
std::vector<MyData> data{};
data.emplace_back(145.4f, 84.48f);
std::vector<MyData> res{};
//we cant use quick (de)serialization helper methods, because we ant to serialize container directly
//create buffer
Buffer buffer{};
//create and serialize container, and get written bytes count
Serializer<OutputAdapter> ser{OutputAdapter{buffer}};
ser.container(data, 10);
auto writtenBytes = AdapterAccess::getWriter(ser).writtenBytesCount();
//create and deserialize container
Deserializer<InputAdapter> des{InputAdapter{buffer.begin(), writtenBytes}};
des.container(res, 10);
//check if everything went ok
auto& reader = AdapterAccess::getReader(des);
assert(reader.error() == ReaderError::NoError && reader.isCompletedSuccessfully());
assert(res == data);
}

160
examples/raw_pointers.cpp Normal file
View 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;
}

View File

@@ -0,0 +1,273 @@
//
// Created by fraillt on 18.4.26.
//
#include <cassert>
#include <memory>
#include <bitsery/bitsery.h>
#include <bitsery/traits/vector.h>
#include <bitsery/adapter/buffer.h>
#include <bitsery/ext/pointer.h>
#include <bitsery/ext/inheritance.h>
#include <bitsery/ext/std_smart_ptr.h>
//in order to work with polymorphic types, we need to describe few steps:
// 1) describe relationships between base and derived types
// this will allow to know what are possible types reachable from base class
// 2) bind serializer to base class
// this will allow to iterate through all types, and add serialization functions,
// without this step compiler would simply remove functions that are not bound at compile-time even it we use type at runtime.
using bitsery::ext::BaseClass;
using bitsery::ext::PointerObserver;
using bitsery::ext::StdSmartPtr;
//define our data structures
struct Color {
float r{}, g{}, b{};
bool operator == (const Color& o) const {
return std::tie(r, g, b) ==
std::tie(o.r, o.g, o.b);
}
};
struct Shape {
Color clr{};
virtual ~Shape() = 0;
};
Shape::~Shape() = default;
struct Circle : Shape {
int32_t radius{};
bool operator == (const Circle& o) const {
return std::tie(radius, clr) ==
std::tie(o.radius, o.clr);
}
};
struct Rectangle : Shape {
int32_t width{};
int32_t height{};
bool operator == (const Rectangle& o) const {
return std::tie(width, height, clr) ==
std::tie(o.width, o.height, o.clr);
}
};
struct RoundedRectangle : Rectangle {
int32_t radius{};
bool operator == (const RoundedRectangle& o) const {
return std::tie(radius, static_cast<const Rectangle&>(*this)) ==
std::tie(o.radius, static_cast<const Rectangle&>(o));
}
};
//define serialization functions
template<typename S>
void serialize(S &s, Color &o) {
//in real world scenario, it might be possible to serialize this using ValueRange, to map values in smaller space
//but for the sake of this example keep it simple
s.value4b(o.r);
s.value4b(o.g);
s.value4b(o.b);
}
template<typename S>
void serialize(S &s, Shape &o) {
s.object(o.clr);
}
template<typename S>
void serialize(S &s, Circle &o) {
s.ext(o, bitsery::ext::BaseClass<Shape>{});
s.value4b(o.radius);
}
template<typename S>
void serialize(S &s, Rectangle &o) {
s.ext(o, bitsery::ext::BaseClass<Shape>{});
s.value4b(o.width);
s.value4b(o.height);
}
template<typename S>
void serialize(S &s, RoundedRectangle &o) {
s.ext(o, bitsery::ext::BaseClass<Rectangle>{});
s.value4b(o.radius);
}
//define our test structure
struct SomeShapes {
std::vector<std::shared_ptr<Shape>> sharedList;
std::unique_ptr<Shape> uniquePtr;
//weak ptr and refPtr will point to sharedList
std::weak_ptr<Shape> weakPtr;
Shape* refPtr;
};
//creates object, and populates some data
SomeShapes createData() {
SomeShapes data{};
{
auto tmp = new RoundedRectangle{};
tmp->height = 151572;
tmp->width = 488795;
tmp->radius = 898;
tmp->clr.r = 0.5f;
tmp->clr.g = 1.0f;
tmp->clr.b = 1.0f;
data.uniquePtr.reset(tmp);
}
{
auto tmp = new Circle{};
tmp->radius = 75987;
tmp->clr.r = 0.5f;
tmp->clr.g = 0.0f;
tmp->clr.b = 1.0f;
data.sharedList.emplace_back(tmp);
}
{
auto tmp = new Rectangle{};
tmp->height = 15157;
tmp->width = 48879;
tmp->clr.r = 1.0f;
tmp->clr.g = 0.0f;
tmp->clr.b = 0.0f;
data.sharedList.emplace_back(tmp);
}
data.weakPtr = data.sharedList[0];
data.refPtr = data.sharedList[1].get();
return data;
}
template<typename S>
void serialize(S &s, SomeShapes &o) {
s.ext(o.uniquePtr, StdSmartPtr{});
// to make things more interesting first serialize weakPtr and refPtr,
// even though objects that weakPtr and refPtr is serialized later,
// bitsery will work regardless
s.ext(o.weakPtr, StdSmartPtr{});
s.ext(o.refPtr, PointerObserver{});
s.container(o.sharedList, 100, [&s](std::shared_ptr<Shape> &item) {
s.ext(item, StdSmartPtr{});
});
}
// STEP 1
// define relationships between base and derived classes
namespace bitsery {
namespace ext {
//for each base class define DIRECTLY derived classes
//e.g. PolymorphicBaseClass<Shape> : PolymorphicDerivedClasses<Circle, Rectangle, RoundedRectangle>
// is incorrect, because RoundedRectangle does not directly derive from Shape
template<>
struct PolymorphicBaseClass<Shape> : PolymorphicDerivedClasses<Circle, Rectangle> {
};
template<>
struct PolymorphicBaseClass<Rectangle> : PolymorphicDerivedClasses<RoundedRectangle> {
};
}
}
// convenient type that stores all our types, so that we could easily register and
// also it automatically ensures, that classes is registered in the same order for serialization and deserialization
using MyPolymorphicClassesForRegistering = bitsery::ext::PolymorphicClassesList<Shape>;
//use bitsery namespace for convenience
using namespace bitsery;
//some helper types
using Buffer = std::vector<uint8_t>;
using 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{};
{
//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
TContext ctx{};
std::get<1>(ctx).registerBasesList<MySerializer>(MyPolymorphicClassesForRegistering{});
//serialize our data
MySerializer ser{OutputAdapter{buffer}, &ctx};
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{};
std::get<1>(ctx).registerBasesList<MyDeserializer>(MyPolymorphicClassesForRegistering{});
//serialize our data
MyDeserializer des{InputAdapter{buffer.begin(), writtenSize}, &ctx};
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;
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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()

View File

@@ -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,21 @@
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;
static constexpr bool isConstBuffer = std::is_const<Buffer>::value;
using BuffNonConst = typename std::remove_const<Buffer>::type;
protected:
using TIterator = typename std::conditional<isConstBuffer,
typename traits::BufferAdapterTraits<BuffNonConst>::TConstIterator,
typename traits::BufferAdapterTraits<BuffNonConst>::TIterator>::type;
static_assert(details::IsDefined<TIterator>::value,
"Please define BufferAdapterTraits or include from <bitsery/traits/...>");
BufferIterators(TIterator begin, TIterator end)
:posIt{begin},
endIt{end}
{
: posIt{begin},
endIt{end} {
}
friend details::SessionAccess;
@@ -47,25 +52,25 @@ 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/...>");
using TValue = typename traits::BufferAdapterTraits<typename std::remove_const<Buffer>::type>::TValue;
static_assert(details::IsDefined<TValue>::value,
"Please define BufferAdapterTraits or include from <bitsery/traits/...>");
static_assert(traits::ContainerTraits<typename std::remove_const<Buffer>::type>::isContiguous,
"BufferAdapter only works with contiguous containers");
InputBufferAdapter(TIterator begin, TIterator end): BufferIterators<Buffer>(begin, end)
{
InputBufferAdapter(TIterator begin, TIterator endIt)
: BufferIterators<Buffer>(begin, endIt) {
}
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 +80,6 @@ namespace bitsery {
this->posIt -= size;
//set everything to zeros
std::memset(data, 0, size);
if (error() == ReaderError::NoError)
setError(ReaderError::DataOverflow);
}
@@ -101,24 +105,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<typename std::remove_const<Buffer>::type>::TValue;
static_assert(details::IsDefined<TValue>::value,
"Please define BufferAdapterTraits or include from <bitsery/traits/...>");
static_assert(traits::ContainerTraits<typename std::remove_const<Buffer>::type>::isContiguous,
"BufferAdapter only works with contiguous containers");
UnsafeInputBufferAdapter(TIterator beginIt, TIterator endIt) : BufferIterators<Buffer>(beginIt, endIt) {
}
UnsafeInputBufferAdapter(TIterator begin, size_t size)
: BufferIterators<Buffer>(begin, std::next(begin, size)) {
}
void read(TValue *data, size_t size) {
//for optimization
auto tmp = this->posIt;
this->posIt += size;
assert(std::distance(this->posIt, this->endIt) >= 0);
std::memcpy(data, std::addressof(*tmp), size);
}
ReaderError error() const {
return err;
}
void setError(ReaderError error) {
err = error;
}
bool isCompletedSuccessfully() const {
return this->posIt == this->endIt;
}
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 +174,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 +190,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 +232,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 +245,6 @@ namespace bitsery {
}
};
}
#endif //BITSERY_ADAPTERS_INPUT_BUFFER_ADAPTER_H
#endif //BITSERY_ADAPTER_BUFFER_H

View File

@@ -20,15 +20,13 @@
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//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>
namespace bitsery {
template <typename TChar, typename CharTraits>
@@ -38,32 +36,37 @@ 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)} {}
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 +75,129 @@ 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)} {}
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;
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 +214,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

View File

@@ -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,24 +111,20 @@ 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>
void directRead(T *v, size_t count) {
static_assert(!std::is_const<T>::value, "");
_inputAdapter.read(reinterpret_cast<TValue *>(v), sizeof(T) * count);
//swap each byte if nessesarry
//swap each byte if necessary
_swapDataBits(v, count, std::integral_constant<bool,
Config::NetworkEndianness != details::getSystemEndianness()>{});
}
@@ -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>(), "");
@@ -246,7 +240,7 @@ namespace bitsery {
auto bitsLeft = size;
T res{};
while (bitsLeft > 0) {
auto bits = std::min(bitsLeft, details::BitsSize<UnsignedValue>::value);
auto bits = (std::min)(bitsLeft, details::BitsSize<UnsignedValue>::value);
if (m_scratchBits < bits) {
UnsignedValue tmp;
_reader.template readBytes<sizeof(UnsignedValue), UnsignedValue>(tmp);
@@ -264,6 +258,20 @@ namespace bitsery {
}
};
namespace details {
// used in "making friends" with non-wrapped deserializer type
template <typename TReader>
struct GetNonWrappedAdapterReader {
using Reader = TReader;
};
template <typename TWrapped>
struct GetNonWrappedAdapterReader<AdapterReaderBitPackingWrapper<TWrapped>> {
using Reader = TWrapped;
};
}
}
#endif //BITSERY_BUFFER_READER_H
#endif //BITSERY_ADAPTER_READER_H

View File

@@ -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;
@@ -297,7 +295,7 @@ namespace bitsery {
auto value = v;
auto bitsLeft = size;
while (bitsLeft > 0) {
auto bits = std::min(bitsLeft, valueSize);
auto bits = (std::min)(bitsLeft, valueSize);
_scratch |= static_cast<ScratchType>( value ) << _scratchBits;
_scratchBits += bits;
if (_scratchBits >= valueSize) {
@@ -326,12 +324,25 @@ namespace bitsery {
}
}
const UnsignedType _MASK = std::numeric_limits<UnsignedType>::max();
const UnsignedType _MASK = (std::numeric_limits<UnsignedType>::max)();
ScratchType _scratch{};
size_t _scratchBits{};
TWriter& _writer;
};
namespace details {
// used in "making friends" with non-wrapped serializer type
template <typename TWriter>
struct GetNonWrappedAdapterWriter {
using Writer = TWriter;
};
template <typename TWrapped>
struct GetNonWrappedAdapterWriter<AdapterWriterBitPackingWrapper<TWrapped>> {
using Writer = TWrapped;
};
}
}
#endif //BITSERY_BASIC_WRITER_H
#endif //BITSERY_ADAPTER_WRITER_H

View File

@@ -25,8 +25,8 @@
#define BITSERY_BITSERY_H
#define BITSERY_MAJOR_VERSION 4
#define BITSERY_MINOR_VERSION 0
#define BITSERY_PATCH_VERSION 0
#define BITSERY_MINOR_VERSION 6
#define BITSERY_PATCH_VERSION 1
#define BITSERY_QUOTE_MACRO(name) #name
#define BITSERY_BUILD_VERSION_STR(major,minor, patch) \

View File

@@ -24,10 +24,12 @@
#ifndef BITSERY_COMMON_H
#define BITSERY_COMMON_H
#include <tuple>
namespace bitsery {
/*
* endianess
* endianness
*/
enum class EndiannessType {
LittleEndian,
@@ -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<>;
};
}

View File

@@ -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
@@ -89,6 +102,13 @@ namespace bitsery {
archive(std::forward<TArgs>(tail)...);
}
template <typename T, typename... TArgs>
BasicDeserializer &operator()(T &&head, TArgs &&... tail) {
details::ArchiveFunction<BasicDeserializer, T>::invoke(*this, std::forward<T>(head));
archive(std::forward<TArgs>(tail)...);
return *this;
}
/*
* value
*/
@@ -117,7 +137,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 +147,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 +157,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 +283,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); }
@@ -318,9 +338,13 @@ namespace bitsery {
private:
friend AdapterAccess;
// this is required when creating bitpacking serializer, to access internal context
friend class BasicDeserializer<typename details::GetNonWrappedAdapterReader<TAdapterReader>::Reader, TContext>;
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 +352,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 +362,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,8 +414,11 @@ 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);
// move internal context to and from of bitpacking enabled serializer
tmp._internalContext = std::move(_internalContext);
fnc(tmp);
_internalContext = std::move(tmp._internalContext);
}
//these are dummy functions for extensions that have TValue = void
@@ -421,7 +448,7 @@ namespace bitsery {
des.object(value);
auto& r = AdapterAccess::getReader(des);
return {r.error(), r.isCompletedSuccessfully()};
};
}
}

View File

@@ -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>
@@ -29,6 +29,7 @@
#include <vector>
#include <stack>
#include <cstring>
#include <climits>
#include "adapter_utils.h"
#include "not_defined_type.h"
@@ -40,7 +41,7 @@ namespace bitsery {
template<typename T>
struct BitsSize:public std::integral_constant<size_t, sizeof(T) * 8> {
static_assert(CHAR_BIT == 8, "only support systems with byte size of 8 bits");
};
//add swap functions to class, to avoid compilation warning about unused functions
@@ -124,4 +125,4 @@ namespace bitsery {
}
#endif //BITSERY_DETAILS_BUFFER_COMMON_H
#endif //BITSERY_DETAILS_ADAPTER_COMMON_H

View File

@@ -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

View File

@@ -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>{});
};
}
}
}

View File

@@ -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,10 +32,10 @@ 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
//this time will be used as iterator, so define all operators necessary to work with iterators
friend bool operator == (const NotDefinedType&, const NotDefinedType&) {
return true;
}
@@ -52,10 +52,11 @@ namespace bitsery {
friend int operator - (const NotDefinedType&, const NotDefinedType&) {
return 0;
}
int& operator*() {
return data;
}
int data;
int data{};
};
template <typename T>
@@ -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

View File

@@ -25,32 +25,61 @@
#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
//just make friend it to that class
struct Access {
//this allows to call private serialize method, and construct instance (if no default constructor is provided) for your type
//just make friend it in your class
class Access {
public:
template<typename S, typename T>
static auto serialize(S &s, T &obj) -> decltype(obj.serialize(s)) {
obj.serialize(s);
}
template <typename T>
static T create() {
//if you get an error here, please create default constructor
return T{};
}
template <typename T>
static T* createInHeap() {
return new T{};
}
};
//serializer/deserializer, does not public interface to get underlying writer/reader
//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 have public interface to get underlying writer/reader
//to prevent users from using writer/reader directly, because they have different interface
//and they cannot be used describing serialization flows.: use extensions for this reason.
//this class allows to get underlying adapter writer/reader, and only should be used outside serialization functions.
struct AdapterAccess {
template <typename Serializer>
static typename Serializer::TWriter& getWriter(Serializer& s) {
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 +87,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,37 +151,46 @@ 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
//used for extensions when extension TValue = void
struct DummyType {
};
/*
* this includes all integral types floats and enums(except bool)
* this includes all integral types, floats and enums(except bool)
*/
template<typename T>
struct IsFundamentalType : std::integral_constant<bool,
@@ -149,23 +231,38 @@ 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>{});
}
static constexpr bool isDefined() {
return HasSerializeFunction<S, T>::value || HasSerializeMethod<S, T>::value;
}
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 +273,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. Context type doesn't exists.\nSome functionality requires (de)seserializer to have specific context.");
return std::addressof(std::get<TCastIndex::value>(*ctx));
}
template<typename TCast, typename TContext>
TCast *getContextImpl(TContext *ctx, std::false_type) {
static_assert(std::is_convertible<TContext *, TCast *>::value, "Invalid context cast. Context type doesn't exists.\nSome functionality requires (de)seserializer to have specific context.");
return static_cast<TCast *>(ctx);
}
//get local ctx
template<typename TCast, typename TContext, typename TInternalContext>
TCast *chooseInternalOrExternalContext(TContext *, TInternalContext &internalCtx, std::true_type) {
return getContextImpl<TCast>(&internalCtx, std::true_type{});
}
//get external ctx
template<typename TCast, typename TContext, typename TInternalContext>
TCast *chooseInternalOrExternalContext(TContext *ctx, TInternalContext &, std::false_type) {
return ctx
? getContextImpl<TCast>(ctx, IsSpecializationOf<TContext, std::tuple>{})
: nullptr;
}
template<typename TCast, typename TContext, typename TInternalContext>
TCast *getContext(TContext *ctx, TInternalContext &internalCtx) {
return chooseInternalOrExternalContext<TCast>(ctx, internalCtx, HasContext<TCast, TInternalContext>{});
}
/*
* get context, if type doesn't exists then do not static_assert but return null instead
*/
template<typename TCast, typename TContext>
TCast *getContextFromTypeIfExists(TContext *ctx, std::true_type) {
return static_cast<TCast *>(ctx);
}
template<typename TCast, typename TContext>
TCast *getContextFromTypeIfExists(TContext *, std::false_type) {
return nullptr;
}
template<typename TCast, typename TContext>
TCast *getContextImplIfExists(TContext *ctx, std::false_type) {
return getContextFromTypeIfExists<TCast>(ctx, std::is_convertible<TContext *, TCast *>{});
}
template<typename TCast, typename TContext>
TCast *getContextFromTupleIfExists(TContext *ctx, std::true_type tmp) {
return getContextImpl<TCast>(ctx, tmp);
}
template<typename TCast, typename TContext>
TCast *getContextFromTupleIfExists(TContext *, std::false_type) {
return nullptr;
}
template<typename TCast, typename TContext>
TCast *getContextImplIfExists(TContext *ctx, std::true_type) {
return getContextFromTupleIfExists<TCast>(ctx, HasContext<TCast, TContext>{});
}
//get local ctx
template<typename TCast, typename TContext, typename TInternalContext>
TCast *chooseInternalOrExternalContextIfExists(TContext *, TInternalContext &internalCtx, std::true_type) {
return getContextImplIfExists<TCast>(&internalCtx, std::true_type{});
}
//get external ctx
template<typename TCast, typename TContext, typename TInternalContext>
TCast *chooseInternalOrExternalContextIfExists(TContext *ctx, TInternalContext &, std::false_type) {
return ctx
? getContextImplIfExists<TCast>(ctx, IsSpecializationOf<TContext, std::tuple>{})
: nullptr;
}
template<typename TCast, typename TContext, typename TInternalContext>
TCast *getContextIfTypeExists(TContext *ctx, TInternalContext &internalCtx) {
return chooseInternalOrExternalContextIfExists<TCast>(ctx, internalCtx, HasContext<TCast, TInternalContext>{});
}
}
}
#endif //BITSERY_DETAILS_SERIALIZATION_COMMON_H

View File

@@ -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

View File

@@ -0,0 +1,185 @@
//MIT License
//
//Copyright (c) 2018 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef BITSERY_EXT_COMPACT_VALUE_H
#define BITSERY_EXT_COMPACT_VALUE_H
#include "../details/serialization_common.h"
#include "../details/adapter_common.h"
#include <cassert>
namespace bitsery {
namespace details {
template <bool CheckOverflow>
class CompactValueImpl {
public:
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &s, Writer &writer, const T &v, Fnc &&) const {
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "");
using TValue = typename IntegralFromFundamental<T>::TValue;
serializeImpl(s, writer, reinterpret_cast<const TValue&>(v), std::integral_constant<bool, sizeof(T) != 1>{});
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &d, Reader &reader, T &v, Fnc &&) const {
static_assert(std::is_integral<T>::value || std::is_enum<T>::value, "");
using TValue = typename IntegralFromFundamental<T>::TValue;
deserializeImpl(d, reader, reinterpret_cast<TValue &>(v), std::integral_constant<bool, sizeof(T) != 1>{});
}
private:
// if value is 1byte size, just serialize/ deserialize whole value
template<typename Ser, typename Writer, typename T>
void serializeImpl(Ser &s, Writer &, const T &v, std::false_type) const {
s.value1b(v);
}
template<typename Des, typename Reader, typename T>
void deserializeImpl(Des &d, Reader &, T &v, std::false_type) const {
d.value1b(v);
}
// when value is bigger than 1byte size,
template<typename Ser, typename Writer, typename T>
void serializeImpl(Ser &, Writer &writer, const T &v, std::true_type) const {
auto val = zigZagEncode(v, std::is_signed<typename IntegralFromFundamental<T>::TValue>{});
writeBytes(writer, val);
}
template<typename Des, typename Reader, typename T>
void deserializeImpl(Des &, Reader &reader, T &v, std::true_type) const {
using TUnsigned = SameSizeUnsigned<T>;
TUnsigned res{};
readBytes(reader, res);
v = zigZagDecode<T>(res, std::is_signed<typename IntegralFromFundamental<T>::TValue>{});
}
// zigzag encode signed types
template<typename T>
const SameSizeUnsigned<T> &zigZagEncode(const T &v, std::false_type) const {
return v;
}
template<typename TResult, typename TUnsigned>
const TResult &zigZagDecode(const TUnsigned &v, std::false_type) const{
return v;
}
template<typename T>
SameSizeUnsigned<T> zigZagEncode(const T &v, std::true_type) const {
return (v << 1) ^ (v >> (BitsSize<T>::value - 1));
}
template<typename TResult, typename TUnsigned>
TResult zigZagDecode(TUnsigned v, std::true_type) const {
return (v >> 1) ^ (~(v & 1) + 1); // same as -(v & 1), but no warning on VisualStudio
}
// write/read bytes one by one
template<typename Writer, typename T>
void writeBytes(Writer &w, const T &v) const {
auto val = v;
while(val > 0x7Fu) {
w.template writeBytes<1>(static_cast<uint8_t>(val | 0x80u));
val >>=7u;
}
w.template writeBytes<1>(static_cast<uint8_t>(val));
}
template<typename Reader, typename T>
void readBytes(Reader &r, T &v) const {
constexpr auto TBITS = sizeof(T)*8;
uint8_t b1{0x80u};
auto i = 0u;
for (;i < TBITS && b1 > 0x7Fu; i +=7u) {
r.template readBytes<1>(b1);
v += static_cast<T>(b1 & 0x7Fu) << i;
}
checkReadOverflow<Reader, T>(r, i, b1, std::integral_constant<bool, CheckOverflow>{});
}
template <typename Reader, typename T>
void checkReadOverflow(Reader &r, unsigned shiftedBy, uint8_t remainder, std::true_type) const {
constexpr auto TBITS = sizeof(T)*8;
if (shiftedBy > TBITS && remainder >> (TBITS + 7 - shiftedBy)) {
r.setError(bitsery::ReaderError::DataOverflow);
}
}
template <typename Reader, typename T>
void checkReadOverflow(Reader &, unsigned , uint8_t , std::false_type) const {
}
};
}
namespace ext {
// this type will use value overload, and do not check if type is sufficiently large during deserialization
class CompactValue: public details::CompactValueImpl<false> {};
// this type will enable object overload, and set DataOverflow if value doesn't fit in type, during deserialization
class CompactValueAsObject: public details::CompactValueImpl<true> {};
}
namespace traits {
template<typename T>
struct ExtensionTraits<ext::CompactValue, T> {
using TValue = T;
static constexpr bool SupportValueOverload = true;
// disable object overload, because we don't have implemented serialization function for fundamental types
static constexpr bool SupportObjectOverload = false;
static constexpr bool SupportLambdaOverload = false;
};
template<typename T>
struct ExtensionTraits<ext::CompactValueAsObject, T> {
// use dummy implemenations for value and object overload
using TValue = void;
// only enable object overload
static constexpr bool SupportValueOverload = false;
static constexpr bool SupportObjectOverload = true;
static constexpr bool SupportLambdaOverload = false;
};
template<typename T, bool Check>
struct ExtensionTraits<details::CompactValueImpl<Check>, T> {
using TValue = T;
static constexpr bool SupportValueOverload = !Check;
static constexpr bool SupportObjectOverload = Check;
static constexpr bool SupportLambdaOverload = false;
};
}
}
#endif //BITSERY_EXT_COMPACT_VALUE_H

View File

@@ -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)});

View File

@@ -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();

View 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

View 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

View File

@@ -0,0 +1,94 @@
//MIT License
//
//Copyright (c) 2019 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef BITSERY_EXT_STD_CHRONO_H
#define BITSERY_EXT_STD_CHRONO_H
#include "../traits/core/traits.h"
#include <chrono>
namespace bitsery {
namespace ext {
class StdDuration {
public:
template<typename Ser, typename Writer, typename T, typename Period, typename Fnc>
void serialize(Ser&, Writer&, const std::chrono::duration<T, Period>& obj, Fnc&& fnc) const {
auto res = obj.count();
fnc(res);
}
template<typename Des, typename Reader, typename T, typename Period, typename Fnc>
void deserialize(Des&, Reader&, std::chrono::duration<T, Period>& obj, Fnc&& fnc) const {
T res{};
fnc(res);
obj = std::chrono::duration<T, Period>{res};
}
};
class StdTimePoint {
public:
template<typename Ser, typename Writer, typename Clock, typename T, typename Period, typename Fnc>
void serialize(Ser&, Writer&, const std::chrono::time_point<Clock, std::chrono::duration<T, Period>>& obj,
Fnc&& fnc) const {
auto res = obj.time_since_epoch().count();
fnc(res);
}
template<typename Des, typename Reader, typename Clock, typename T, typename Period, typename Fnc>
void deserialize(Des&, Reader&, std::chrono::time_point<Clock, std::chrono::duration<T, Period>>& obj,
Fnc&& fnc) const {
T res{};
fnc(res);
auto dur = std::chrono::duration<T, Period>{res};
obj = std::chrono::time_point<Clock, std::chrono::duration<T, Period>>{dur};
}
};
}
namespace traits {
template<typename Rep, typename Period>
struct ExtensionTraits<ext::StdDuration, std::chrono::duration<Rep, Period>> {
using TValue = Rep;
static constexpr bool SupportValueOverload = true;
static constexpr bool SupportObjectOverload = false;
static constexpr bool SupportLambdaOverload = false;
};
template<typename Clock, typename Rep, typename Period>
struct ExtensionTraits<ext::StdTimePoint,
std::chrono::time_point<Clock, std::chrono::duration<Rep, Period>>> {
using TValue = Rep;
static constexpr bool SupportValueOverload = true;
static constexpr bool SupportObjectOverload = false;
static constexpr bool SupportLambdaOverload = false;
};
}
}
#endif //BITSERY_EXT_STD_CHRONO_H

View File

@@ -23,7 +23,11 @@
#ifndef BITSERY_EXT_STD_MAP_H
#define BITSERY_EXT_STD_MAP_H
#include "../traits/core/traits.h"
#include "../details/adapter_utils.h"
#include "../details/serialization_common.h"
//we need this, so we could reserve for non ordered map
#include <unordered_map>
namespace bitsery {
namespace ext {
@@ -52,17 +56,30 @@ namespace bitsery {
size_t size{};
details::readSize(reader, size, _maxSize);
auto hint = obj.begin();
obj.clear();
reserve(obj, size);
auto hint = obj.begin();
for (auto i = 0u; i < size; ++i) {
TKey key;
TValue value;
auto key{bitsery::Access::create<TKey>()};
auto value{bitsery::Access::create<TValue>()};
fnc(key, value);
hint = obj.emplace_hint(hint, std::move(key), std::move(value));
}
}
private:
template <typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
void reserve(std::unordered_map<Key, T, Hash, KeyEqual, Allocator>& obj, size_t size) const {
obj.reserve(size);
}
template <typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
void reserve(std::unordered_multimap<Key, T, Hash, KeyEqual, Allocator>& obj, size_t size) const {
obj.reserve(size);
}
template <typename T>
void reserve(T& , size_t ) const {
//for ordered container do nothing
}
size_t _maxSize;
};
}

View File

@@ -24,22 +24,13 @@
#ifndef BITSERY_EXT_STD_OPTIONAL_H
#define BITSERY_EXT_STD_OPTIONAL_H
//this module do not include optional, but expects it to be declared in std::optional
//if you're using experimental optional from <experimental/optional>
//add it in std namespace like this:
//namespace std {
// template <typename T>
// using optional = experimental::optional<T>;
//}
#include <type_traits>
#include "../traits/core/traits.h"
#include "../details/serialization_common.h"
#include <optional>
namespace bitsery {
namespace ext {
template<typename T>
using std_optional = ::std::optional<T>;
class StdOptional {
public:
@@ -48,38 +39,27 @@ namespace bitsery {
* @param alignBeforeData only makes sense when bit-packing enabled, by default aligns after writing/reading bool state of optional
*/
explicit StdOptional(bool alignBeforeData=true):_alignBeforeData{alignBeforeData} {}
template<typename T>
constexpr void assertType() const {
using TOpt = typename std::remove_cv<T>::type;
using TVal = typename TOpt::value_type;
static_assert(std::is_same<TOpt, std_optional<TVal>>(), "");
static_assert(std::is_default_constructible<TVal>::value, "");
};
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &ser, Writer &, const T &obj, Fnc &&fnc) const {
assertType<T>();
void serialize(Ser &ser, Writer &, const std::optional<T> &obj, Fnc &&fnc) const {
ser.boolValue(static_cast<bool>(obj));
if (_alignBeforeData)
ser.align();
if (obj)
fnc(const_cast<typename T::value_type & >(*obj));
fnc(const_cast<T&>(*obj));
}
template<typename Des, typename Reader, typename T, typename Fnc>
void deserialize(Des &des, Reader &, T &obj, Fnc &&fnc) const {
assertType<T>();
void deserialize(Des &des, Reader &, std::optional<T> &obj, Fnc &&fnc) const {
bool exists{};
des.boolValue(exists);
if (_alignBeforeData)
des.align();
if (exists) {
typename T::value_type tmp{};
fnc(tmp);
obj = tmp;
obj = ::bitsery::Access::create<T>();
fnc(*obj);
} else {
//experimental optional doesnt have .reset method
obj = T{};
obj = std::nullopt;
}
}
private:
@@ -89,8 +69,8 @@ namespace bitsery {
namespace traits {
template<typename T>
struct ExtensionTraits<ext::StdOptional, T> {
using TValue = typename T::value_type;
struct ExtensionTraits<ext::StdOptional, std::optional<T>> {
using TValue = T;
static constexpr bool SupportValueOverload = true;
static constexpr bool SupportObjectOverload = true;
static constexpr bool SupportLambdaOverload = true;

View File

@@ -51,15 +51,15 @@ namespace bitsery {
}
};
//inherit from queue so we could take underlying container
template <typename T, typename C>
struct PriorityQueueCnt : public std::priority_queue<T, C>
template <typename T, typename Seq, typename Cmp>
struct PriorityQueueCnt : public std::priority_queue<T, Seq, Cmp>
{
static const C& getContainer(const std::priority_queue<T, C>& s )
static const Seq& getContainer(const std::priority_queue<T, Seq, Cmp>& s )
{
//get address of underlying container
return s.*(&PriorityQueueCnt::c);
}
static C& getContainer(std::priority_queue<T, C>& s )
static Seq& getContainer(std::priority_queue<T, Seq, Cmp>& s )
{
//get address of underlying container
return s.*(&PriorityQueueCnt::c);
@@ -82,14 +82,14 @@ namespace bitsery {
}
//for priority_queue
template<typename Ser, typename Writer, typename T, typename C, typename Fnc>
void serialize(Ser &ser, Writer &, const std::priority_queue<T,C> &obj, Fnc &&fnc) const {
ser.container(PriorityQueueCnt<T,C>::getContainer(obj), _maxSize, std::forward<Fnc>(fnc));
template<typename Ser, typename Writer, typename T, typename C, typename Comp, typename Fnc>
void serialize(Ser &ser, Writer &, const std::priority_queue<T,C, Comp> &obj, Fnc &&fnc) const {
ser.container(PriorityQueueCnt<T,C, Comp>::getContainer(obj), _maxSize, std::forward<Fnc>(fnc));
}
template<typename Des, typename Reader, typename T, typename C, typename Fnc>
void deserialize(Des &des, Reader &, std::priority_queue<T,C> &obj, Fnc &&fnc) const {
des.container(PriorityQueueCnt<T,C>::getContainer(obj), _maxSize, std::forward<Fnc>(fnc));
template<typename Des, typename Reader, typename T, typename C, typename Comp, typename Fnc>
void deserialize(Des &des, Reader &, std::priority_queue<T,C, Comp> &obj, Fnc &&fnc) const {
des.container(PriorityQueueCnt<T,C, Comp>::getContainer(obj), _maxSize, std::forward<Fnc>(fnc));
}
};

View File

@@ -25,7 +25,8 @@
#include <cassert>
#include "../details/adapter_utils.h"
//we need this, so we could
#include "../details/serialization_common.h"
//we need this, so we could reserve for non ordered set
#include <unordered_set>
namespace bitsery {
@@ -53,28 +54,30 @@ namespace bitsery {
size_t size{};
details::readSize(reader, size, _maxSize);
auto hint = obj.begin();
obj.clear();
reserve(obj, size);
auto hint = obj.begin();
for (auto i = 0u; i < size; ++i) {
TKey key;
auto key{bitsery::Access::create<TKey>()};
fnc(key);
hint = obj.emplace_hint(hint, std::move(key));
}
}
private:
template <typename T>
void reserve(std::unordered_set<T>& obj, size_t size) const {
template <typename Key, typename Hash, typename KeyEqual, typename Allocator>
void reserve(std::unordered_set<Key, Hash, KeyEqual, Allocator>& obj, size_t size) const {
obj.reserve(size);
}
template <typename T>
void reserve(std::unordered_multiset<T>& obj, size_t size) const {
template <typename Key, typename Hash, typename KeyEqual, typename Allocator>
void reserve(std::unordered_multiset<Key, Hash, KeyEqual, Allocator>& obj, size_t size) const {
obj.reserve(size);
}
template <typename T>
void reserve(T obj, size_t size) const {
void reserve(T& , size_t ) const {
//for ordered container do nothing
}
size_t _maxSize;

View File

@@ -0,0 +1,129 @@
//MIT License
//
//Copyright (c) 2018 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef BITSERY_EXT_STD_SMART_PTR_H
#define BITSERY_EXT_STD_SMART_PTR_H
#include <cassert>
#include "../traits/core/traits.h"
#include "utils/pointer_utils.h"
#include "utils/polymorphism_utils.h"
#include "utils/rtti_utils.h"
#include <memory>
namespace bitsery {
namespace ext {
namespace smart_ptr_details {
//further code is for managing shared ownership
//do not nest this type in pointer manager class itself, because it will be different type for different T
struct SharedPtrSharedState : pointer_utils::PointerSharedStateBase {
std::shared_ptr<void> obj{};
};
template<typename T>
struct SmartPtrOwnerManager {
using TElement = typename T::element_type;
template <typename TDeleter>
static TElement *getPtr(std::unique_ptr<TElement, TDeleter> &obj) {
return obj.get();
}
static TElement *getPtr(std::shared_ptr<TElement> &obj) {
return obj.get();
}
static TElement *getPtr(std::weak_ptr<TElement> &obj) {
if (auto ptr = obj.lock())
return ptr.get();
return nullptr;
}
static constexpr PointerOwnershipType getOwnership() {
return ::bitsery::details::IsSpecializationOf<T, std::unique_ptr>::value
? PointerOwnershipType::Owner
: std::is_same<std::shared_ptr<TElement>, T>::value
? PointerOwnershipType::SharedOwner
: PointerOwnershipType::SharedObserver;
}
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

View File

@@ -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;

View File

@@ -0,0 +1,81 @@
//MIT License
//
//Copyright (c) 2019 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef BITSERY_EXT_STD_TUPLE_H
#define BITSERY_EXT_STD_TUPLE_H
#include "utils/composite_type_overloads.h"
#include "../traits/core/traits.h"
#include <tuple>
namespace bitsery {
namespace ext {
template<typename ...Overloads>
class StdTuple : public details::CompositeTypeOverloadsUtils<std::tuple, Overloads...> {
public:
template<typename Ser, typename Writer, typename Fnc, typename ...Ts>
void serialize(Ser& ser, Writer&, const std::tuple<Ts...>& obj, Fnc&&) const {
serializeAll(ser, const_cast<std::tuple<Ts...>&>(obj));
}
template<typename Des, typename Reader, typename Fnc, typename ...Ts>
void deserialize(Des& des, Reader&, std::tuple<Ts...>& obj, Fnc&&) const {
serializeAll(des, obj);
}
private:
template<typename S, typename ...Ts>
void serializeAll(S& s, std::tuple<Ts...>& obj) const {
this->execAll(obj, [this, &s](auto& data, auto index) {
constexpr size_t Index = decltype(index)::value;
this->serializeType(s, std::get<Index>(data));
});
}
};
// deduction guide
template<typename ...Overloads>
StdTuple(Overloads...) -> StdTuple<Overloads...>;
}
namespace traits {
template<typename Tuple, typename ... Overloads>
struct ExtensionTraits<ext::StdTuple<Overloads...>, Tuple> {
static_assert(bitsery::details::IsSpecializationOf<Tuple, std::tuple>::value,
"StdTuple only works with std::tuple");
using TValue = void;
static constexpr bool SupportValueOverload = false;
static constexpr bool SupportObjectOverload = true;
static constexpr bool SupportLambdaOverload = false;
};
}
}
#endif //BITSERY_EXT_STD_TUPLE_H

View File

@@ -0,0 +1,91 @@
//MIT License
//
//Copyright (c) 2019 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef BITSERY_EXT_STD_VARIANT_H
#define BITSERY_EXT_STD_VARIANT_H
#include "utils/composite_type_overloads.h"
#include "../traits/core/traits.h"
#include <variant>
namespace bitsery {
namespace ext {
template<typename ...Overloads>
class StdVariant : public details::CompositeTypeOverloadsUtils<std::variant, Overloads...> {
public:
template<typename Ser, typename Writer, typename Fnc, typename ...Ts>
void serialize(Ser& ser, Writer& writer, const std::variant<Ts...>& obj, Fnc&&) const {
auto index = obj.index();
assert(index != std::variant_npos);
details::writeSize(writer, index);
this->execIndex(index, const_cast<std::variant<Ts...>&>(obj), [this, &ser](auto& data, auto index) {
constexpr size_t Index = decltype(index)::value;
this->serializeType(ser, std::get<Index>(data));
});
}
template<typename Des, typename Reader, typename Fnc, typename ...Ts>
void deserialize(Des& des, Reader& reader, std::variant<Ts...>& obj, Fnc&&) const {
size_t index{};
details::readSize(reader, index, sizeof...(Ts));
this->execIndex(index, obj, [this, &des](auto& data, auto index) {
constexpr size_t Index = decltype(index)::value;
using TElem = typename std::variant_alternative<Index, std::variant<Ts...>>::type;
TElem item = ::bitsery::Access::create<TElem>();
this->serializeType(des, item);
data = std::variant<Ts...>(std::in_place_index_t<Index>{}, std::move(item));
});
}
};
// deduction guide
template<typename ...Overloads>
StdVariant(Overloads...) -> StdVariant<Overloads...>;
}
//defines empty fuction, that handles monostate
template <typename S>
void serialize(S& , std::monostate&) {}
namespace traits {
template<typename Variant, typename ... Overloads>
struct ExtensionTraits<ext::StdVariant<Overloads...>, Variant> {
static_assert(bitsery::details::IsSpecializationOf<Variant, std::variant>::value,
"StdVariant only works with std::variant");
using TValue = void;
static constexpr bool SupportValueOverload = false;
static constexpr bool SupportObjectOverload = true;
static constexpr bool SupportLambdaOverload = false;
};
}
}
#endif //BITSERY_EXT_STD_VARIANT_H

View File

@@ -0,0 +1,136 @@
//MIT License
//
//Copyright (c) 2019 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef BITSERY_EXT_COMPOSITE_TYPE_OVERLOADS_H
#define BITSERY_EXT_COMPOSITE_TYPE_OVERLOADS_H
#include "../../details/serialization_common.h"
#include <functional>
#if __cplusplus < 201703L
#error these utils requires c++17
// in theory, it could be implemented using C++11
// but without class template argument deduction guides that would be very inconvenient to use
// these are very helpul for sum types (e.g. std::variant),
// but for product types (e.g. std::tuple) you can you can easily do it your self with lambda, without extension
#endif
namespace bitsery {
namespace ext {
// might be usable, when you want to have one overload set for different composite types,
// e.g. variant, tuple and pair
template<class... Ts>
struct CompositeTypeOverloads : Ts ... {
using Ts::operator()...;
};
template<typename ...Overloads>
CompositeTypeOverloads(Overloads...) -> CompositeTypeOverloads<Overloads...>;
// convenient way to invoke s.value<N>, shorter than specifying a lambda
template<typename T, size_t N>
struct OverloadValue {
template <typename S>
void operator()(S& s, T& v) const {
s.template value<N>(v);
}
};
// convenient way to invoke other extension using value or object overloads
// there is no reason to write OverloadExtLambda,
// because you'll need to specify lambda type, which is very inconvenient and it will be much
// easier to simple write a lambda with extension inside it,
// in order to implement it in a convenient way, i need a way to deduce only last template parameter (lambda type)
// but this is not possible with deduction guides at the moment
template<typename T, size_t N, typename Ext>
struct OverloadExtValue : public Ext {
template <typename S>
void operator()(S& s, T& v) const {
s.template ext<N>(v, static_cast<const Ext&>(*this));
}
};
template<typename T, typename Ext>
struct OverloadExtObject : public Ext {
template <typename S>
void operator()(S& s, T& v) const {
s.ext(v, static_cast<const Ext&>(*this));
}
};
}
namespace details {
template<template<typename ...> typename CompositeType, typename ...Overloads>
class CompositeTypeOverloadsUtils : public ext::CompositeTypeOverloads<Overloads...> {
protected:
// converts run-time index to compile-time index,
// by calling lambda with std::integral_constant<size_t, INDEX>
template<typename Fnc, typename ... Ts>
void execIndex(size_t index, CompositeType<Ts...>& obj, Fnc&& fnc) const {
execIndexImpl(index, obj, std::forward<Fnc>(fnc), std::index_sequence_for<Ts...>{});
}
// call lambda for all indexes in composite type
template<typename Fnc, typename ... Ts>
void execAll(CompositeType<Ts...>& obj, Fnc&& fnc) const {
execAllImpl(obj, std::forward<Fnc>(fnc), std::index_sequence_for<Ts...>{});
}
// serialize a type, by using overload first
template<typename S, typename T>
void serializeType(S& s, T& v) const {
// first check if overload exists, otherwise try to call serialize method
if constexpr (hasOverload<S, T>()) {
std::invoke(*this, s, v);
} else {
static_assert(details::SerializeFunction<S, T>::isDefined(),
"Please define overload or 'serialize' function for your type.");
s.object(v);
}
}
private:
template<typename S, typename T>
static constexpr bool hasOverload() {
return std::is_invocable<ext::CompositeTypeOverloads<Overloads...>,
std::add_lvalue_reference_t<S>, std::add_lvalue_reference_t<T>>::value;
}
template<typename Variant, typename Fnc, size_t ...Is>
void execIndexImpl(size_t index, Variant& obj, Fnc&& fnc, std::index_sequence<Is...>) const {
((index == Is ? fnc(obj, std::integral_constant<size_t, Is>{}), 0 : 0), ...);
}
template<typename Variant, typename Fnc, size_t ...Is>
void execAllImpl(Variant& obj, Fnc&& fnc, std::index_sequence<Is...>) const {
(fnc(obj, std::integral_constant<size_t, Is>{}), ...);
}
};
}
}
#endif //BITSERY_EXT_COMPOSITE_TYPE_OVERLOADS_H

View File

@@ -0,0 +1,387 @@
//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"
#include "../../details/serialization_common.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;
void update(PointerOwnershipType ptrType) {
//do nothing for observer
if (ptrType == PointerOwnershipType::Observer)
return;
if (ownershipType == PointerOwnershipType::Observer) {
//set ownership type
ownershipType = ptrType;
return;
}
//only shared ownership can get here multiple times
assert(ptrType == PointerOwnershipType::SharedOwner || ptrType == PointerOwnershipType::SharedObserver);
//check if need to update to SharedOwner
if (ptrType == PointerOwnershipType::SharedOwner)
ownershipType = ptrType;
//mark that object already processed, so we do not serialize/deserialize duplicate objects
isSharedProcessed = true;
}
};
struct PLCInfoSerializer: PLCInfo {
PLCInfoSerializer(size_t id_, PointerOwnershipType ownershipType_)
: PLCInfo(ownershipType_), id{id_} {}
size_t id;
};
struct PLCInfoDeserializer : PLCInfo {
PLCInfoDeserializer(void *ptr, PointerOwnershipType ownershipType_)
: 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{};
};
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;
}
ptrInfo.update(ptrType);
return ptrInfo;
}
//valid, when all pointers have owners.
//we cannot serialize pointers, if we haven't serialized objects themselves
bool isPointerSerializationValid() const {
return std::all_of(_ptrMap.begin(), _ptrMap.end(),
[](const std::pair<const void *, PLCInfoSerializer> &p) {
return p.second.ownershipType == PointerOwnershipType::SharedOwner ||
p.second.ownershipType == PointerOwnershipType::Owner;
});
}
private:
size_t _currId;
std::unordered_map<const void *, PLCInfoSerializer> _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)
ptrInfo.update(ptrType);
return ptrInfo;
}
void clearSharedState() {
for (auto &item: _idMap)
item.second.sharedState.reset();
}
//valid, when all pointers has owners
bool isPointerDeserializationValid() const {
return std::all_of(_idMap.begin(), _idMap.end(),
[](const std::pair<const size_t, PLCInfoDeserializer> &p) {
return p.second.ownershipType == PointerOwnershipType::SharedOwner ||
p.second.ownershipType == PointerOwnershipType::Owner;
});
}
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 = ::bitsery::Access::createInHeap<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 = ::bitsery::Access::createInHeap<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:
explicit PointerLinkingContext() = default;
bool isValid() {
return isPointerSerializationValid() && isPointerDeserializationValid();
}
};
}
}
#endif //BITSERY_POINTER_UTILS_H

View File

@@ -0,0 +1,238 @@
//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"
#include "../../details/serialization_common.h"
namespace bitsery {
namespace ext {
//helper type, that contains list of types
template<typename ...>
struct PolymorphicClassesList {
};
//specialize for your base class by deriving from PolymorphicDerivedClasses with list of derivatives that DIRECTLY inherits from your base class.
//e.g.
// template <> PolymorphicBaseClass<Animal>: PolymorphicDerivedClasses<Dog, Cat>{};
// template <> PolymorphicBaseClass<Dog>: PolymorphicDerivedClasses<Bulldog, GoldenRetriever> {};
// IMPORTANT !!!
// although you can add all derivates to same base like this:
// template <> PolymorphicBaseClass<Animal>:PolymorphicDerivedClasses<Dog, Cat, Bulldog, GoldenRetriever>{};
// it will not work when you try to serialize Dog*, because it will not find Bulldog and GoldenRetriever
template<typename TBase>
struct PolymorphicBaseClass {
using Childs = PolymorphicClassesList<>;
};
//derive from this class when specifying childs for your base class, atleast one child must exists, hence T1
//e.g.
// template <> PolymorphicBaseClass<Animal>: PolymorphicDerivedClasses<Dog, Cat>{};
template<typename T1, typename ... Tn>
struct PolymorphicDerivedClasses {
using Childs = PolymorphicClassesList<T1, Tn...>;
};
class PolymorphicHandlerBase {
public:
virtual void *create() const = 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(::bitsery::Access::createInHeap<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, template<typename> class THierarchy, typename TBase, typename TDerived>
void add() {
addToMap<TSerializer, TBase, TDerived>(std::is_abstract<TDerived>{});
addChilds<TSerializer, THierarchy, TBase, TDerived>(typename THierarchy<TDerived>::Childs{});
}
template<typename TSerializer, template<typename> class THierarchy, typename TBase, typename TDerived, typename T1, typename ... Tn>
void addChilds(PolymorphicClassesList<T1, Tn...>) {
static_assert(std::is_base_of<TDerived, T1>::value,
"PolymorphicBaseClass<TBase> must derive a list of derived classes from TBase.");
add<TSerializer, THierarchy, TBase, T1>();
addChilds<TSerializer, THierarchy, TBase, TDerived>(PolymorphicClassesList<Tn...>{});
//iterate through derived class hierarchy as well
add<TSerializer, THierarchy, T1, T1>();
}
template<typename TSerializer, template<typename> class THierarchy, typename TBase, typename TDerived>
void addChilds(PolymorphicClassesList<>) {
}
template<typename TSerializer, typename TBase, typename TDerived>
void addToMap(std::false_type) {
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, template<typename> class THierarchy = PolymorphicBaseClass, typename T1, typename ...Tn>
[[deprecated("de/serializer instance is not required")]] void registerBasesList(const TSerializer &s, PolymorphicClassesList<T1, Tn...>) {
add<TSerializer, THierarchy, T1, T1>();
registerBasesList<TSerializer, THierarchy>(s, PolymorphicClassesList<Tn...>{});
}
template<typename TSerializer, template<typename> class THierarchy>
[[deprecated]] void registerBasesList(const TSerializer &, PolymorphicClassesList<>) {
}
// THierarchy is the name of class, that defines hierarchy
// PolymorphicBaseClass is defined as default parameter, so that at instantiation time
// it will get unique symbol in translation unit for PolymorphicBaseClass (which is defined in anonymous namespace)
// https://github.com/fraillt/bitsery/issues/9
template<typename TSerializer, template<typename> class THierarchy = PolymorphicBaseClass, typename T1, typename ...Tn>
void registerBasesList(PolymorphicClassesList<T1, Tn...>) {
add<TSerializer, THierarchy, T1, T1>();
registerBasesList<TSerializer, THierarchy>(PolymorphicClassesList<Tn...>{});
}
template<typename TSerializer, template<typename> class THierarchy>
void registerBasesList(PolymorphicClassesList<>) {
}
// optional method, in case you want to construct base class hierarchy your self
template <typename TSerializer, typename TBase, typename TDerived>
void registerSingleBaseBranch() {
static_assert(std::is_base_of<TBase, TDerived>::value, "TDerived must be derived from TBase");
static_assert(!std::is_abstract<TDerived>::value, "TDerived cannot be abstract");
addToMap<TSerializer, TBase, TDerived>(std::false_type{});
}
template<typename Serializer, typename 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

View 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

View File

@@ -96,7 +96,8 @@ namespace bitsery {
constexpr RangeSpec(T minValue, T maxValue, T precision) :
min{minValue},
max{maxValue},
bitsRequired{calcRequiredBits<details::SameSizeUnsigned<T>>({}, ((max - min) / precision))} {
bitsRequired{calcRequiredBits<details::SameSizeUnsigned<T>>(
{}, static_cast<details::SameSizeUnsigned<T>>((max - min) / precision))} {
}
@@ -108,13 +109,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 +123,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 +142,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 +164,8 @@ namespace bitsery {
public:
template<typename ... Args>
explicit constexpr ValueRange(Args &&... args):_range{std::forward<Args>(args)...} {};
constexpr ValueRange(const TValue& min, const TValue& max, Args &&... args)
:_range{min, max, std::forward<Args>(args)...} {}
template<typename Ser, typename Writer, typename T, typename Fnc>
void serialize(Ser &, Writer &writer, const T &v, Fnc &&) const {

View File

@@ -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.
};
}
}

View File

@@ -0,0 +1,40 @@
//MIT License
//
//Copyright (c) 2019 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef BITSERY_FLEXIBLE_TYPE_STD_CHRONO_H
#define BITSERY_FLEXIBLE_TYPE_STD_CHRONO_H
#include "../ext/std_chrono.h"
namespace bitsery {
template<typename S, typename T, typename P>
void serialize(S &s, std::chrono::duration<T, P> &obj) {
s.template ext<sizeof(T)>(obj, ext::StdDuration{});
}
template<typename S, typename C, typename T, typename P>
void serialize(S &s, std::chrono::time_point<C, std::chrono::duration<T, P>> &obj) {
s.template ext<sizeof(T)>(obj, ext::StdTimePoint{});
}
}
#endif //BITSERY_FLEXIBLE_TYPE_STD_CHRONO_H

View File

@@ -28,8 +28,8 @@
#include "../details/flexible_common.h"
namespace bitsery {
template<typename S, typename ... TArgs>
void serialize(S &s, std::deque<TArgs... > &obj) {
template<typename S, typename T, typename Allocator>
void serialize(S &s, std::deque<T, Allocator> &obj) {
flexible::processContainer(s, obj);
}
}

View File

@@ -28,8 +28,8 @@
#include "../details/flexible_common.h"
namespace bitsery {
template<typename S, typename ... TArgs>
void serialize(S &s, std::forward_list<TArgs... > &obj) {
template<typename S, typename T, typename Allocator>
void serialize(S &s, std::forward_list<T, Allocator> &obj) {
flexible::processContainer(s, obj);
}
}

View File

@@ -28,8 +28,8 @@
#include "../details/flexible_common.h"
namespace bitsery {
template<typename S, typename ... TArgs>
void serialize(S &s, std::list<TArgs... > &obj) {
template<typename S, typename T, typename Allocator>
void serialize(S &s, std::list<T, Allocator> &obj) {
flexible::processContainer(s, obj);
}
}

View File

@@ -28,23 +28,19 @@
#include "../ext/std_map.h"
namespace bitsery {
template<typename S, typename ... TArgs>
void serialize(S &s, std::map<TArgs ... > &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
using TKey = typename std::map<TArgs...>::key_type;
using TValue = typename std::map<TArgs...>::mapped_type;
template<typename S, typename Key, typename T, typename Compare, typename Allocator>
void serialize(S &s, std::map<Key, T, Compare, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
s.ext(obj, ext::StdMap{maxSize},
[&s](TKey& key, TValue& value) {
[&s](Key& key, T& value) {
s.object(key);
s.object(value);
});
}
template<typename S, typename ... TArgs>
void serialize(S &s, std::multimap<TArgs ... > &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
using TKey = typename std::multimap<TArgs...>::key_type;
using TValue = typename std::multimap<TArgs...>::mapped_type;
template<typename S, typename Key, typename T, typename Compare, typename Allocator>
void serialize(S &s, std::multimap<Key, T, Compare, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
s.ext(obj, ext::StdMap{maxSize},
[&s](TKey& key, TValue& value) {
[&s](Key& key, T& value) {
s.object(key);
s.object(value);
});

View 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

View File

@@ -28,13 +28,13 @@
#include "../ext/std_set.h"
namespace bitsery {
template<typename S, typename ... TArgs>
void serialize(S &s, std::set<TArgs...> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
template<typename S, typename Key, typename Compare, typename Allocator>
void serialize(S &s, std::set<Key, Compare, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
s.ext(obj, ext::StdSet{maxSize});
}
template<typename S, typename ... TArgs>
void serialize(S &s, std::multiset<TArgs...> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
template<typename S, typename Key, typename Compare, typename Allocator>
void serialize(S &s, std::multiset<Key, Compare, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
s.ext(obj, ext::StdSet{maxSize});
}

View File

@@ -28,8 +28,8 @@
#include "../details/flexible_common.h"
namespace bitsery {
template<typename S, typename T, typename ... TArgs>
void serialize(S &s, std::basic_string<T, TArgs...> &str) {
template<typename S, typename CharT, typename Traits, typename Allocator>
void serialize(S &s, std::basic_string<CharT, Traits, Allocator> &str) {
flexible::processContainer(s, str);
}
}

View File

@@ -0,0 +1,35 @@
//MIT License
//
//Copyright (c) 2019 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef BITSERY_FLEXIBLE_TYPE_STD_TUPLE_H
#define BITSERY_FLEXIBLE_TYPE_STD_TUPLE_H
#include "../ext/std_tuple.h"
namespace bitsery {
template<typename S, typename ...Ts>
void serialize(S &s, std::tuple<Ts...> &obj) {
s.ext(obj, ext::StdTuple{});
}
}
#endif //BITSERY_FLEXIBLE_TYPE_STD_TUPLE_H

View File

@@ -28,23 +28,19 @@
#include "../ext/std_map.h"
namespace bitsery {
template<typename S, typename ... TArgs>
void serialize(S &s, std::unordered_map<TArgs ... > &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
using TKey = typename std::unordered_map<TArgs...>::key_type;
using TValue = typename std::unordered_map<TArgs...>::mapped_type;
template<typename S, typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
void serialize(S &s, std::unordered_map<Key, T, Hash, KeyEqual, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
s.ext(obj, ext::StdMap{maxSize},
[&s](TKey& key, TValue& value) {
[&s](Key& key, T& value) {
s.object(key);
s.object(value);
});
}
template<typename S, typename ... TArgs>
void serialize(S &s, std::unordered_multimap<TArgs ... > &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
using TKey = typename std::unordered_multimap<TArgs...>::key_type;
using TValue = typename std::unordered_multimap<TArgs...>::mapped_type;
template<typename S, typename Key, typename T, typename Hash, typename KeyEqual, typename Allocator>
void serialize(S &s, std::unordered_multimap<Key, T, Hash, KeyEqual, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
s.ext(obj, ext::StdMap{maxSize},
[&s](TKey& key, TValue& value) {
[&s](Key& key, T& value) {
s.object(key);
s.object(value);
});

View File

@@ -28,13 +28,13 @@
#include "../ext/std_set.h"
namespace bitsery {
template<typename S, typename ... TArgs>
void serialize(S &s, std::unordered_set<TArgs...> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
template<typename S, typename Key, typename Hash, typename KeyEqual, typename Allocator>
void serialize(S &s, std::unordered_set<Key, Hash, KeyEqual, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
s.ext(obj, ext::StdSet{maxSize});
}
template<typename S, typename ... TArgs>
void serialize(S &s, std::unordered_multiset<TArgs...> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
template<typename S, typename Key, typename Hash, typename KeyEqual, typename Allocator>
void serialize(S &s, std::unordered_multiset<Key, Hash, KeyEqual, Allocator> &obj, size_t maxSize = std::numeric_limits<size_t>::max()) {
s.ext(obj, ext::StdSet{maxSize});
}

View File

@@ -0,0 +1,35 @@
//MIT License
//
//Copyright (c) 2019 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#ifndef BITSERY_FLEXIBLE_TYPE_STD_VARIANT_H
#define BITSERY_FLEXIBLE_TYPE_STD_VARIANT_H
#include "../ext/std_variant.h"
namespace bitsery {
template<typename S, typename ...Ts>
void serialize(S &s, std::variant<Ts...> &obj) {
s.ext(obj, ext::StdVariant{});
}
}
#endif //BITSERY_FLEXIBLE_TYPE_STD_VARIANT_H

View File

@@ -28,8 +28,8 @@
#include "../details/flexible_common.h"
namespace bitsery {
template<typename S, typename ... TArgs>
void serialize(S &s, std::vector<TArgs... > &obj) {
template<typename S, typename T, typename Allocator>
void serialize(S &s, std::vector<T, Allocator> &obj) {
flexible::processContainer(s, obj);
}
}

View File

@@ -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
@@ -86,6 +100,13 @@ namespace bitsery {
archive(std::forward<TArgs>(tail)...);
}
template <typename T, typename... TArgs>
BasicSerializer &operator()(T &&head, TArgs &&... tail) {
details::ArchiveFunction<BasicSerializer, T>::invoke(*this, std::forward<T>(head));
archive(std::forward<TArgs>(tail)...);
return *this;
}
/*
* value overloads
*/
@@ -114,7 +135,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 +145,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 +155,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 +280,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); }
@@ -314,9 +335,13 @@ namespace bitsery {
private:
friend AdapterAccess;
// this is required when creating bitpacking serializer, to access internal context
friend class BasicSerializer<typename details::GetNonWrappedAdapterWriter<TAdapterWriter>::Writer, TContext>;
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 +349,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 +360,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 +369,7 @@ namespace bitsery {
for (; first != last; ++first) {
fnc(const_cast<TValue&>(*first));
}
};
}
//process text,
template<size_t VSIZE, typename T>
@@ -354,14 +379,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,8 +406,11 @@ 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);
// move internal context to and from of bitpacking enabled serializer
tmp._internalContext = std::move(_internalContext);
fnc(tmp);
_internalContext = std::move(tmp._internalContext);
}
//these are dummy functions for extensions that have TValue = void
@@ -401,6 +429,7 @@ namespace bitsery {
};
//helper type
template <typename Adapter>
using Serializer = BasicSerializer<AdapterWriter<Adapter, DefaultConfig>>;
@@ -413,11 +442,11 @@ namespace bitsery {
auto& w = AdapterAccess::getWriter(ser);
w.flush();
return w.writtenBytesCount();
};
}
template <typename T>
size_t quickMeasureSize(const T& value) {
BasicSerializer<MeasureSize> ser {nullptr};
BasicSerializer<MeasureSize> ser{MeasureSize{}};
ser.object(value);
auto& w = AdapterAccess::getWriter(ser);
w.flush();

View File

@@ -20,11 +20,11 @@
//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>
#include "../../details/serialization_common.h"
namespace bitsery {
namespace traits {
@@ -53,13 +53,28 @@ namespace bitsery {
return container.size();
}
static void resize(T& container, size_t size) {
resizeImpl(container, size, std::is_default_constructible<TValue>{});
}
private:
static void resizeImpl(T& container, size_t size, std::true_type) {
container.resize(size);
}
static void resizeImpl(T& container, size_t newSize, std::false_type) {
const auto oldSize = size(container);
for (auto it = oldSize; it < newSize; ++it) {
container.push_back(::bitsery::Access::create<TValue>());
}
if (oldSize > newSize) {
container.erase(std::next(std::begin(container), newSize));
}
}
};
template <typename T, bool Resizable = ContainerTraits<T>::isResizable>
struct StdContainerForBufferAdapter {
using TIterator = typename T::iterator;
using TConstIterator = typename T::const_iterator;
using TValue = typename ContainerTraits<T>::TValue;
};
@@ -73,13 +88,14 @@ namespace bitsery {
auto newSize = static_cast<size_t>(container.size() * 1.5 + 128);
//make data cache friendly
newSize -= newSize % 64;//64 is cache line size
container.resize(std::max(newSize, container.capacity()));
container.resize((std::max)(newSize, container.capacity()));
}
using TIterator = typename T::iterator;
using TConstIterator = typename T::const_iterator;
using TValue = typename ContainerTraits<T>::TValue;
};
}
}
#endif //BITSERY_TRAITS_HELPER_STD_DEFAULTS_H
#endif //BITSERY_TRAITS_CORE_STD_DEFAULTS_H

View File

@@ -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,12 +149,13 @@ 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");
}
using TIterator = details::NotDefinedType;
using TConstIterator = details::NotDefinedType;
using TValue = typename ContainerTraits<T>::TValue;
};
@@ -137,6 +163,7 @@ namespace bitsery {
template <typename T, size_t N>
struct BufferAdapterTraits<T[N]> {
using TIterator = T*;
using TConstIterator = const T*;
using TValue = T;
};
@@ -144,12 +171,14 @@ namespace bitsery {
template <typename T>
struct BufferAdapterTraits<const T*> {
using TIterator = const T*;
using TConstIterator = const T*;
using TValue = T;
};
template <typename T>
struct BufferAdapterTraits<T*> {
using TIterator = T*;
using TConstIterator = const T*;
using TValue = T;
};

View File

@@ -31,9 +31,9 @@ namespace bitsery {
namespace traits {
template<typename ... TArgs>
struct ContainerTraits<std::deque<TArgs...>>
: public StdContainer<std::deque<TArgs...>, true, false> {};
template<typename T, typename Allocator>
struct ContainerTraits<std::deque<T, Allocator>>
: public StdContainer<std::deque<T, Allocator>, true, false> {};
}

View File

@@ -24,23 +24,42 @@
#ifndef BITSERY_TRAITS_STD_FORWARD_LIST_H
#define BITSERY_TRAITS_STD_FORWARD_LIST_H
#include "core/traits.h"
#include "../details/serialization_common.h"
#include <forward_list>
namespace bitsery {
namespace traits {
template<typename ... TArgs>
struct ContainerTraits<std::forward_list<TArgs...>> {
using TValue = typename std::forward_list<TArgs...>::value_type;
template<typename T, typename Allocator>
struct ContainerTraits<std::forward_list<T, Allocator>> {
using TValue = T;
static constexpr bool isResizable = true;
static constexpr bool isContiguous = false;
static size_t size(const std::forward_list<TArgs...>& container) {
static size_t size(const std::forward_list<T, Allocator>& container) {
return static_cast<size_t>(std::distance(container.begin(), container.end()));
}
static void resize(std::forward_list<TArgs...>& container, size_t size) {
static void resize(std::forward_list<T, Allocator>& container, size_t size) {
resizeImpl(container, size, std::is_default_constructible<TValue>{});
}
private:
static void resizeImpl(std::forward_list<T, Allocator>& container, size_t size, std::true_type) {
container.resize(size);
}
static void resizeImpl(std::forward_list<T, Allocator>& container, size_t newSize, std::false_type) {
const auto oldSize = size(container);
for (auto it = oldSize; it < newSize; ++it) {
container.push_front(::bitsery::Access::create<TValue>());
}
if (oldSize > newSize) {
//erase_after must have atleast one element to work
if (newSize > 0)
container.erase_after(std::next(std::begin(container), newSize-1));
else
container.clear();
}
}
};
}
}

View File

@@ -31,9 +31,9 @@ namespace bitsery {
namespace traits {
template<typename ... TArgs>
struct ContainerTraits<std::list<TArgs...>>
: public StdContainer<std::list<TArgs...>, true, false> {};
template<typename T, typename Allocator>
struct ContainerTraits<std::list<T, Allocator>>
: public StdContainer<std::list<T, Allocator>, true, false> {};
}

View File

@@ -33,18 +33,18 @@ namespace bitsery {
// specialization for string, because string is already included for std::char_traits
template<typename ... TArgs>
struct ContainerTraits<std::basic_string<TArgs...>>
:public StdContainer<std::basic_string<TArgs...>, true, true> {};
template<typename CharT, typename Traits, typename Allocator>
struct ContainerTraits<std::basic_string<CharT, Traits, Allocator>>
:public StdContainer<std::basic_string<CharT, Traits, Allocator>, true, true> {};
template <typename ... TArgs>
struct TextTraits<std::basic_string<TArgs...>> {
using TValue = typename ContainerTraits<std::basic_string<TArgs...>>::TValue;
template <typename CharT, typename Traits, typename Allocator>
struct TextTraits<std::basic_string<CharT, Traits, Allocator>> {
using TValue = typename ContainerTraits<std::basic_string<CharT, Traits, Allocator>>::TValue;
//string is automatically null-terminated
static constexpr bool addNUL = false;
//is is not 100% accurate, but for performance reasons assume that string stores text, not binary data
static size_t length(const std::basic_string<TArgs...>& str) {
static size_t length(const std::basic_string<CharT, Traits, Allocator>& str) {
return str.size();
}
};
@@ -60,9 +60,9 @@ namespace bitsery {
}
};
template<typename ... TArgs>
struct BufferAdapterTraits<std::basic_string<TArgs...>>
:public StdContainerForBufferAdapter<std::basic_string<TArgs...>> {};
template<typename CharT, typename Traits, typename Allocator>
struct BufferAdapterTraits<std::basic_string<CharT, Traits, Allocator>>
:public StdContainerForBufferAdapter<std::basic_string<CharT, Traits, Allocator>> {};
}

View File

@@ -30,18 +30,18 @@
namespace bitsery {
namespace traits {
template<typename ... TArgs>
struct ContainerTraits<std::vector<TArgs...>>
:public StdContainer<std::vector<TArgs...>, true, true> {};
template<typename T, typename Allocator>
struct ContainerTraits<std::vector<T, Allocator>>
:public StdContainer<std::vector<T, Allocator>, true, true> {};
//bool vector is not contiguous, do not copy it directly to buffer
template<typename Allocator>
struct ContainerTraits<std::vector<bool, Allocator>>
:public StdContainer<std::vector<bool, Allocator>, true, false> {};
template<typename ... TArgs>
struct BufferAdapterTraits<std::vector<TArgs...>>
:public StdContainerForBufferAdapter<std::vector<TArgs...>> {};
template<typename T, typename Allocator>
struct BufferAdapterTraits<std::vector<T, Allocator>>
:public StdContainerForBufferAdapter<std::vector<T, Allocator>> {};
}

View File

@@ -0,0 +1,6 @@
set(CTEST_PROJECT_NAME "bitsery")
set(CTEST_DROP_METHOD "http")
set(CTEST_DROP_SITE "my.cdash.org")
set(CTEST_DROP_LOCATION "/submit.php?project=bitsery")
set(CTEST_DROP_SITE_CDASH TRUE)

View File

@@ -0,0 +1,19 @@
# run from linux shell:
#$ ctest -S build.bitsery.cmake
set(CTEST_SOURCE_DIRECTORY "..") #path to bitsery root (top-level) directory
set(CTEST_BINARY_DIRECTORY "build")
set(ENV{CXXFLAGS} "--coverage")
#when using Ninja generator, ctest_coverage cannot find files...
set(CTEST_CMAKE_GENERATOR "CodeBlocks - Unix Makefiles")
set(CTEST_COVERAGE_COMMAND "gcov")
configure_file(CTestConfig.cmake ${CTEST_SOURCE_DIRECTORY}/CTestConfig.cmake)
ctest_start("Continuous")
ctest_configure(OPTIONS "-DBITSERY_BUILD_EXAMPLES=OFF;-DBITSERY_BUILD_TESTS=ON")
ctest_build()
ctest_test(BUILD ${CTEST_BINARY_DIRECTORY}/tests)
ctest_coverage()
#ctest_submit()

8
scripts/show_coverage.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/sh
BUILD_DIR=./build
TESTS_BUILD_DIR=$BUILD_DIR/tests/CMakeFiles/
COV_INFO=$TESTS_BUILD_DIR/bitsery_coverage.info
lcov --directory $TESTS_BUILD_DIR --capture --output-file $COV_INFO
lcov --extract $COV_INFO '*include/bitsery*' --output-file $COV_INFO.clean
genhtml --output-directory $TESTS_BUILD_DIR/coverage_web $COV_INFO.clean
x-www-browser $TESTS_BUILD_DIR/coverage_web/index.html

View File

@@ -20,55 +20,43 @@
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.
cmake_minimum_required(VERSION 3.2)
set(TestProjectName bitsery_tests)
project(${TestProjectName} C CXX)
cmake_minimum_required(VERSION 3.10)
project(bitsery_tests CXX)
find_package(GTest 1.8 REQUIRED)
find_package(Threads REQUIRED)
#add googletest external project
#USE_GMOCK enable gmock
#exports variables GTEST_INCLUDE_DIRS, GTEST_LIBS_DIR, GTEST_LIBNAME, GTEST_MAIN_LIBNAME
set(ExtCMakeFilesDir ${CMAKE_SOURCE_DIR}/ext)
set(UseGMock ON)
add_subdirectory(${ExtCMakeFilesDir}/gtest ${CMAKE_BINARY_DIR}/gtest)
#this helps idea to know which files are actually used
file(GLOB_RECURSE IncludeHeaders ${CMAKE_SOURCE_DIR}/include/bitsery/*.h)
# set common include folder for module
include_directories(SYSTEM ${GTestIncludeDirs})
include_directories(${CMAKE_SOURCE_DIR}/include)
if (NOT TARGET Bitsery::bitsery)
message(FATAL_ERROR "Bitsery::bitsery alias not set. Please generate CMake from bitsery root directory.")
endif()
file(GLOB TestSourceFiles ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
message(WARNING "extension tests for optional is disable for VS, because VS currenty doesn't have <optional>")
list(REMOVE_ITEM TestSourceFiles ${CMAKE_CURRENT_SOURCE_DIR}/serialization_ext_std_optional.cpp)
endif()
enable_testing()
include(${ExtCMakeFilesDir}/LinkTestLib.cmake)
FOREACH(TestFile ${TestSourceFiles})
foreach (TestFile ${TestSourceFiles})
get_filename_component(TestName ${TestFile} NAME_WE)
set(TestName TEST_${TestName})
add_executable(${TestName} ${TestFile} ${IncludeHeaders} serialization_test_utils.h)
LinkTestLib(${TestName})
set(TestName bitsery.test.${TestName})
add_executable(${TestName} ${TestFile})
target_link_libraries(${TestName} PRIVATE GTest::Main Bitsery::bitsery)
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(${TestName} PRIVATE -Wextra -Wno-missing-braces -Wpedantic -Weffc++ -Wno-c++14-extensions)
endif()
gtest_discover_tests(${TestName})
add_test(NAME ${TestName} COMMAND $<TARGET_FILE:${TestName}>)
# add_test(NAME ${TestName} COMMAND $<TARGET_FILE:${TestName}>)
endforeach()
ENDFOREACH()
#======================= setup development environment ====================
#all in one tests for code coverage
add_executable(${TestProjectName} ${TestSourceFiles})
if(CMAKE_COMPILER_IS_GNUCXX)
include(${ExtCMakeFilesDir}/CodeCoverage.cmake)
target_compile_options(${TestProjectName} PUBLIC -O0 -fprofile-arcs -ftest-coverage)
target_link_libraries(${TestProjectName} -O0 -fprofile-arcs -ftest-coverage)
setup_target_for_coverage(tests_coverage ${TestProjectName} coverage)
# get all header files for IDE (in my case Clion) and create dummy project that consumes theses files
get_directory_property(ParentDir PARENT_DIRECTORY)
if (ParentDir)
# only include when working from root project (Bitsery)
file(GLOB_RECURSE HeadersForIDE ${ParentDir}/include/bitsery/*.h)
# create dummy target IDE
file(WRITE ${CMAKE_BINARY_DIR}/dummy_for_ide.cpp "//generated by CMake to create dummy target with all includes for IDE.")
add_library(bitsery.dummy_for_ide ${CMAKE_BINARY_DIR}/dummy_for_ide.cpp)
# add headers so IDE correctly show them
target_sources(bitsery.dummy_for_ide PRIVATE ${HeadersForIDE} serialization_test_utils.h)
target_link_libraries(bitsery.dummy_for_ide PRIVATE GTest::Main Bitsery::bitsery)
endif()
LinkTestLib(${TestProjectName})

View File

@@ -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));
}

View File

@@ -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.b = 0xBBCCDDEE;
src.c = 0xCCDD;
src.d = 0xDD;
src.e = 0xEE;
src.a = static_cast<int64_t>(0x1122334455667788u);
src.b = 0xBBCCDDEEu;
src.c = static_cast<int16_t>(0xCCDDu);
src.d = static_cast<uint8_t>(0xDDu);
src.e = static_cast<int8_t>(0xEEu);
//fill expected result after swap
IntegralTypes resInv{};
resInv.a = 0x8877665544332211;
resInv.b = 0xEEDDCCBB;
resInv.c = 0xDDCC;
resInv.d = 0xDD;
resInv.e = 0xEE;
resInv.a = static_cast<int64_t>(0x8877665544332211u);
resInv.b = 0xEEDDCCBBu;
resInv.c = static_cast<int16_t>(0xDDCCu);
resInv.d = static_cast<uint8_t>(0xDDu);
resInv.e = static_cast<int8_t>(0xEEu);
//create and write to buffer
Buffer buf{};
@@ -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;

View File

@@ -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;

View File

@@ -153,3 +153,28 @@ TEST(DataReading, WhenReaderHasErrorsAllOperationsReadsReturnZero) {
EXPECT_THAT(r2[1], Eq(0u));
EXPECT_THAT(r3, Eq(0u));
}
TEST(DataReading, ConstBufferAllAdapters) {
//create and write to buffer
uint16_t data = 7549;
Buffer bufWrite{};
Writer bw{bufWrite};
bw.writeBytes<2>(data);
bw.flush();
const Buffer buf{bufWrite};
//read from buffer
using Adapter1 = bitsery::InputBufferAdapter<const Buffer>;
using Adapter2 = bitsery::UnsafeInputBufferAdapter<const Buffer>;
bitsery::AdapterReader<Adapter1, bitsery::DefaultConfig> r1{Adapter1{buf.begin(), buf.end()}};
bitsery::AdapterReader<Adapter2, bitsery::DefaultConfig> r2{Adapter2{buf.begin(), buf.end()}};
uint16_t res1{};
r1.readBytes<2>(res1);
uint16_t res2{};
r2.readBytes<2>(res2);
EXPECT_THAT(res1, Eq(data));
EXPECT_THAT(res2, Eq(data));
}

View File

@@ -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));
}

View File

@@ -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;

View File

@@ -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,21 @@
#include <bitsery/flexible/unordered_map.h>
#include <bitsery/flexible/set.h>
#include <bitsery/flexible/unordered_set.h>
#include <bitsery/flexible/memory.h>
#include <bitsery/flexible/chrono.h>
#if __cplusplus > 201402L
#include <bitsery/flexible/tuple.h>
#include <bitsery/flexible/variant.h>
#else
#if defined(_MSC_VER)
#pragma message("tuple and variant only works with c++17")
#else
#warning "tuple and variant only works with c++17"
#endif
#endif
#include <gmock/gmock.h>
#include "serialization_test_utils.h"
using testing::Eq;
@@ -44,18 +56,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 +81,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 +95,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 +116,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 +128,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 +143,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 +174,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 +194,6 @@ TEST(FlexibleSyntax, CStyleArray) {
EXPECT_THAT(r1, ::testing::ContainerEq(t1));
}
TEST(FlexibleSyntax, StdString) {
std::string t1{"my nice string"};
std::string t2{};
@@ -195,56 +205,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 +259,6 @@ TEST(FlexibleSyntax, StdQueue) {
EXPECT_THAT(procArchive(t1), Eq(t1));
EXPECT_THAT(procArchiveWithMaxSize(t1), Eq(t1));
}
TEST(FlexibleSyntax, StdPriorityQueue) {
@@ -272,7 +276,6 @@ TEST(FlexibleSyntax, StdPriorityQueue) {
r1.pop();
t1.pop();
}
}
TEST(FlexibleSyntax, StdStack) {
@@ -282,13 +285,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 +298,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 +308,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 +317,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 +377,54 @@ 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, StdDuration) {
std::chrono::duration<int64_t, std::milli> t1{54654};
EXPECT_TRUE(procArchive(t1) == t1);
}
TEST(FlexibleSyntax, StdTimePoint) {
using Duration = std::chrono::duration<double, std::milli>;
using TP = std::chrono::time_point<std::chrono::system_clock, Duration>;
TP data{Duration{874656.4798}};
EXPECT_TRUE(procArchive(data) == data);
}
#if __cplusplus > 201402L
TEST(FlexibleSyntax, StdTuple) {
std::tuple<int, std::string, std::vector<char>> t1{5,"hello hello", {'A','B','C'}};
EXPECT_TRUE(procArchive(t1) == t1);
}
TEST(FlexibleSyntax, StdVariant) {
std::variant<float, std::string, std::chrono::milliseconds> t1{std::string("hello hello")};
EXPECT_TRUE(procArchive(t1) == t1);
}
#endif
TEST(FlexibleSyntax, NestedTypes) {
std::unordered_map<std::string, std::vector<std::string>> t1;

View File

@@ -0,0 +1,346 @@
//MIT License
//
//Copyright (c) 2019 Mindaugas Vinkelis
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.
#include <forward_list>
#include <bitsery/traits/forward_list.h>
#include <bitsery/ext/std_set.h>
#include <bitsery/ext/std_map.h>
#include <bitsery/ext/pointer.h>
#include <bitsery/ext/std_smart_ptr.h>
#include <gmock/gmock.h>
#include "serialization_test_utils.h"
using testing::ContainerEq;
using testing::Eq;
//forward declare, for testing with std::unordered_map
class HasherForNonDefaultConstructible;
class NonDefaultConstructible {
int32_t i{0};
friend class HasherForNonDefaultConstructible;
friend class bitsery::Access;
NonDefaultConstructible() = default;
template <typename S>
void serialize(S& s) {
s.value4b(i);
}
public:
explicit NonDefaultConstructible(int32_t v):i{v} {}
bool operator == (const NonDefaultConstructible& other) const {
return i == other.i;
}
bool operator < (const NonDefaultConstructible& other) const {
return i < other.i;
}
};
class HasherForNonDefaultConstructible {
public:
size_t operator()(const NonDefaultConstructible& o) const {
return std::hash<int32_t>()(o.i);
}
};
TEST(DeserializeNonDefaultConstructible, Container) {
SerializationContext ctx{};
std::vector<NonDefaultConstructible> data{};
data.emplace_back(1);
data.emplace_back(2);
data.emplace_back(3);
std::vector<NonDefaultConstructible> res{};
ctx.createSerializer().container(data, 10);
ctx.createDeserializer().container(res, 10);
EXPECT_THAT(res, ContainerEq(data));
}
//this test is here, because when object is not constructible we cannot simple "resize" container
TEST(DeserializeNonDefaultConstructible, ResultContainerShouldShrink) {
SerializationContext ctx{};
std::vector<NonDefaultConstructible> data{};
data.emplace_back(1);
std::vector<NonDefaultConstructible> res{};
res.emplace_back(2);
res.emplace_back(3);
ctx.createSerializer().container(data, 10);
ctx.createDeserializer().container(res, 10);
EXPECT_THAT(res, ContainerEq(data));
}
TEST(DeserializeNonDefaultConstructible, ResultStdForwardListShouldShrink) {
// forward list doesn't have .erase function, bet has erase_after
// in this case, if new size is 0 it must call clear, so we need to check two cases
{
// 1) when result should have more than 0 elements
SerializationContext ctx{};
std::forward_list<NonDefaultConstructible> data{};
data.push_front(NonDefaultConstructible{1});
std::forward_list<NonDefaultConstructible> res{};
res.push_front(NonDefaultConstructible{21});
res.push_front(NonDefaultConstructible{14});
ctx.createSerializer().container(data, 10);
ctx.createDeserializer().container(res, 10);
auto resIt = res.begin();
for (auto it = data.begin(); it != data.end(); ++it, ++resIt) {
EXPECT_THAT(*resIt, Eq(*it));
}
EXPECT_THAT(resIt, Eq(res.end()));
}
{
// 1) when result should have 0 elements
SerializationContext ctx{};
std::forward_list<NonDefaultConstructible> data{};
std::forward_list<NonDefaultConstructible> res{};
res.push_front(NonDefaultConstructible{1});
res.push_front(NonDefaultConstructible{14});
ctx.createSerializer().container(data, 10);
ctx.createDeserializer().container(res, 10);
EXPECT_THAT(res.begin(), Eq(res.end()));
}
{
// also check if correctly expands if source is bigger than destination
SerializationContext ctx{};
std::forward_list<NonDefaultConstructible> data{};
data.push_front(NonDefaultConstructible{1});
data.push_front(NonDefaultConstructible{14});
std::forward_list<NonDefaultConstructible> res{};
ctx.createSerializer().container(data, 10);
ctx.createDeserializer().container(res, 10);
auto resIt = res.begin();
for (auto it = data.begin(); it != data.end(); ++it, ++resIt) {
EXPECT_THAT(*resIt, Eq(*it));
}
EXPECT_THAT(resIt, Eq(res.end()));
}
}
TEST(DeserializeNonDefaultConstructible, StdSet) {
SerializationContext ctx{};
std::set<NonDefaultConstructible> data;
data.insert(NonDefaultConstructible{1});
data.insert(NonDefaultConstructible{2});
std::set<NonDefaultConstructible> res{};
data.insert(NonDefaultConstructible{3});
ctx.createSerializer().ext(data, bitsery::ext::StdSet{10});
ctx.createDeserializer().ext(res, bitsery::ext::StdSet{10});
EXPECT_THAT(res, ContainerEq(data));
}
TEST(DeserializeNonDefaultConstructible, StdMap) {
SerializationContext ctx{};
std::unordered_map<NonDefaultConstructible, NonDefaultConstructible, HasherForNonDefaultConstructible> data;
data.emplace(NonDefaultConstructible{2}, NonDefaultConstructible{3});
std::unordered_map<NonDefaultConstructible, NonDefaultConstructible, HasherForNonDefaultConstructible> res{};
data.emplace(NonDefaultConstructible{2}, NonDefaultConstructible{3});
data.emplace(NonDefaultConstructible{4}, NonDefaultConstructible{4});
auto& ser = ctx.createSerializer();
ser.ext(data, bitsery::ext::StdMap{10},[&ser](NonDefaultConstructible& key, NonDefaultConstructible& value) {
ser.object(key);
ser.object(value);
});
auto& des = ctx.createDeserializer();
des.ext(res, bitsery::ext::StdMap{10},[&des](NonDefaultConstructible& key, NonDefaultConstructible& value) {
des.object(key);
des.object(value);
});
EXPECT_THAT(res, ContainerEq(data));
}
struct NonPolymorphicPointers {
NonDefaultConstructible* pp;
std::unique_ptr<NonDefaultConstructible> up;
std::shared_ptr<NonDefaultConstructible> sp;
std::weak_ptr<NonDefaultConstructible> wp;
};
template <typename S>
void serialize(S& s, NonPolymorphicPointers& o) {
s.ext(o.pp, bitsery::ext::PointerOwner{});
s.ext(o.up, bitsery::ext::StdSmartPtr{});
s.ext(o.sp, bitsery::ext::StdSmartPtr{});
s.ext(o.wp, bitsery::ext::StdSmartPtr{});
}
TEST(DeserializeNonDefaultConstructible, NonPolymorphicPointerAndSmartPointer) {
using SerContext = BasicSerializationContext<bitsery::DefaultConfig, bitsery::ext::PointerLinkingContext>;
SerContext ctx{};
NonPolymorphicPointers data{};
data.pp = new NonDefaultConstructible{3};
data.up = std::unique_ptr<NonDefaultConstructible>(new NonDefaultConstructible{54});
data.sp = std::shared_ptr<NonDefaultConstructible>(new NonDefaultConstructible{-481});
data.wp = data.sp;
NonPolymorphicPointers res{};
bitsery::ext::PointerLinkingContext plctx1{};
ctx.createSerializer(&plctx1).object(data);
ctx.createDeserializer(&plctx1).object(res);
EXPECT_THAT(*res.pp, Eq(*data.pp));
delete res.pp;
delete data.pp;
EXPECT_THAT(*res.up, Eq(*data.up));
EXPECT_THAT(*res.sp, Eq(*data.sp));
EXPECT_THAT(*(res.wp.lock()), Eq(*(data.wp.lock())));
}
class PolymorphicNDCBase {
public:
virtual ~PolymorphicNDCBase() = 0;
template <typename S>
void serialize(S& ) {}
};
PolymorphicNDCBase::~PolymorphicNDCBase() = default;
class PolymorphicNDC1:public PolymorphicNDCBase {
int8_t i{};
friend class bitsery::Access;
template <typename S>
void serialize(S& s) {
s.value1b(i);
}
public:
PolymorphicNDC1() = default;
PolymorphicNDC1(int8_t v):i{v} {}
bool operator == (const PolymorphicNDC1& other) const {
return i == other.i;
}
};
class PolymorphicNDC2:public PolymorphicNDCBase {
uint16_t ui{};
friend class bitsery::Access;
template <typename S>
void serialize(S& s) {
s.value2b(ui);
}
public:
PolymorphicNDC2() = default;
PolymorphicNDC2(uint16_t v):ui{v} {}
bool operator == (const PolymorphicNDC2& other) const {
return ui == other.ui;
}
};
namespace bitsery {
namespace ext {
template<>
struct PolymorphicBaseClass<PolymorphicNDCBase> : PolymorphicDerivedClasses<PolymorphicNDC1, PolymorphicNDC2> {
};
}
}
struct PolymorphicPointers {
PolymorphicNDCBase* pp;
std::unique_ptr<PolymorphicNDCBase> up;
std::shared_ptr<PolymorphicNDCBase> sp;
std::weak_ptr<PolymorphicNDCBase> wp;
};
template <typename S>
void serialize(S& s, PolymorphicPointers& o) {
s.ext(o.pp, bitsery::ext::PointerOwner{});
s.ext(o.up, bitsery::ext::StdSmartPtr{});
s.ext(o.sp, bitsery::ext::StdSmartPtr{});
s.ext(o.wp, bitsery::ext::StdSmartPtr{});
}
TEST(DeserializeNonDefaultConstructible, PolymorphicPointerAndSmartPointer) {
using TContext = std::tuple<bitsery::ext::PointerLinkingContext, bitsery::ext::PolymorphicContext<bitsery::ext::StandardRTTI>>;
using SerContext = BasicSerializationContext<bitsery::DefaultConfig, TContext>;
SerContext ctx{};
PolymorphicPointers data{};
data.pp = new PolymorphicNDC1{-4};
data.up = std::unique_ptr<PolymorphicNDCBase>(new PolymorphicNDC2{54});
data.sp = std::shared_ptr<PolymorphicNDCBase>(new PolymorphicNDC1{15});
data.wp = data.sp;
PolymorphicPointers res{};
TContext serCtx{};
std::get<1>(serCtx).registerBasesList<typename SerContext::TSerializer>(bitsery::ext::PolymorphicClassesList<PolymorphicNDCBase>{});
TContext desCtx{};
std::get<1>(desCtx).registerBasesList<typename SerContext::TDeserializer>(bitsery::ext::PolymorphicClassesList<PolymorphicNDCBase>{});
ctx.createSerializer(&serCtx).object(data);
ctx.createDeserializer(&desCtx).object(res);
auto respp = dynamic_cast<PolymorphicNDC1*>(res.pp);
auto resup = dynamic_cast<PolymorphicNDC2*>(res.up.get());
auto ressp = dynamic_cast<PolymorphicNDC1*>(res.sp.get());
auto reswp = dynamic_cast<PolymorphicNDC1*>(res.wp.lock().get());
auto datapp = dynamic_cast<PolymorphicNDC1*>(data.pp);
auto dataup = dynamic_cast<PolymorphicNDC2*>(data.up.get());
auto datasp = dynamic_cast<PolymorphicNDC1*>(data.sp.get());
auto datawp = dynamic_cast<PolymorphicNDC1*>(data.wp.lock().get());
EXPECT_THAT(respp, ::testing::Ne(nullptr));
EXPECT_THAT(resup, ::testing::Ne(nullptr));
EXPECT_THAT(ressp, ::testing::Ne(nullptr));
EXPECT_THAT(reswp, ::testing::Ne(nullptr));
EXPECT_THAT(*respp, Eq(*datapp));
delete res.pp;
delete data.pp;
EXPECT_THAT(*resup, Eq(*dataup));
EXPECT_THAT(*ressp, Eq(*datasp));
EXPECT_THAT(*reswp, Eq(*datawp));
}

View File

@@ -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;

View File

@@ -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),);

View File

@@ -0,0 +1,161 @@
//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());
}
TEST(SerializationContext, WhenBitPackingIsEnabledThenInternalContextIsMovedToNewInstanceAndMovedBackAfterwards) {
Buffer buf{};
using Ser = SerializerConfigWithContext<void, int>;
using BPSer = typename Ser::BPEnabledType;
using Des = DeserializerConfigWithContext<void, int>;
using BPDes = typename Des::BPEnabledType;
Ser ser{buf, nullptr};
*ser.context<int>() = 1;
EXPECT_THAT(*ser.context<int>(), Eq(1));
ser.enableBitPacking([](BPSer& s) {
EXPECT_THAT(*s.context<int>(), Eq(1));
*s.context<int>() = 2;
});
EXPECT_THAT(*ser.context<int>(), Eq(2));
Des des{InputAdapter{buf.begin(), 1}, nullptr};
*des.context<int>() = 3;
EXPECT_THAT(*des.context<int>(), Eq(3));
des.enableBitPacking([](BPDes& d) {
EXPECT_THAT(*d.context<int>(), Eq(3));
*d.context<int>() = 4;
});
EXPECT_THAT(*des.context<int>(), Eq(4));
}

View File

@@ -0,0 +1,240 @@
//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/compact_value.h>
#include <gmock/gmock.h>
#include "serialization_test_utils.h"
#include <bitsery/traits/array.h>
#include <bitset>
#include <chrono>
using testing::Eq;
using bitsery::ext::CompactValue;
using bitsery::ext::CompactValueAsObject;
using bitsery::EndiannessType;
// helper function, that gets value filled with specified number of bits
template <typename TValue>
TValue getValue(bool isPositive, size_t significantBits) {
TValue v = isPositive ? 0 : -1;
if (significantBits == 0)
return v;
using TUnsigned = typename std::make_unsigned<TValue>::type;
TUnsigned mask = {};
mask = ~mask; // invert shiftByBits
auto shiftBy = bitsery::details::BitsSize<TValue>::value - significantBits;
mask >>= shiftBy;
//cast to unsigned when applying mask
return (TUnsigned)v ^ mask;
}
// helper function, that serialize and return deserialized value
template <typename TSerContext, typename TValue>
std::pair<TValue, size_t> serializeAndGetDeserialized(TValue data) {
TSerContext ctx;
TValue res{};
ctx.createSerializer().template ext<sizeof(TValue)>(data, CompactValue{});
ctx.createDeserializer().template ext<sizeof(TValue)>(res, CompactValue{});
return {res, ctx.getBufferSize()};
}
struct LittleEndianConfig: public bitsery::DefaultConfig {
static constexpr EndiannessType NetworkEndianness = EndiannessType::LittleEndian;
};
struct BigEndianConfig: public bitsery::DefaultConfig {
static constexpr EndiannessType NetworkEndianness = EndiannessType::BigEndian;
};
template <typename TValue, bool isPositiveNr, typename TConfig>
struct TC {
static_assert(isPositiveNr || std::is_signed<TValue>::value, "");
using Value = TValue;
using Config = TConfig;
bool isPositive = isPositiveNr;
};
template<typename T>
class SerializeExtensionCompactValueCorrectness : public testing::Test {
public:
using TestCase = T;
};
using AllValueSizesTestCases = ::testing::Types<
TC<uint8_t, true, LittleEndianConfig>,
TC<uint16_t, true, LittleEndianConfig>,
TC<uint32_t, true, LittleEndianConfig>,
TC<uint64_t, true, LittleEndianConfig>,
TC<int8_t, true, LittleEndianConfig>,
TC<int16_t, true, LittleEndianConfig>,
TC<int32_t, true, LittleEndianConfig>,
TC<int64_t, true, LittleEndianConfig>,
TC<int8_t, false, LittleEndianConfig>,
TC<int16_t, false, LittleEndianConfig>,
TC<int32_t, false, LittleEndianConfig>,
TC<int64_t, false, LittleEndianConfig>,
TC<uint8_t, true, BigEndianConfig>,
TC<uint16_t, true, BigEndianConfig>,
TC<uint32_t, true, BigEndianConfig>,
TC<uint64_t, true, BigEndianConfig>,
TC<int8_t, true, BigEndianConfig>,
TC<int16_t, true, BigEndianConfig>,
TC<int32_t, true, BigEndianConfig>,
TC<int64_t, true, BigEndianConfig>,
TC<int8_t, false, BigEndianConfig>,
TC<int16_t, false, BigEndianConfig>,
TC<int32_t, false, BigEndianConfig>,
TC<int64_t, false, BigEndianConfig>
>;
TYPED_TEST_CASE(SerializeExtensionCompactValueCorrectness, AllValueSizesTestCases);
TYPED_TEST(SerializeExtensionCompactValueCorrectness, TestDifferentSizeValues) {
using TCase = typename TestFixture::TestCase;
using TValue = typename TCase::Value;
TCase tc{};
for (auto i = 0u; i < bitsery::details::BitsSize<TValue>::value + 1; ++i) {
auto data = getValue<TValue>(tc.isPositive, i);
auto res = serializeAndGetDeserialized<BasicSerializationContext<typename TCase::Config, void>>(data);
EXPECT_THAT(res.first, Eq(data));
}
}
// this stucture will contain test data and result, as type paramters
template <typename TValue, bool isPositiveNr, size_t significantBits, size_t resultBytes>
struct SizeTC {
static_assert(isPositiveNr || std::is_signed<TValue>::value, "");
static_assert(bitsery::details::BitsSize<TValue>::value >= significantBits, "");
using Value = TValue;
bool isPositive = isPositiveNr;
size_t fillBits = significantBits;
size_t bytesCount = resultBytes;
};
template<typename T>
class SerializeExtensionCompactValueRequiredBytes : public testing::Test {
public:
using TestCase = T;
};
using RequiredBytesTestCases = ::testing::Types<
//1 byte always writes to 1 byte
SizeTC<uint8_t, true, 0,1>,
SizeTC<uint8_t, true, 8,1>,
SizeTC<int8_t, false, 0,1>,
SizeTC<int8_t, true, 8,1>,
//2 byte, +1 byte after 15 significant bits
SizeTC<uint16_t, true, 7,1>,
SizeTC<uint16_t, true, 8,2>,
SizeTC<uint16_t, true, 14,2>,
SizeTC<uint16_t, true, 15,3>,
//2 byte, +1 byte after 15-1 significant bits (1 bit for sign)
SizeTC<int16_t, true, 6,1>,
SizeTC<int16_t, false, 7,2>,
SizeTC<int16_t, true, 13,2>,
SizeTC<int16_t, false, 14,3>,
//4 byte, +1 byte after 29 significant bits
SizeTC<uint32_t, true, 14,2>,
SizeTC<uint32_t, true, 21,3>,
SizeTC<uint32_t, true, 28,4>,
SizeTC<uint32_t, true, 29,5>,
SizeTC<uint32_t, true, 32,5>,
//4 byte
SizeTC<int32_t, true, 13,2>,
SizeTC<int32_t, false, 20,3>,
SizeTC<int32_t, true, 27,4>,
SizeTC<int32_t, false, 28,5>,
SizeTC<int32_t, true, 31,5>,
//8 byte, +1 byte after 57 significant bits, or +2 byte when all bits are significant
SizeTC<uint64_t, true, 28,4>,
SizeTC<uint64_t, true, 35,5>,
SizeTC<uint64_t, true, 42,6>,
SizeTC<uint64_t, true, 49,7>,
SizeTC<uint64_t, true, 56,8>,
SizeTC<uint64_t, true, 57,9>,
SizeTC<uint64_t, true, 63,9>,
SizeTC<uint64_t, true, 64,10>,
//8 byte,
SizeTC<int64_t, true, 27,4>,
SizeTC<int64_t, false, 34,5>,
SizeTC<int64_t, true, 41,6>,
SizeTC<int64_t, false, 48,7>,
SizeTC<int64_t, true, 55,8>,
SizeTC<int64_t, false, 56,9>,
SizeTC<int64_t, true, 62,9>,
SizeTC<int64_t, false, 63,10>
>;
TYPED_TEST_CASE(SerializeExtensionCompactValueRequiredBytes, RequiredBytesTestCases);
TYPED_TEST(SerializeExtensionCompactValueRequiredBytes, Test) {
using TCase = typename TestFixture::TestCase;
using TValue = typename TCase::Value;
TCase tc{};
TValue data = getValue<TValue>(tc.isPositive, tc.fillBits);
auto res = serializeAndGetDeserialized<SerializationContext>(data);
EXPECT_THAT(res.first, Eq(data));
EXPECT_THAT(res.second, tc.bytesCount);
}
enum b1En: uint8_t {
A,B,C,D=54,E
};
enum class b8En: int64_t {
A=-874987489,B,C=0,D,E=489748978, F,G
};
TEST(SerializeExtensionCompactValueEnum, TestEnums) {
auto d1 = b1En::E;
auto d2 = b8En::B;
auto d3 = b8En::F;
EXPECT_THAT(serializeAndGetDeserialized<SerializationContext>(d1).first, Eq(d1));
EXPECT_THAT(serializeAndGetDeserialized<SerializationContext>(d2).first, Eq(d2));
EXPECT_THAT(serializeAndGetDeserialized<SerializationContext>(d3).first, Eq(d3));
}
TEST(SerializeExtensionCompactValueAsObjectDeserializeOverflow, TestEnums) {
SerializationContext ctx;
auto data = getValue<uint32_t >(true, 17);
uint16_t res{};
auto& ser = ctx.createSerializer();
ser.ext(data, CompactValueAsObject{});
auto& des = ctx.createDeserializer();
des.ext(res, CompactValueAsObject{});
auto& rd = bitsery::AdapterAccess::getReader(des);
EXPECT_THAT(data, ::testing::Ne(res));
EXPECT_THAT(rd.error(), Eq(bitsery::ReaderError::DataOverflow));
}

View File

@@ -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});
});

View File

@@ -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(1u));
ctx.bw->flush();
EXPECT_THAT(ctx.getBufferSize(), Gt(0));
EXPECT_THAT(ctx.getBufferSize(), Gt(1u));
}
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);

View 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));
}

View 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));
}

Some files were not shown because too many files have changed in this diff Show More