mirror of
https://github.com/fraillt/bitsery.git
synced 2026-06-19 05:39:03 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b1dc3bcfa |
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,3 +1,31 @@
|
||||
# [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 serializers is typesafe.
|
||||
Also for better pointers support, added posibility to have multiple types in context with *std::tuple*.
|
||||
E.g. when using pointers 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
|
||||
|
||||
21
README.md
21
README.md
@@ -14,14 +14,15 @@ All cross-platform requirements are enforced at compile time, so serialized data
|
||||
|
||||
* Cross-platform compatible.
|
||||
* Optimized for speed and space.
|
||||
* Allows flexible or/and verbose syntax for better serialization control.
|
||||
* No code generation required: no IDL or metadata, just use your types directly.
|
||||
* Runtime error checking on deserialization.
|
||||
* Supports forward/backward compatibility for your types.
|
||||
* 2-in-1 declarative control flow, same code for serialization and deserialization.
|
||||
* Allows fine-grained bit-level serialization control.
|
||||
* Can read/write from any source: stream (file, network stream. etc... ), or buffer (vector, c-array, etc...).
|
||||
* Don't pay for what you don't use! - customize your serialization via **extensions**. Some notable *extensions* allow:
|
||||
* forward/backward compatibility for your types.
|
||||
* raw pointers (no polymorphism yet).
|
||||
* fine-grained bit-level serialization control.
|
||||
* Easily extendable for any type.
|
||||
* Allows flexible or/and verbose syntax for better serialization control.
|
||||
* Configurable endianess support.
|
||||
* No macros.
|
||||
|
||||
@@ -31,12 +32,12 @@ Look at the numbers and features list, and decide yourself.
|
||||
|
||||
| | binary size | data size | serialize | deserialize |
|
||||
|------------------------------|-------------|-----------|-------------|-------------|
|
||||
| **test_bitsery** | 64704 | **7565** | **1229 ms** | **1086 ms** |
|
||||
| **test_bitsery_compression** | 64880 | **4784** | **1641 ms** | **2462 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)*
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ Library design:
|
||||
* `extending library functionality`
|
||||
* `errors handling`
|
||||
* `forward/backward compatibility via Growable extension`
|
||||
* `pointers`
|
||||
|
||||
|
||||
Core Serializer/Deserializer functions (alphabetical order):
|
||||
@@ -24,6 +25,9 @@ Core Serializer/Deserializer functions (alphabetical order):
|
||||
Serializer/Deserializer extensions via `ext` method (alphabetical order):
|
||||
* `Entropy`
|
||||
* `Growable`
|
||||
* `PointerOwner`
|
||||
* `PointerObserver`
|
||||
* `ReferencedByPointer`
|
||||
* `StdMap`
|
||||
* `StdOptional`
|
||||
* `StdQueue`
|
||||
@@ -52,7 +56,7 @@ Input adapters (buffer and stream) functions:
|
||||
Output adapters (buffer and stream) functions:
|
||||
* `write`
|
||||
* `flush`
|
||||
* `writtenBytesCount`
|
||||
* `writtenBytesCount` (buffer adapter only)
|
||||
|
||||
|
||||
Tips and tricks:
|
||||
|
||||
@@ -41,9 +41,9 @@ But do it on your own risk, and static assert using *assertFundamentalTypeSizes*
|
||||
It also doesn't serialize any type information, all information needed is writen in your code!
|
||||
* **No code generation required: no IDL or metadata** since it doesn't support any other formats except binary, it doesn't need any metadata.
|
||||
* **Runtime error checking on deserialization** library designed to be save with untrusted network data, that's why all overloads that work on containers has *maxSize* value, unless container is static size like *std::array*, this way bitsery ensures that no malicious data crash you.
|
||||
* **Supports forward/backward compatibility for your types** library has optional forward/backward compatibility for types implemented in *BasicBufferReader/BasicBufferWriter* by allowing to have inner data sessions in inside buffer.
|
||||
* **Supports forward/backward compatibility for your types** library has optional forward/backward compatibility for types implemented in *AdapterReader/Writer* by allowing to have inner data sessions inside buffer.
|
||||
This is the only functionality that requires dynamic memory allocation.
|
||||
*Glowable* extension use these sessions to add compatibility support for your types, in most basic form.
|
||||
*Growable* extension use these sessions to add compatibility support for your types, in most basic form.
|
||||
You can implement your own extensions if you want to be able to add default values.
|
||||
* **2-in-1 declarative control flow, same code for serialization and deserialization.** only one function to define, for serialization and deserialization in same manner as *cereal* does.
|
||||
It might be handy to have separate *load* and *save* functions, but Bitsery explicitly doesn't support it, to avoid any serialization deserialization divergence, because it is very hard to catch an errors if you make a bug in one of these functions.
|
||||
@@ -67,5 +67,3 @@ To use same container for buffer writing/reading add specialization to *BufferAd
|
||||
You want to customize serialization flow - use extensions, only two methods to define, and *ExtensionTraits* to further customize usage.
|
||||
* **Configurable endianess support.** default is *Little Endian*, but if your primary target is PowerPC architecture, eg. PlayStation3, just change your configuration to be *Big Endian*.
|
||||
* **No macros.** Not so much to say, if you are like me, then it's a feature :)
|
||||
|
||||
*project for performance benchmark will be added to separate github project, i'll give you a link to it when its done.*
|
||||
@@ -6,12 +6,6 @@
|
||||
|
||||
#include <bitsery/ext/value_range.h>
|
||||
|
||||
//defines game mode parameters
|
||||
struct GameMode {
|
||||
uint32_t maxMonsters;
|
||||
uint32_t damageRange[2];
|
||||
};
|
||||
|
||||
namespace MyTypes {
|
||||
|
||||
struct Monster {
|
||||
@@ -39,12 +33,16 @@ namespace MyTypes {
|
||||
|
||||
template<typename S>
|
||||
void serialize(S& s, GameState &o) {
|
||||
auto mode = static_cast<GameMode*>(s.context());
|
||||
//we know max number of monsters from context
|
||||
s.container(o.monsters, mode->maxMonsters, [&s, mode] (Monster& m) {
|
||||
//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{mode->damageRange[0], mode->damageRange[1]};
|
||||
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);
|
||||
@@ -61,24 +59,37 @@ using namespace bitsery;
|
||||
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>>;
|
||||
|
||||
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 game mode
|
||||
//this store game mode parameters
|
||||
GameMode mode{};
|
||||
mode.maxMonsters = 4;
|
||||
mode.damageRange[0] = 100;
|
||||
mode.damageRange[1] = 1000;
|
||||
//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
|
||||
Serializer<OutputAdapter> ser{buffer, &mode};
|
||||
BasicSerializer<AdapterWriter<OutputAdapter, bitsery::DefaultConfig>, Context> ser{buffer, &ctx};
|
||||
ser.object(data);
|
||||
|
||||
auto& w = AdapterAccess::getWriter(ser);
|
||||
@@ -86,7 +97,7 @@ int main() {
|
||||
auto writtenSize = w.writtenBytesCount();
|
||||
|
||||
MyTypes::GameState res{};
|
||||
Deserializer <InputAdapter> des { InputAdapter{buffer.begin(), writtenSize}, &mode};
|
||||
BasicDeserializer <AdapterReader<InputAdapter, bitsery::DefaultConfig>, Context> des { InputAdapter{buffer.begin(), writtenSize}, &ctx};
|
||||
des.object(res);
|
||||
auto& r = AdapterAccess::getReader(des);
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ 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);
|
||||
|
||||
159
examples/raw_pointers.cpp
Normal file
159
examples/raw_pointers.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
#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;
|
||||
|
||||
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
|
||||
s.ext4b(pi1, PointerOwner{});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -55,6 +55,7 @@ namespace bitsery {
|
||||
using TIterator = typename BufferIterators<Buffer>::TIterator;
|
||||
using TValue = typename traits::BufferAdapterTraits<Buffer>::TValue;
|
||||
static_assert(details::IsDefined<TValue>::value, "Please define BufferAdapterTraits or include from <bitsery/traits/...>");
|
||||
static_assert(traits::ContainerTraits<Buffer>::isContiguous, "BufferAdapter only works with contiguous containers");
|
||||
|
||||
InputBufferAdapter(TIterator begin, TIterator end): BufferIterators<Buffer>(begin, end)
|
||||
{
|
||||
@@ -102,17 +103,18 @@ namespace bitsery {
|
||||
};
|
||||
|
||||
|
||||
template<typename Container>
|
||||
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(traits::ContainerTraits<Buffer>::isContiguous, "BufferAdapter only works with contiguous containers");
|
||||
|
||||
OutputBufferAdapter(Container &buffer)
|
||||
: _buffer{buffer}
|
||||
OutputBufferAdapter(Buffer &buffer)
|
||||
: _buffer{std::addressof(buffer)}
|
||||
{
|
||||
|
||||
init(TResizable{});
|
||||
@@ -128,13 +130,13 @@ 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;
|
||||
Buffer* _buffer;
|
||||
TIterator _outIt{};
|
||||
TIterator _end{};
|
||||
|
||||
@@ -144,11 +146,11 @@ 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) {
|
||||
@@ -171,12 +173,12 @@ namespace bitsery {
|
||||
_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{});
|
||||
}
|
||||
@@ -186,8 +188,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) {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#define BITSERY_ADAPTERS_DYNAMIC_STREAM_H
|
||||
|
||||
#include "../details/adapter_common.h"
|
||||
#include "../traits/core/traits.h"
|
||||
#include "../traits/array.h"
|
||||
#include <ios>
|
||||
|
||||
|
||||
@@ -38,28 +38,28 @@ 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);
|
||||
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;
|
||||
}
|
||||
@@ -68,7 +68,7 @@ namespace bitsery {
|
||||
}
|
||||
|
||||
private:
|
||||
std::basic_ios<TChar, CharTraits>& _ios;
|
||||
std::basic_ios<TChar, CharTraits>* _ios;
|
||||
};
|
||||
|
||||
template <typename TChar, typename CharTraits>
|
||||
@@ -77,30 +77,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>
|
||||
@@ -117,11 +216,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
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
#define BITSERY_BITSERY_H
|
||||
|
||||
#define BITSERY_MAJOR_VERSION 4
|
||||
#define BITSERY_MINOR_VERSION 0
|
||||
#define BITSERY_PATCH_VERSION 1
|
||||
#define BITSERY_MINOR_VERSION 1
|
||||
#define BITSERY_PATCH_VERSION 0
|
||||
|
||||
#define BITSERY_QUOTE_MACRO(name) #name
|
||||
#define BITSERY_BUILD_VERSION_STR(major,minor, patch) \
|
||||
|
||||
@@ -31,18 +31,18 @@
|
||||
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>;
|
||||
|
||||
|
||||
template <typename ReaderParam>
|
||||
explicit BasicDeserializer(ReaderParam&& r, void* context = nullptr)
|
||||
explicit BasicDeserializer(ReaderParam&& r, TContext* context = nullptr)
|
||||
: _reader{std::forward<ReaderParam>(r)},
|
||||
_context{context}
|
||||
{
|
||||
@@ -60,10 +60,15 @@ 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* object function
|
||||
*/
|
||||
@@ -320,7 +325,7 @@ namespace bitsery {
|
||||
friend AdapterAccess;
|
||||
|
||||
TAdapterReader _reader;
|
||||
void* _context;
|
||||
TContext* _context;
|
||||
|
||||
//process value types
|
||||
//false_type means that we must process all elements individually
|
||||
@@ -390,7 +395,7 @@ namespace bitsery {
|
||||
template <typename Fnc>
|
||||
void procEnableBitPacking(const Fnc& fnc, std::false_type) {
|
||||
//create serializer using bitpacking wrapper
|
||||
BasicDeserializer<AdapterReaderBitPackingWrapper<TAdapterReader>> tmp(_reader, _context);
|
||||
BPEnabledType tmp(_reader, _context);
|
||||
fnc(tmp);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,8 @@ namespace bitsery {
|
||||
NoError,
|
||||
ReadingError, // this might be used with stream adapter
|
||||
DataOverflow,
|
||||
InvalidData
|
||||
InvalidData,
|
||||
InvalidPointer
|
||||
};
|
||||
|
||||
namespace details {
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace bitsery {
|
||||
friend int operator - (const NotDefinedType&, const NotDefinedType&) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int& operator*() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -25,9 +25,11 @@
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <tuple>
|
||||
#include "adapter_utils.h"
|
||||
#include "../traits/core/traits.h"
|
||||
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
//this allows to call private serialize method for the class
|
||||
@@ -234,8 +236,73 @@ namespace bitsery {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* function for getting context from serializer/deserializer
|
||||
* has different behaviour when context is tuple
|
||||
*/
|
||||
|
||||
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)))>
|
||||
{};
|
||||
|
||||
|
||||
//when context is tuple, then get object from tuple
|
||||
#if __cplusplus >= 201402L
|
||||
template <typename TCast, typename ... Args>
|
||||
TCast* getContextImpl(std::tuple<Args...>* ctx, std::true_type) {
|
||||
static_assert(HasType<TCast, Args...>::value, "Invalid context cast from tuple. Type doesn't exists.");
|
||||
return std::addressof(std::get<TCast>(*ctx));
|
||||
}
|
||||
#else
|
||||
//c++11 doesn't have std::get<type> overload for getting tuple element, so we need to write our selves
|
||||
|
||||
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 from tuple. Type doesn't exists.");
|
||||
return std::addressof(std::get<TCastIndex::value>(*ctx));
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename TCast, typename TContext>
|
||||
TCast* getContextImpl(TContext* ctx, std::false_type) {
|
||||
static_assert(std::is_convertible<TContext*, TCast*>::value, "Invalid context cast");
|
||||
return static_cast<TCast*>(ctx);
|
||||
}
|
||||
|
||||
template <typename TCast, typename TContext>
|
||||
TCast* getContext(TContext* ctx) {
|
||||
return ctx
|
||||
? getContextImpl<TCast>(ctx, IsSpecializationOf<TContext, std::tuple>{})
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif //BITSERY_DETAILS_SERIALIZATION_COMMON_H
|
||||
|
||||
|
||||
373
include/bitsery/ext/pointer.h
Normal file
373
include/bitsery/ext/pointer.h
Normal file
@@ -0,0 +1,373 @@
|
||||
//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 <unordered_map>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace bitsery {
|
||||
|
||||
namespace ext {
|
||||
|
||||
//forward declare
|
||||
class PointerLinkingContext;
|
||||
|
||||
namespace details_pointer {
|
||||
|
||||
enum class PointerOwnershipType:uint8_t {
|
||||
//is not responsible for pointer lifetime management.
|
||||
Observer,
|
||||
//only ONE owner is responsible for this pointers creation/destruction
|
||||
Owner,
|
||||
//MANY shared owners is responsible for pointer creation/destruction
|
||||
//requires additional context to manage shared owners themselves.
|
||||
Shared
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
|
||||
size_t createId(const void *ptr, PointerOwnershipType ptrType) {
|
||||
if (ptr == nullptr)
|
||||
return 0u;
|
||||
|
||||
auto res = _ptrMap.emplace(ptr, PointerInfo{_currId + 1u, ptrType});
|
||||
auto& ptrInfo = res.first->second;
|
||||
if (res.second) {
|
||||
++_currId;
|
||||
return ptrInfo.id;
|
||||
}
|
||||
//ptr already exists
|
||||
//for observer return success
|
||||
if (ptrType == PointerOwnershipType::Observer)
|
||||
return ptrInfo.id;
|
||||
//set owner and return success
|
||||
if (ptrInfo.ownershipType == PointerOwnershipType::Observer) {
|
||||
ptrInfo.ownershipType = ptrType;
|
||||
return ptrInfo.id;
|
||||
}
|
||||
//only shared ownership can get here multiple times
|
||||
assert(ptrType == PointerOwnershipType::Shared);
|
||||
return ptrInfo.id;
|
||||
}
|
||||
|
||||
template <typename S, typename TPtr>
|
||||
void serialize(S& s, const TPtr& obj) {
|
||||
using TNonPtr = typename std::remove_pointer<TPtr>::type;
|
||||
static_assert(!std::is_polymorphic<TNonPtr>::value, "Polymorphic types are not supported");
|
||||
serializeImpl(s, *obj, details::IsFundamentalType<TNonPtr>{});
|
||||
}
|
||||
|
||||
//valid, when all pointers have owners.
|
||||
//we cannot serialize pointers, if we haven't serialized objects themselves
|
||||
bool isPointerSerializationValid() const {
|
||||
return std::all_of(_ptrMap.begin(), _ptrMap.end(), [](const std::pair<const void*, PointerInfo>& p) {
|
||||
return p.second.ownershipType != PointerOwnershipType::Observer;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename S, typename T>
|
||||
void serializeImpl(S& s, const T& obj, std::true_type) {
|
||||
s.template value<sizeof(T)>(obj);
|
||||
}
|
||||
|
||||
template <typename S, typename T>
|
||||
void serializeImpl(S& s, const T& obj, std::false_type) {
|
||||
s.object(obj);
|
||||
}
|
||||
|
||||
struct PointerInfo {
|
||||
PointerInfo(size_t id_, PointerOwnershipType ownershipType_)
|
||||
:id{id_},
|
||||
ownershipType{ownershipType_}
|
||||
{};
|
||||
size_t id;
|
||||
PointerOwnershipType ownershipType;
|
||||
};
|
||||
|
||||
size_t _currId;
|
||||
std::unordered_map<const void*, PointerInfo> _ptrMap;
|
||||
|
||||
};
|
||||
|
||||
//this class is used to store context for shared ptr owners
|
||||
struct SharedContextBase {
|
||||
virtual ~SharedContextBase() = default;
|
||||
};
|
||||
|
||||
//helper functions that creates or destroys pointers
|
||||
//useful, because can be specialized
|
||||
template <typename T>
|
||||
void destroyPointer(T& ptr) {
|
||||
if (ptr) {
|
||||
delete ptr;
|
||||
}
|
||||
ptr = nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void createPointer(T& ptr) {
|
||||
using TNonPtr = typename std::remove_pointer<T>::type;
|
||||
if (ptr == nullptr)
|
||||
ptr = new TNonPtr{};
|
||||
}
|
||||
|
||||
class PointerLinkingContextDeserialization {
|
||||
public:
|
||||
explicit PointerLinkingContextDeserialization()
|
||||
: _idMap{}
|
||||
{}
|
||||
|
||||
PointerLinkingContextDeserialization(const PointerLinkingContextDeserialization&) = delete;
|
||||
PointerLinkingContextDeserialization& operator = (const PointerLinkingContextDeserialization&) = delete;
|
||||
|
||||
PointerLinkingContextDeserialization(PointerLinkingContextDeserialization&&) = default;
|
||||
PointerLinkingContextDeserialization& operator = (PointerLinkingContextDeserialization&&) = default;
|
||||
~PointerLinkingContextDeserialization() = default;
|
||||
|
||||
void processOwnerPtr(size_t id, void* ptr, PointerOwnershipType ptrType) {
|
||||
assert(id != 0 && ptr != nullptr && ptrType != PointerOwnershipType::Observer);
|
||||
auto res = _idMap.emplace(id, PointerInfo{id, ptr, ptrType});
|
||||
auto& ptrInfo = res.first->second;
|
||||
if (!res.second) {
|
||||
assert(ptrInfo.ownershipType != PointerOwnershipType::Owner);
|
||||
//if already exists, then process observer list
|
||||
if (ptrInfo.ownershipType == PointerOwnershipType::Observer) {
|
||||
ptrInfo.ptrAddress = ptr;
|
||||
ptrInfo.ownershipType = ptrType;
|
||||
processObserverList(ptrInfo.observersList, ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processObserverPtr(size_t id, void* (&ptr)) {
|
||||
auto res = _idMap.emplace(id, PointerInfo{id, nullptr, PointerOwnershipType::Observer});
|
||||
auto& ptrInfo = res.first->second;
|
||||
if (ptrInfo.ptrAddress)
|
||||
ptr = ptrInfo.ptrAddress;
|
||||
else
|
||||
ptrInfo.observersList.push_back(ptr);
|
||||
}
|
||||
|
||||
template <typename S, typename TPtr>
|
||||
void deserialize(S& s, TPtr& obj) {
|
||||
using TNonPtr = typename std::remove_pointer<TPtr>::type;
|
||||
static_assert(!std::is_polymorphic<TNonPtr>::value, "Polymorphic types are not supported");
|
||||
createPointer(obj);
|
||||
deserializeImpl(s, *obj, details::IsFundamentalType<TNonPtr>{});
|
||||
}
|
||||
|
||||
//valid, when all pointers has owners
|
||||
bool isPointerDeserializationValid() const {
|
||||
return std::all_of(_idMap.begin(), _idMap.end(), [](const std::pair<const size_t, PointerInfo>& p) {
|
||||
return p.second.ownershipType != PointerOwnershipType::Observer;
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
struct PointerInfo {
|
||||
PointerInfo(size_t id_, void* ptr, PointerOwnershipType ownershipType_)
|
||||
:id{id_},
|
||||
ownershipType{ownershipType_},
|
||||
ptrAddress{ptr},
|
||||
observersList{},
|
||||
sharedContext{}
|
||||
{};
|
||||
PointerInfo(const PointerInfo&) = delete;
|
||||
PointerInfo& operator = (const PointerInfo&) = delete;
|
||||
PointerInfo(PointerInfo&&) = default;
|
||||
PointerInfo&operator = (PointerInfo&&) = default;
|
||||
~PointerInfo() = default;
|
||||
|
||||
size_t id;
|
||||
PointerOwnershipType ownershipType;
|
||||
void* ptrAddress;
|
||||
std::vector<std::reference_wrapper<void*>> observersList;
|
||||
std::unique_ptr<SharedContextBase> sharedContext;
|
||||
};
|
||||
|
||||
void processObserverList(std::vector<std::reference_wrapper<void*>>& observers, void* ptr) {
|
||||
for (auto& o:observers)
|
||||
o.get() = ptr;
|
||||
observers.clear();
|
||||
observers.shrink_to_fit();
|
||||
}
|
||||
|
||||
template <typename S, typename T>
|
||||
void deserializeImpl(S& s, T& obj, std::true_type) {
|
||||
s.template value<sizeof(T)>(obj);
|
||||
}
|
||||
|
||||
template <typename S, typename T>
|
||||
void deserializeImpl(S& s, T& obj, std::false_type) {
|
||||
s.object(obj);
|
||||
}
|
||||
|
||||
std::unordered_map<size_t, PointerInfo> _idMap;
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
PointerLinkingContext& getLinkingContext(S& s) {
|
||||
auto res = s.template context<PointerLinkingContext>();
|
||||
assert(res != nullptr);
|
||||
return *res;
|
||||
}
|
||||
}
|
||||
|
||||
//this class is for convenience
|
||||
class PointerLinkingContext:
|
||||
public details_pointer::PointerLinkingContextSerialization,
|
||||
public details_pointer::PointerLinkingContextDeserialization {
|
||||
public:
|
||||
bool isValid() {
|
||||
return isPointerSerializationValid() && isPointerDeserializationValid();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class PointerOwner {
|
||||
public:
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &w, const T &obj, Fnc &&) const {
|
||||
auto& ctx = details_pointer::getLinkingContext(ser);
|
||||
auto id = ctx.createId(obj, details_pointer::PointerOwnershipType::Owner);
|
||||
details::writeSize(w, id);
|
||||
if (id)
|
||||
ctx.serialize(ser, obj);
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &des, Reader &r, T &obj, Fnc &&) const {
|
||||
details_pointer::getLinkingContext(des);
|
||||
size_t id{};
|
||||
details::readSize(r, id, std::numeric_limits<size_t>::max());
|
||||
if (id) {
|
||||
auto& ctx = details_pointer::getLinkingContext(des);
|
||||
ctx.deserialize(des, obj);
|
||||
ctx.processOwnerPtr(id, obj, details_pointer::PointerOwnershipType::Owner);
|
||||
} else {
|
||||
details_pointer::destroyPointer(obj);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class PointerObserver {
|
||||
public:
|
||||
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &w, const T &obj, Fnc &&) const {
|
||||
auto& ctx = details_pointer::getLinkingContext(ser);
|
||||
details::writeSize(w, ctx.createId(obj, details_pointer::PointerOwnershipType::Observer));
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &des, Reader &r, T &obj, Fnc &&) const {
|
||||
size_t id{};
|
||||
details::readSize(r, id, std::numeric_limits<size_t>::max());
|
||||
if (id) {
|
||||
auto& ctx = details_pointer::getLinkingContext(des);
|
||||
ctx.processObserverPtr(id, reinterpret_cast<void*&>(obj));
|
||||
} else {
|
||||
obj = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class ReferencedByPointer {
|
||||
public:
|
||||
template<typename Ser, typename Writer, typename T, typename Fnc>
|
||||
void serialize(Ser &ser, Writer &w, const T &obj, Fnc && fnc) const {
|
||||
auto& ctx = details_pointer::getLinkingContext(ser);
|
||||
details::writeSize(w, ctx.createId(&obj, details_pointer::PointerOwnershipType::Owner));
|
||||
fnc(const_cast<T&>(obj));
|
||||
}
|
||||
|
||||
template<typename Des, typename Reader, typename T, typename Fnc>
|
||||
void deserialize(Des &des, Reader &r, T &obj, Fnc && fnc) const {
|
||||
size_t id{};
|
||||
details::readSize(r, id, std::numeric_limits<size_t>::max());
|
||||
if (id) {
|
||||
auto& ctx = details_pointer::getLinkingContext(des);
|
||||
fnc(obj);
|
||||
ctx.processOwnerPtr(id, &obj, details_pointer::PointerOwnershipType::Owner);
|
||||
} else {
|
||||
//cannot be null for references
|
||||
r.setError(ReaderError::InvalidPointer);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace traits {
|
||||
|
||||
template<typename T>
|
||||
struct ExtensionTraits<ext::PointerOwner, T*> {
|
||||
using TValue = T;
|
||||
static constexpr bool SupportValueOverload = true;
|
||||
static constexpr bool SupportObjectOverload = true;
|
||||
//pointers cannot have lamba overload, when polymorphism support will be added
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
};
|
||||
|
||||
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
|
||||
using TValue = T;
|
||||
static constexpr bool SupportValueOverload = true;
|
||||
static constexpr bool SupportObjectOverload = true;
|
||||
//pointers cannot have lamba overload, when polymorphism support will be added
|
||||
static constexpr bool SupportLambdaOverload = false;
|
||||
};
|
||||
|
||||
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
|
||||
@@ -30,17 +30,17 @@
|
||||
|
||||
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>;
|
||||
|
||||
template <typename WriterParam>
|
||||
explicit BasicSerializer(WriterParam&& w, void* context = nullptr)
|
||||
explicit BasicSerializer(WriterParam&& w, TContext* context = nullptr)
|
||||
: _writer{std::forward<WriterParam>(w)},
|
||||
_context{context}
|
||||
{
|
||||
@@ -58,10 +58,15 @@ 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);
|
||||
}
|
||||
|
||||
/*
|
||||
* object function
|
||||
*/
|
||||
@@ -316,7 +321,7 @@ namespace bitsery {
|
||||
friend AdapterAccess;
|
||||
|
||||
TAdapterWriter _writer;
|
||||
void* _context;
|
||||
TContext* _context;
|
||||
|
||||
//process value types
|
||||
//false_type means that we must process all elements individually
|
||||
@@ -381,7 +386,7 @@ namespace bitsery {
|
||||
template <typename Fnc>
|
||||
void procEnableBitPacking(const Fnc& fnc, std::false_type) {
|
||||
//create serializer using bitpacking wrapper
|
||||
BasicSerializer<AdapterWriterBitPackingWrapper<TAdapterWriter>> tmp(_writer, _context);
|
||||
BPEnabledType tmp(_writer, _context);
|
||||
fnc(tmp);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,8 @@ namespace bitsery {
|
||||
}
|
||||
};
|
||||
|
||||
//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
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
#include <bitsery/adapter/stream.h>
|
||||
#include <bitsery/adapter_writer.h>
|
||||
#include <bitsery/adapter_reader.h>
|
||||
#include <bitsery/traits/vector.h>
|
||||
#include <bitsery/traits/array.h>
|
||||
#include <bitsery/traits/string.h>
|
||||
#include <sstream>
|
||||
|
||||
//some helper types
|
||||
@@ -33,21 +36,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;
|
||||
@@ -108,4 +103,59 @@ TEST(AdapterIOStream, WhenReadingMoreThanAvailableThenDataOverflow) {
|
||||
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));
|
||||
}
|
||||
69
tests/serialization_context.cpp
Normal file
69
tests/serialization_context.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
//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 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));
|
||||
}
|
||||
@@ -45,7 +45,7 @@ struct DataV3 {
|
||||
|
||||
|
||||
TEST(SerializeExtensionGrowable, WriteSessionsDataAtBufferEndAfterFlush) {
|
||||
BasicSerializationContext<SessionsEnabledConfig> ctx;
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
auto& ser = ctx.createSerializer();
|
||||
//session cannot be empty
|
||||
ser.ext(int8_t{}, Growable{}, [&ser] (int8_t& v) {
|
||||
@@ -59,7 +59,7 @@ TEST(SerializeExtensionGrowable, WriteSessionsDataAtBufferEndAfterFlush) {
|
||||
|
||||
|
||||
TEST(SerializeExtensionGrowable, SessionDataConsistOfSessionsEndPosAnd4BytesSessionsDataOffset) {
|
||||
BasicSerializationContext<SessionsEnabledConfig> ctx;
|
||||
BasicSerializationContext<SessionsEnabledConfig, void> ctx;
|
||||
|
||||
|
||||
constexpr size_t DATA_SIZE = 4;
|
||||
@@ -89,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();
|
||||
@@ -121,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);
|
||||
@@ -147,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);
|
||||
@@ -174,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);
|
||||
@@ -202,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);
|
||||
@@ -238,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);
|
||||
@@ -278,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);
|
||||
@@ -334,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);
|
||||
@@ -396,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);
|
||||
|
||||
431
tests/serialization_ext_pointer.cpp
Normal file
431
tests/serialization_ext_pointer.cpp
Normal file
@@ -0,0 +1,431 @@
|
||||
//MIT License
|
||||
//
|
||||
//Copyright (c) 2017 Mindaugas Vinkelis
|
||||
//
|
||||
//Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
//of this software and associated documentation files (the "Software"), to deal
|
||||
//in the Software without restriction, including without limitation the rights
|
||||
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
//copies of the Software, and to permit persons to whom the Software is
|
||||
//furnished to do so, subject to the following conditions:
|
||||
//
|
||||
//The above copyright notice and this permission notice shall be included in all
|
||||
//copies or substantial portions of the Software.
|
||||
//
|
||||
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
//SOFTWARE.
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "serialization_test_utils.h"
|
||||
#include <bitsery/ext/pointer.h>
|
||||
|
||||
using bitsery::ext::PointerOwner;
|
||||
using bitsery::ext::PointerObserver;
|
||||
using bitsery::ext::ReferencedByPointer;
|
||||
using bitsery::ext::PointerLinkingContext;
|
||||
|
||||
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.createId(sharedPtr, bitsery::ext::details_pointer::PointerOwnershipType::Shared), Eq(1));
|
||||
EXPECT_THAT(plctx1.createId(sharedPtr, bitsery::ext::details_pointer::PointerOwnershipType::Shared), Eq(1));
|
||||
EXPECT_THAT(plctx1.createId(sharedPtr, bitsery::ext::details_pointer::PointerOwnershipType::Shared), Eq(1));
|
||||
}
|
||||
|
||||
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, WhenPointerLinkingContextIsNullThenAssert) {
|
||||
MyStruct1* data = nullptr;
|
||||
//linking context
|
||||
PointerLinkingContext plctx1{};
|
||||
SerContext sctx1;
|
||||
EXPECT_DEATH(sctx1.createSerializer(nullptr).ext(data, PointerOwner{}), "");
|
||||
EXPECT_DEATH(sctx1.createDeserializer(nullptr).ext(data, PointerObserver{}), "");
|
||||
}
|
||||
|
||||
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{}), "");
|
||||
}
|
||||
|
||||
#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, WhenReferencedByPointerReadsZeroPointerIdThenInvalidPointerError) {
|
||||
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, 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;
|
||||
}
|
||||
@@ -38,8 +38,8 @@ struct MyStruct1 {
|
||||
|
||||
MyStruct1() : MyStruct1{0, 0} {}
|
||||
|
||||
int i1;
|
||||
int i2;
|
||||
int32_t i1;
|
||||
int32_t i2;
|
||||
|
||||
bool operator==(const MyStruct1 &rhs) const {
|
||||
return i1 == rhs.i1 && i2 == rhs.i2;
|
||||
@@ -97,31 +97,33 @@ using Writer = bitsery::AdapterWriter<OutputAdapter, bitsery::DefaultConfig>;
|
||||
using Reader = bitsery::AdapterReader<InputAdapter, bitsery::DefaultConfig>;
|
||||
|
||||
|
||||
template <typename Config = bitsery::DefaultConfig>
|
||||
template <typename Config, typename Context>
|
||||
class BasicSerializationContext {
|
||||
public:
|
||||
using TWriter = bitsery::AdapterWriter<OutputAdapter, Config>;
|
||||
using TReader = bitsery::AdapterReader<InputAdapter, Config>;
|
||||
using TSerializer = bitsery::BasicSerializer<TWriter, Context>;
|
||||
using TDeserializer = bitsery::BasicDeserializer<TReader, Context>;
|
||||
|
||||
Buffer buf{};
|
||||
std::unique_ptr<bitsery::BasicSerializer<TWriter>> ser{};
|
||||
std::unique_ptr<bitsery::BasicDeserializer<TReader>> des{};
|
||||
std::unique_ptr<TSerializer> ser{};
|
||||
std::unique_ptr<bitsery::BasicDeserializer<TReader, Context>> des{};
|
||||
TWriter* bw{};
|
||||
TReader* br{};
|
||||
|
||||
bitsery::BasicSerializer<TWriter>& createSerializer() {
|
||||
TSerializer& createSerializer(Context* ctx = nullptr) {
|
||||
if (!ser) {
|
||||
ser = std::unique_ptr<bitsery::BasicSerializer<TWriter>>(new bitsery::BasicSerializer<TWriter>(OutputAdapter{buf}));
|
||||
ser = std::unique_ptr<TSerializer>(new TSerializer(OutputAdapter{buf}, ctx));
|
||||
bw = &bitsery::AdapterAccess::getWriter(*ser);
|
||||
}
|
||||
return *ser;
|
||||
};
|
||||
|
||||
bitsery::BasicDeserializer<bitsery::AdapterReader<InputAdapter, Config>>& createDeserializer() {
|
||||
TDeserializer & createDeserializer(Context* ctx = nullptr) {
|
||||
bw->flush();
|
||||
if (!des) {
|
||||
des = std::unique_ptr<bitsery::BasicDeserializer<TReader>>(
|
||||
new bitsery::BasicDeserializer<TReader>(InputAdapter{buf.begin(), bw->writtenBytesCount()}));
|
||||
des = std::unique_ptr<TDeserializer>(
|
||||
new TDeserializer(InputAdapter{buf.begin(), bw->writtenBytesCount()}, ctx));
|
||||
br = &bitsery::AdapterAccess::getReader(*des);
|
||||
}
|
||||
return *des;
|
||||
@@ -144,6 +146,6 @@ public:
|
||||
};
|
||||
|
||||
//helper type
|
||||
using SerializationContext = BasicSerializationContext<bitsery::DefaultConfig>;
|
||||
using SerializationContext = BasicSerializationContext<bitsery::DefaultConfig, void>;
|
||||
|
||||
#endif //BITSERY_SERIALIZER_TEST_UTILS_H
|
||||
|
||||
Reference in New Issue
Block a user