mirror of
https://github.com/fraillt/bitsery.git
synced 2026-06-08 16:23:54 +00:00
161 lines
5.6 KiB
C++
161 lines
5.6 KiB
C++
#include <bitsery/bitsery.h>
|
|
#include <bitsery/adapter/buffer.h>
|
|
#include <bitsery/traits/vector.h>
|
|
|
|
//include pointers extension
|
|
//this header contains multiple extensions for different pointer types and pointer linking context,
|
|
//that validates pointer ownership and checks if there are and no dangling pointers after serialization/deserialization.
|
|
//dangling pointer in this context means, that non-owning pointer points to data, that was not serialized.
|
|
#include <bitsery/ext/pointer.h>
|
|
|
|
using bitsery::ext::ReferencedByPointer;
|
|
using bitsery::ext::PointerObserver;
|
|
using bitsery::ext::PointerOwner;
|
|
using bitsery::ext::PointerType ;
|
|
|
|
enum class MyEnum:uint16_t { V1,V2,V3 };
|
|
struct MyStruct {
|
|
MyStruct(uint32_t i_, MyEnum e_, std::vector<float> fs_)
|
|
:i{i_},
|
|
e{e_},
|
|
fs{fs_} {}
|
|
MyStruct():MyStruct{0, MyEnum::V1, {}} {}
|
|
uint32_t i;
|
|
MyEnum e;
|
|
std::vector<float> fs;
|
|
};
|
|
|
|
template <typename S>
|
|
void serialize(S& s, MyStruct& o) {
|
|
s.value4b(o.i);
|
|
s.value2b(o.e);
|
|
s.container4b(o.fs, 10);
|
|
}
|
|
|
|
//our test data
|
|
struct Test1Data {
|
|
//regular data, nothing fancy here
|
|
MyStruct o1;
|
|
int32_t i1;
|
|
//these container elements can be referenced by pointers
|
|
std::vector<MyStruct> vdata;
|
|
//container that holds non owning pointers (observers),
|
|
std::vector<MyStruct*> vptr;
|
|
//treat it as is observer
|
|
MyStruct* po1;
|
|
//we treat this as owner (responsible for allocation/deallocation
|
|
int32_t* pi1;
|
|
|
|
private:
|
|
friend bitsery::Access;
|
|
|
|
template <typename S>
|
|
void serialize(S& s) {
|
|
//just a regular fields
|
|
s.object(o1);
|
|
s.value4b(i1);
|
|
|
|
//set container elements to be candidates for non-owning pointers
|
|
s.container(vdata, 100, [&s](MyStruct& d){
|
|
s.ext(d, ReferencedByPointer{});
|
|
});
|
|
//contains non owning pointers
|
|
//
|
|
//IMPORTANT !!!
|
|
//ALWAYS ACCEPT BY REFERENCE like this: T* (&obj)
|
|
//if using c++14, then auto& always works.
|
|
//
|
|
//you can also serialize non owning pointers first, pointer linking context will keep track on them
|
|
//and as soon as pointer owner data is deserialized, all non-owning pointers will be updated
|
|
s.container(vptr, 100, [&s](MyStruct* (&d)){
|
|
s.ext(d, PointerObserver{});
|
|
});
|
|
//observer
|
|
s.ext(po1, PointerObserver{});
|
|
//owner, mark it as not null
|
|
s.ext4b(pi1, PointerOwner{PointerType::NotNull});
|
|
|
|
}
|
|
};
|
|
|
|
using namespace bitsery;
|
|
|
|
//some helper types
|
|
using Buffer = std::vector<uint8_t>;
|
|
using OutputAdapter = OutputBufferAdapter<Buffer>;
|
|
using InputAdapter = InputBufferAdapter<Buffer>;
|
|
|
|
//we will need PointerLinkingContext to work with pointers
|
|
//so lets define our serializer/deserializer
|
|
//if we need context for our own custom flow, we can define it as tuple like this:
|
|
// std::tuple<MyContext,ext::PointerLinkingContext>
|
|
//and other code will work as expected as long as it cast to proper type.
|
|
//see context_usage.cpp for usage example
|
|
using MySerializer = BasicSerializer<AdapterWriter<OutputAdapter, DefaultConfig>, ext::PointerLinkingContext>;
|
|
using MyDeserializer = BasicDeserializer<AdapterReader<InputAdapter, DefaultConfig>, ext::PointerLinkingContext>;
|
|
|
|
int main() {
|
|
//set some random data
|
|
Test1Data data{};
|
|
data.vdata.emplace_back(8941, MyEnum::V1, std::vector<float>{4.4f});
|
|
data.vdata.emplace_back(15478, MyEnum::V2, std::vector<float>{15.0f});
|
|
data.vdata.emplace_back(59, MyEnum::V3, std::vector<float>{-8.5f, 0.045f});
|
|
//container of non owning pointers (observers)
|
|
data.vptr.emplace_back(nullptr);
|
|
data.vptr.emplace_back(std::addressof(data.vdata[0]));
|
|
data.vptr.emplace_back(std::addressof(data.vdata[2]));
|
|
//regular fields
|
|
data.o1 = MyStruct{4, MyEnum::V2, {57.078f}};
|
|
data.i1 = 9455;
|
|
//observer
|
|
data.po1 = std::addressof(data.vdata[1]);
|
|
//owning pointer
|
|
data.pi1 = new int32_t{};
|
|
|
|
//create buffer to store data
|
|
Buffer buffer{};
|
|
size_t writtenSize{};
|
|
//in order to use pointers, we need to pass pointer linking context to serializer/deserializer
|
|
{
|
|
ext::PointerLinkingContext ctx{};
|
|
//pass lining context to serializer, by pointer
|
|
MySerializer ser{OutputAdapter{buffer}, &ctx};
|
|
//serialize our data
|
|
ser.object(data);
|
|
auto& w = AdapterAccess::getWriter(ser);
|
|
w.flush();
|
|
writtenSize = w.writtenBytesCount();
|
|
|
|
//make sure that pointer linking context is valid
|
|
//this ensures that all non-owning pointers points to data that has been serialized,
|
|
//so we can successfully reconstruct pointers after deserialization
|
|
assert(ctx.isValid());
|
|
}
|
|
|
|
Test1Data res{};
|
|
{
|
|
ext::PointerLinkingContext ctx{};
|
|
//pass lining context to deserializer, by pointer
|
|
MyDeserializer des{InputAdapter{buffer.begin(), writtenSize}, &ctx};
|
|
//deserialize our data
|
|
des.object(res);
|
|
auto& r = AdapterAccess::getReader(des);
|
|
//check if everything went find
|
|
assert(r.error() == ReaderError::NoError && r.isCompletedSuccessfully());
|
|
//also check for dangling pointers, after deserialization
|
|
assert(ctx.isValid());
|
|
}
|
|
//owning pointers owns data
|
|
assert(*res.pi1 == *data.pi1);
|
|
assert(res.pi1 != data.pi1);
|
|
//observers, points to other data
|
|
assert(res.vptr[0] == nullptr);
|
|
assert(res.vptr[1] == std::addressof(res.vdata[0]));
|
|
assert(res.vptr[2] == std::addressof(res.vdata[2]));
|
|
assert(res.po1 == std::addressof(res.vdata[1]));
|
|
|
|
//delete raw owning pointers
|
|
delete data.pi1;
|
|
delete res.pi1;
|
|
}
|