1 Commits

Author SHA1 Message Date
fraillt
5b1dc3bcfa raw pointers support, without polymorphism. 2017-10-27 08:14:01 +03:00
22 changed files with 1457 additions and 124 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -33,7 +33,8 @@ namespace bitsery {
NoError,
ReadingError, // this might be used with stream adapter
DataOverflow,
InvalidData
InvalidData,
InvalidPointer
};
namespace details {

View File

@@ -52,6 +52,7 @@ namespace bitsery {
friend int operator - (const NotDefinedType&, const NotDefinedType&) {
return 0;
}
int& operator*() {
return data;
}

View File

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

View 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

View File

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

View File

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

View File

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

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

View File

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

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

View File

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