mirror of
https://github.com/fraillt/bitsery.git
synced 2026-06-08 00:03:54 +00:00
basic functionality is tested and is can be used
This commit is contained in:
100
README.md
100
README.md
@@ -1,4 +1,98 @@
|
||||
# Bitsery #
|
||||
Header only C++ binary serialization library.
|
||||
# Bitsery
|
||||
|
||||
Header only C++ binary serialization library.
|
||||
It is designed around the networking requirements for multiplayer real-time fast paced games as first person shooters.
|
||||
All cross-platform requirements are enforced at compile time, so serialized data do not store any run-time type information and is as small as possible.
|
||||
|
||||
## Status
|
||||
|
||||
**bitsery** is in pre-release state and is looking for your feedback.
|
||||
It has basic features, serialize arithmetic types, enums, containers and text, but is missing any advanced compression functions like delta changes serialization, entropy encoding, floating point compression, geometry compression, etc...
|
||||
> Current version do not handle Big/Little Endianness.
|
||||
|
||||
## Example
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include "BufferReader.h"
|
||||
#include "BufferWriter.h"
|
||||
#include "Serializer.h"
|
||||
#include "Deserializer.h"
|
||||
|
||||
enum class MyEnum {
|
||||
V1,V2,V3
|
||||
};
|
||||
|
||||
struct MyStruct {
|
||||
int i;
|
||||
MyEnum e;
|
||||
std::vector<float> fs;
|
||||
};
|
||||
|
||||
//define how object should be serialized
|
||||
SERIALIZE(MyStruct) {
|
||||
return s.
|
||||
value(o.i).
|
||||
value(o.e).
|
||||
container(o.fs);
|
||||
}
|
||||
|
||||
void print(const char* msg, const MyStruct& v) {
|
||||
std::cout << msg << std::endl;
|
||||
std::cout << "i:" << v.i << std::endl;
|
||||
std::cout << "e:" << (int)v.e << std::endl;
|
||||
std::cout << "fs:";
|
||||
for (auto p:v.fs)
|
||||
std::cout << '\t' << p;
|
||||
std::cout << std::endl << std::endl;
|
||||
}
|
||||
|
||||
int main() {
|
||||
//set some random data
|
||||
MyStruct data{};
|
||||
data.e = MyEnum::V2;
|
||||
data.i = 48465;
|
||||
data.fs.resize(4);
|
||||
float tmp = 4253;
|
||||
for (auto& v: data.fs) {
|
||||
tmp /=2;
|
||||
v = tmp;
|
||||
}
|
||||
|
||||
//create serializer
|
||||
//1) create buffer to store data
|
||||
std::vector<uint8_t> buffer;
|
||||
//2) create buffer writer that is able to write bytes or bits to buffer
|
||||
BufferWriter bw{buffer};
|
||||
//3) create serializer
|
||||
Serializer<BufferWriter> ser{bw};
|
||||
|
||||
//call serialize function
|
||||
serialize(ser, data);
|
||||
|
||||
//flush to buffer
|
||||
bw.flush();
|
||||
|
||||
MyStruct result{};
|
||||
|
||||
//create deserializer
|
||||
//1) create buffer reader
|
||||
BufferReader br{buffer};
|
||||
//2) create deserializer
|
||||
Deserializer<BufferReader> des{br};
|
||||
|
||||
//call same function with different arguments
|
||||
serialize(des, result);
|
||||
|
||||
//print results
|
||||
print("initial data", data);
|
||||
print("result", result);
|
||||
}
|
||||
```
|
||||
|
||||
## Platforms
|
||||
|
||||
This library was tested on
|
||||
* Windows: Visual Studio 2015
|
||||
* Linux: GCC 5.4, GCC 6.2
|
||||
|
||||
Currently it is not usable
|
||||
|
||||
@@ -18,6 +18,7 @@ struct MyStruct {
|
||||
std::vector<float> fs;
|
||||
};
|
||||
|
||||
//define how object should be serialized
|
||||
SERIALIZE(MyStruct) {
|
||||
return s.
|
||||
value(o.i).
|
||||
@@ -48,17 +49,25 @@ int main() {
|
||||
}
|
||||
|
||||
//create serializer
|
||||
//1) create buffer to store data
|
||||
std::vector<uint8_t> buffer;
|
||||
//2) create buffer writer that is able to write bytes or bits to buffer
|
||||
BufferWriter bw{buffer};
|
||||
//3) create serializer
|
||||
Serializer<BufferWriter> ser{bw};
|
||||
|
||||
//call serialize function
|
||||
serialize(ser, data);
|
||||
//this is required if using bit operations
|
||||
|
||||
//flush to buffer
|
||||
bw.flush();
|
||||
|
||||
MyStruct result{};
|
||||
|
||||
//create deserializer
|
||||
//1) create buffer reader
|
||||
BufferReader br{buffer};
|
||||
//2) create deserializer
|
||||
Deserializer<BufferReader> des{br};
|
||||
|
||||
//call same function with different arguments
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "Common.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
|
||||
struct BufferReader {
|
||||
@@ -21,9 +22,9 @@ struct BufferReader {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
using UT = typename std::make_unsigned<T>::type;
|
||||
return m_scratch
|
||||
? readBits<SIZE * 8>(reinterpret_cast<UT&>(v))
|
||||
: directRead(&v, 1);
|
||||
return !m_scratch
|
||||
? directRead(&v, 1)
|
||||
: readBits(reinterpret_cast<UT&>(v), BITS_SIZE<T>);
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
@@ -31,28 +32,59 @@ struct BufferReader {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
|
||||
if (m_scratchBits) {
|
||||
//todo implement
|
||||
// using UT = typename std::make_unsigned<T>::type;
|
||||
// writeBits<SIZE * 8 * count>(reinterpret_cast<const UT&>(v));
|
||||
} else {
|
||||
if (!m_scratchBits) {
|
||||
return directRead(buf, count);
|
||||
} else {
|
||||
using UT = typename std::make_unsigned<T>::type;
|
||||
//todo improve implementation
|
||||
const auto end = buf + count;
|
||||
for (auto it = buf; it != end; ++it) {
|
||||
if (!readBits(reinterpret_cast<UT&>(*it), BITS_SIZE<T>))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
bool readBits(T& v) {
|
||||
template<typename T>
|
||||
bool readBits(T& v, size_t bitsCount) {
|
||||
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
|
||||
static_assert(SIZE > 0 && SIZE <= BITS_SIZE<T>, "");
|
||||
assert(bitsCount <= BITS_SIZE<T>);
|
||||
|
||||
const auto bytesRequired = SIZE > m_scratchBits
|
||||
? ((SIZE - 1 - m_scratchBits) >> 3) + 1u
|
||||
const auto bytesRequired = bitsCount > m_scratchBits
|
||||
? ((bitsCount - 1 - m_scratchBits) >> 3) + 1u
|
||||
: 0u;
|
||||
if (static_cast<size_t>(std::distance(_pos, std::end(_buf))) < bytesRequired )
|
||||
return false;
|
||||
readBitsInternal(v, SIZE);
|
||||
readBitsInternal(v, bitsCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool align() {
|
||||
if ( m_scratchBits ) {
|
||||
SCRATCH_TYPE tmp{};
|
||||
readBitsInternal(tmp, BITS_SIZE<value_type> - m_scratchBits);
|
||||
return tmp == 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isCompleted() const {
|
||||
return _pos == std::end(_buf);
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<value_type>& _buf;
|
||||
decltype(std::begin(_buf)) _pos;
|
||||
template <typename T>
|
||||
bool directRead(T* v, size_t count) {
|
||||
static_assert(!std::is_const<T>::value, "");
|
||||
const auto bytesCount = sizeof(T) * count;
|
||||
if (static_cast<size_t>(std::distance(_pos, std::end(_buf))) < bytesCount)
|
||||
return false;
|
||||
std::copy_n(_pos, bytesCount, reinterpret_cast<value_type *>(v));
|
||||
std::advance(_pos, bytesCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -80,25 +112,6 @@ struct BufferReader {
|
||||
v = res;
|
||||
}
|
||||
|
||||
bool isCompleted() const {
|
||||
return _pos == std::end(_buf);
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<value_type>& _buf;
|
||||
decltype(std::begin(_buf)) _pos;
|
||||
template <typename T>
|
||||
bool directRead(T* v, size_t count) {
|
||||
static_assert(!std::is_const<T>::value, "");
|
||||
const auto bytesCount = sizeof(T) * count;
|
||||
if (static_cast<size_t>(std::distance(_pos, std::end(_buf))) < bytesCount)
|
||||
return false;
|
||||
std::copy_n(_pos, bytesCount, reinterpret_cast<value_type *>(v));
|
||||
std::advance(_pos, bytesCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
using SCRATCH_TYPE = typename BIGGER_TYPE<value_type>::type;
|
||||
|
||||
SCRATCH_TYPE m_scratch{};
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
#define PROJECT_TEMPLATE_BUFFER_WRITER_H
|
||||
|
||||
#include "Common.h"
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
struct MeasureSize {
|
||||
|
||||
@@ -14,28 +16,29 @@ struct MeasureSize {
|
||||
void writeBytes(const T& ) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
_bytesCount += SIZE;
|
||||
_bitsCount += BITS_SIZE<T>;
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBits(const T& ) {
|
||||
template<typename T>
|
||||
void writeBits(const T& , size_t bitsCount) {
|
||||
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
|
||||
static_assert(SIZE > 0 && SIZE <= BITS_SIZE<T>, "");
|
||||
_bytesCount += SIZE * 8;
|
||||
assert(bitsCount <= BITS_SIZE<T>);
|
||||
_bitsCount += bitsCount;
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBuffer(const T* , size_t count) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
_bytesCount += SIZE * count;
|
||||
_bitsCount += BITS_SIZE<T> * count;
|
||||
}
|
||||
|
||||
//get size in bytes
|
||||
size_t getSize() const {
|
||||
return _bytesCount;
|
||||
return _bitsCount / 8;
|
||||
}
|
||||
private:
|
||||
size_t _bytesCount{};
|
||||
size_t _bitsCount{};
|
||||
|
||||
};
|
||||
|
||||
@@ -43,7 +46,7 @@ private:
|
||||
struct BufferWriter {
|
||||
using value_type = uint8_t;
|
||||
BufferWriter(std::vector<uint8_t>& buffer):_buf{buffer}, _outIt{std::back_inserter(buffer)} {
|
||||
|
||||
static_assert(std::is_unsigned<value_type>::value, "");
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
@@ -51,11 +54,11 @@ struct BufferWriter {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
|
||||
if (m_scratchBits) {
|
||||
using UT = typename std::make_unsigned<T>::type;
|
||||
writeBits<SIZE * 8>(reinterpret_cast<const UT&>(v));
|
||||
} else {
|
||||
if (!m_scratchBits) {
|
||||
directWrite(&v,1);
|
||||
} else {
|
||||
using UT = typename std::make_unsigned<T>::type;
|
||||
writeBits(reinterpret_cast<const UT&>(v), BITS_SIZE<T>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,41 +66,28 @@ struct BufferWriter {
|
||||
void writeBuffer(const T* buf, size_t count) {
|
||||
static_assert(std::is_integral<T>(), "");
|
||||
static_assert(sizeof(T) == SIZE, "");
|
||||
if (m_scratchBits) {
|
||||
//todo implement
|
||||
// using UT = typename std::make_unsigned<T>::type;
|
||||
// writeBits<SIZE * 8 * count>(reinterpret_cast<const UT&>(v));
|
||||
} else {
|
||||
if (!m_scratchBits) {
|
||||
directWrite(buf, count);
|
||||
} else {
|
||||
using UT = typename std::make_unsigned<T>::type;
|
||||
//todo improve implementation
|
||||
const auto end = buf + count;
|
||||
for (auto it = buf; it != end; ++it)
|
||||
writeBits(reinterpret_cast<const UT&>(*it), BITS_SIZE<T>);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<size_t SIZE, typename T>
|
||||
void writeBits(const T& v) {
|
||||
template<typename T>
|
||||
void writeBits(const T& v, size_t bitsCount) {
|
||||
static_assert(std::is_integral<T>() && std::is_unsigned<T>(), "");
|
||||
static_assert(SIZE > 0 && SIZE <= BITS_SIZE<T>, "");
|
||||
writeBitsInternal(v, SIZE);
|
||||
assert(bitsCount <= BITS_SIZE<T>);
|
||||
assert( v <= (( 1ULL << bitsCount ) - 1 ) );
|
||||
writeBitsInternal(v, bitsCount);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void writeBitsInternal(const T& v, size_t size) {
|
||||
auto value = v;
|
||||
auto bitsLeft = size;
|
||||
while (bitsLeft > 0) {
|
||||
auto bits = std::min(bitsLeft, BITS_SIZE<value_type>);
|
||||
m_scratch |= static_cast<SCRATCH_TYPE>( value ) << m_scratchBits;
|
||||
m_scratchBits += bits;
|
||||
if ( m_scratchBits >= BITS_SIZE<value_type> ) {
|
||||
auto tmp = static_cast<value_type>(m_scratch & bufTypeMask);
|
||||
directWrite(&tmp, 1);
|
||||
m_scratch >>= BITS_SIZE<value_type>;
|
||||
m_scratchBits -= BITS_SIZE<value_type>;
|
||||
|
||||
value >>= BITS_SIZE<value_type>;
|
||||
}
|
||||
bitsLeft -= bits;
|
||||
}
|
||||
void align() {
|
||||
if ( m_scratchBits )
|
||||
writeBitsInternal(value_type{}, BITS_SIZE<value_type> - m_scratchBits);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
@@ -121,6 +111,26 @@ private:
|
||||
std::copy_n(reinterpret_cast<const value_type *>(v), bytesSize, _buf.data()+pos);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void writeBitsInternal(const T& v, size_t size) {
|
||||
auto value = v;
|
||||
auto bitsLeft = size;
|
||||
while (bitsLeft > 0) {
|
||||
auto bits = std::min(bitsLeft, BITS_SIZE<value_type>);
|
||||
m_scratch |= static_cast<SCRATCH_TYPE>( value ) << m_scratchBits;
|
||||
m_scratchBits += bits;
|
||||
if ( m_scratchBits >= BITS_SIZE<value_type> ) {
|
||||
auto tmp = static_cast<value_type>(m_scratch & bufTypeMask);
|
||||
directWrite(&tmp, 1);
|
||||
m_scratch >>= BITS_SIZE<value_type>;
|
||||
m_scratchBits -= BITS_SIZE<value_type>;
|
||||
|
||||
value >>= BITS_SIZE<value_type>;
|
||||
}
|
||||
bitsLeft -= bits;
|
||||
}
|
||||
}
|
||||
|
||||
const value_type bufTypeMask = 0xFF;
|
||||
using SCRATCH_TYPE = typename BIGGER_TYPE<value_type>::type;
|
||||
std::vector<value_type>& _buf;
|
||||
|
||||
@@ -48,14 +48,14 @@ struct BIGGER_TYPE<char> {
|
||||
typedef int16_t type;
|
||||
};
|
||||
|
||||
template <size_t SIZE, typename T>
|
||||
constexpr size_t DEFAULT_OR_SIZE =SIZE == 0 ? sizeof(T) : SIZE;
|
||||
template <typename T>
|
||||
constexpr size_t ARITHMETIC_OR_ENUM_SIZE = std::is_arithmetic<T>::value || std::is_enum<T>::value ? sizeof(T) : 0;
|
||||
|
||||
template <size_t SIZE>
|
||||
struct ProcessAnyType {
|
||||
template <typename S, typename T>
|
||||
static void serialize(S& s, T&& v) {
|
||||
s.template value<SIZE>(v);
|
||||
s.template value<SIZE>(std::forward<T>(v));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,13 +8,14 @@
|
||||
#include <array>
|
||||
#include <stack>
|
||||
#include <algorithm>
|
||||
#include "Common.h"
|
||||
#include "Deserializer.h"
|
||||
|
||||
template<typename Reader, typename TObj>
|
||||
class DeltaDeserializer {
|
||||
public:
|
||||
DeltaDeserializer(Reader& w, const TObj& oldObj, const TObj& newObj)
|
||||
:_reader{w},
|
||||
DeltaDeserializer(Reader& r, const TObj& oldObj, const TObj& newObj)
|
||||
:_deserializer{r},
|
||||
_reader{r},
|
||||
_oldObj{oldObj},
|
||||
_newObj{newObj},
|
||||
_objMemPos(std::deque<ObjectMemoryPosition>(1, ObjectMemoryPosition{oldObj, newObj})),
|
||||
@@ -40,7 +41,10 @@ public:
|
||||
|
||||
template<typename T>
|
||||
DeltaDeserializer& text(T&& str) {
|
||||
return container(str, [this](auto& v) { value<1>(v);});
|
||||
if (getChangedState(str)) {
|
||||
_deserializer.text(str);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +84,7 @@ public:
|
||||
DeltaDeserializer& container(T& obj, Fnc&& fnc) {
|
||||
if(getChangedState(obj)) {
|
||||
size_t newSize{};
|
||||
_reader.template readBits<32>(newSize);
|
||||
_reader.readBits(newSize, 32);
|
||||
if (!_isNewElement) {
|
||||
auto old = *_objMemPos.top().getOldObjectField(obj);
|
||||
if (old.size() != newSize)
|
||||
@@ -96,6 +100,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
Deserializer<Reader> _deserializer;
|
||||
Reader& _reader;
|
||||
|
||||
const TObj& _oldObj;
|
||||
@@ -157,24 +162,26 @@ private:
|
||||
|
||||
bool readChangedState() {
|
||||
unsigned char res{};
|
||||
_reader.template readBits<1>(res);
|
||||
_reader.readBits(res, 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
size_t readIndexOffset() {
|
||||
//special case, if items are updated sequentialy
|
||||
unsigned char tmp{};
|
||||
_reader.template readBits<1>(tmp);
|
||||
_reader.readBits(tmp, 1);
|
||||
if (tmp) {
|
||||
return 0u;
|
||||
}
|
||||
else {
|
||||
size_t res{};
|
||||
_reader.template readBits<1>(tmp);
|
||||
_reader.readBits(tmp, 1);
|
||||
if (tmp > 0)
|
||||
_reader.template readBits<4>(res);
|
||||
_reader.readBits(res, 4);
|
||||
else
|
||||
_reader.template readBits<32>(res);
|
||||
_reader.readBits(res, 32);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,13 +8,14 @@
|
||||
#include <array>
|
||||
#include <stack>
|
||||
#include <algorithm>
|
||||
#include "Common.h"
|
||||
#include "Serializer.h"
|
||||
|
||||
template<typename Writter, typename TObj>
|
||||
class DeltaSerializer {
|
||||
public:
|
||||
DeltaSerializer(Writter& w, const TObj& oldObj, const TObj& newObj)
|
||||
:_writter{w},
|
||||
:_serializer{w},
|
||||
_writter{w},
|
||||
_oldObj{oldObj},
|
||||
_newObj{newObj},
|
||||
_objMemPos(std::deque<ObjectMemoryPosition>(1, ObjectMemoryPosition{oldObj, newObj})),
|
||||
@@ -42,7 +43,11 @@ public:
|
||||
|
||||
template<typename T>
|
||||
DeltaSerializer& text(T&& str) {
|
||||
return container(str, [this](auto& v) { value<1>(v);});
|
||||
if(setChangedState(str)) {
|
||||
_serializer.text(str);
|
||||
}
|
||||
return *this;
|
||||
|
||||
}
|
||||
|
||||
template<typename T, size_t N, typename Fnc>
|
||||
@@ -79,7 +84,7 @@ public:
|
||||
template <typename T, typename Fnc>
|
||||
DeltaSerializer& container(T&& obj, Fnc&& fnc) {
|
||||
if(setChangedState(obj)) {
|
||||
_writter.template writeBits<32>(obj.size());
|
||||
_writter.writeBits(obj.size(), 32);
|
||||
if (!_isNewElement) {
|
||||
auto old = *_objMemPos.top().getOldObjectField(obj);
|
||||
processContainer(std::begin(old), std::end(old), std::begin(obj), std::end(obj), std::forward<Fnc>(fnc));
|
||||
@@ -92,6 +97,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
Serializer<Writter> _serializer;
|
||||
Writter& _writter;
|
||||
const TObj& _oldObj;
|
||||
const TObj& _newObj;
|
||||
@@ -159,21 +165,21 @@ private:
|
||||
}
|
||||
|
||||
void writeChangedState(bool state) {
|
||||
_writter.template writeBits<1>(state ? 1u : 0u);
|
||||
_writter.writeBits(state ? 1u : 0u, 1);
|
||||
}
|
||||
|
||||
void writeIndexOffset(const size_t offset) {
|
||||
//special case, if items are updated sequentialy
|
||||
if (offset == 0) {
|
||||
_writter.template writeBits<1>(1u);
|
||||
_writter.writeBits(1u, 1);
|
||||
} else {
|
||||
_writter.template writeBits<1>(0u);
|
||||
_writter.writeBits(0u, 1);
|
||||
auto smallOffset = offset < 16;
|
||||
_writter.template writeBits<1>(smallOffset ? 1u : 0u);
|
||||
_writter.writeBits(smallOffset ? 1u : 0u, 1);
|
||||
if (smallOffset)
|
||||
_writter.template writeBits<4>(offset);
|
||||
_writter.writeBits(offset, 4);
|
||||
else
|
||||
_writter.template writeBits<32>(offset);
|
||||
_writter.writeBits(offset, 32);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -103,9 +103,7 @@ public:
|
||||
decltype(obj.size()) size{};
|
||||
readLength(size);
|
||||
obj.resize(size);
|
||||
using VType = typename T::value_type;
|
||||
constexpr auto VSIZE = std::is_arithmetic<VType>::value || std::is_enum<VType>::value ? sizeof(VType) : 0;
|
||||
procContainer<VSIZE>(obj);
|
||||
procContainer<ARITHMETIC_OR_ENUM_SIZE<typename T::value_type>>(obj);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -130,8 +128,7 @@ public:
|
||||
|
||||
template<typename T, size_t N>
|
||||
Deserializer & array(std::array<T,N> &arr) {
|
||||
constexpr auto VSIZE = std::is_arithmetic<T>::value || std::is_enum<T>::value ? sizeof(T) : 0;
|
||||
procContainer<VSIZE>(arr);
|
||||
procContainer<ARITHMETIC_OR_ENUM_SIZE<T>>(arr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -145,19 +142,39 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<size_t VSIZE, typename T, size_t N>
|
||||
Deserializer& array(T (&arr)[N]) {
|
||||
procCArray<VSIZE>(arr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
Deserializer& array(T (&arr)[N]) {
|
||||
procCArray<ARITHMETIC_OR_ENUM_SIZE<T>>(arr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
Reader& _reader;
|
||||
void readLength(size_t& size) {
|
||||
size = {};
|
||||
_reader.template readBits<32>(size);
|
||||
_reader.readBits(size, 32);
|
||||
}
|
||||
template <size_t VSIZE, typename T>
|
||||
void procContainer(T&& obj) {
|
||||
//todo could be improved for arithmetic types in contiguous containers (std::vector, std::array) (keep in mind std::vector<bool> specialization)
|
||||
for (auto& v: obj)
|
||||
ProcessAnyType<VSIZE>::serialize(*this, v);
|
||||
};
|
||||
|
||||
template <size_t VSIZE, typename T, size_t N>
|
||||
void procCArray(T (&arr)[N]) {
|
||||
//todo could be improved for arithmetic types
|
||||
T* end = arr + N;
|
||||
for (T* it = arr; it != end; ++it)
|
||||
ProcessAnyType<VSIZE>::serialize(*this, *it);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -85,9 +85,7 @@ public:
|
||||
template <typename T>
|
||||
Serializer& container(const T& obj) {
|
||||
writeLength(obj.size());
|
||||
using VType = typename T::value_type;
|
||||
constexpr auto VSIZE = std::is_arithmetic<VType>::value || std::is_enum<VType>::value ? sizeof(VType) : 0;
|
||||
procContainer<VSIZE>(obj);
|
||||
procContainer<ARITHMETIC_OR_ENUM_SIZE<typename T::value_type>>(obj);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -112,8 +110,7 @@ public:
|
||||
|
||||
template<typename T, size_t N>
|
||||
Serializer & array(const std::array<T,N> &arr) {
|
||||
constexpr auto VSIZE = std::is_arithmetic<T>::value || std::is_enum<T>::value ? sizeof(T) : 0;
|
||||
procContainer<VSIZE>(arr);
|
||||
procContainer<ARITHMETIC_OR_ENUM_SIZE<T>>(arr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -127,26 +124,40 @@ public:
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
template<size_t VSIZE, typename T, size_t N>
|
||||
Serializer& array(const T (&arr)[N]) {
|
||||
// const T* end = arr + N;
|
||||
// for (const T* tmp = arr; tmp != end; ++tmp)
|
||||
// fnc(*tmp);
|
||||
procCArray<VSIZE>(arr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
Serializer& array(const T (&arr)[N]) {
|
||||
procCArray<ARITHMETIC_OR_ENUM_SIZE<T>>(arr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
Writter& _writter;
|
||||
void writeLength(const size_t size) {
|
||||
_writter.template writeBits<32>(size);
|
||||
_writter.writeBits(size, 32);
|
||||
}
|
||||
|
||||
template <size_t VSIZE, typename T>
|
||||
void procContainer(T&& obj) {
|
||||
//todo could be improved for arithmetic types in contiguous containers (std::vector, std::array) (keep in mind std::vector<bool> specialization)
|
||||
for (auto& v: obj)
|
||||
ProcessAnyType<VSIZE>::serialize(*this, v);
|
||||
};
|
||||
|
||||
template <size_t VSIZE, typename T, size_t N>
|
||||
void procCArray(T (&arr)[N]) {
|
||||
//todo could be improved for arithmetic types
|
||||
const T* end = arr + N;
|
||||
for (const T* it = arr; it != end; ++it)
|
||||
ProcessAnyType<VSIZE>::serialize(*this, *it);
|
||||
};
|
||||
|
||||
template <size_t VSIZE, typename T>
|
||||
void procText(const T* str, size_t size) {
|
||||
writeLength(size);
|
||||
|
||||
@@ -39,11 +39,11 @@ TEST(BufferBitsOperations, WriteAndReadBits) {
|
||||
std::vector<uint8_t> buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBits<aBITS>(data.a);
|
||||
bw.writeBits<bBITS>(data.b);
|
||||
bw.writeBits<cBITS>(data.c);
|
||||
bw.writeBits<dBITS>(data.d);
|
||||
bw.writeBits<eBITS>(data.e);
|
||||
bw.writeBits(data.a, aBITS);
|
||||
bw.writeBits(data.b, bBITS);
|
||||
bw.writeBits(data.c, cBITS);
|
||||
bw.writeBits(data.d, dBITS);
|
||||
bw.writeBits(data.e, eBITS);
|
||||
bw.flush();
|
||||
auto bytesCount = ((aBITS + bBITS + cBITS + dBITS + eBITS) / 8) +1 ;
|
||||
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(bytesCount));
|
||||
@@ -51,11 +51,11 @@ TEST(BufferBitsOperations, WriteAndReadBits) {
|
||||
BufferReader br{buf};
|
||||
IntegralUnsignedTypes res;
|
||||
|
||||
br.readBits<aBITS>(res.a);
|
||||
br.readBits<bBITS>(res.b);
|
||||
br.readBits<cBITS>(res.c);
|
||||
br.readBits<dBITS>(res.d);
|
||||
br.readBits<eBITS>(res.e);
|
||||
br.readBits(res.a, aBITS);
|
||||
br.readBits(res.b, bBITS);
|
||||
br.readBits(res.c, cBITS);
|
||||
br.readBits(res.d, dBITS);
|
||||
br.readBits(res.e, eBITS);
|
||||
|
||||
EXPECT_THAT(res.a, Eq(data.a));
|
||||
EXPECT_THAT(res.b, Eq(data.b));
|
||||
@@ -70,7 +70,7 @@ TEST(BufferBitsOperations, WhenFinishedFlushWriter) {
|
||||
std::vector<uint8_t> buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBits<2>(0xFFu);
|
||||
bw.writeBits(3u, 2);
|
||||
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(0));
|
||||
bw.flush();
|
||||
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(1));
|
||||
@@ -84,24 +84,62 @@ TEST(BufferBitsOperations, BufferSizeIsCountedPerByteNotPerBit) {
|
||||
std::vector<uint8_t> buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBits<2>(0xFFu);
|
||||
bw.writeBits(7u,3);
|
||||
bw.flush();
|
||||
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(1));
|
||||
|
||||
//read from buffer
|
||||
BufferReader br{buf};
|
||||
unsigned tmp;
|
||||
EXPECT_THAT(br.readBits<4>(tmp), Eq(true));
|
||||
EXPECT_THAT(br.readBits<2>(tmp), Eq(true));
|
||||
EXPECT_THAT(br.readBits<2>(tmp), Eq(true));
|
||||
EXPECT_THAT(br.readBits<2>(tmp), Eq(false));
|
||||
EXPECT_THAT(br.readBits(tmp,4), Eq(true));
|
||||
EXPECT_THAT(br.readBits(tmp,2), Eq(true));
|
||||
EXPECT_THAT(br.readBits(tmp,2), Eq(true));
|
||||
EXPECT_THAT(br.readBits(tmp,2), Eq(false));
|
||||
|
||||
//part of next byte
|
||||
BufferReader br1{buf};
|
||||
EXPECT_THAT(br1.readBits<2>(tmp), Eq(true));
|
||||
EXPECT_THAT(br1.readBits<7>(tmp), Eq(false));
|
||||
EXPECT_THAT(br1.readBits(tmp,2), Eq(true));
|
||||
EXPECT_THAT(br1.readBits(tmp,7), Eq(false));
|
||||
|
||||
//bigger than byte
|
||||
BufferReader br2{buf};
|
||||
EXPECT_THAT(br2.readBits<9>(tmp), Eq(false));
|
||||
EXPECT_THAT(br2.readBits(tmp,9), Eq(false));
|
||||
}
|
||||
|
||||
TEST(BufferBitsOperations, WhenAlignedFlushHasNoEffect) {
|
||||
//setup data
|
||||
|
||||
//create and write to buffer
|
||||
std::vector<uint8_t> buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
bw.writeBits(3u, 2);
|
||||
bw.align();
|
||||
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(1));
|
||||
bw.flush();
|
||||
EXPECT_THAT(std::distance(buf.begin(), buf.end()), Eq(1));
|
||||
}
|
||||
|
||||
TEST(BufferBitsOperations, AlignMustWriteZerosBits) {
|
||||
//setup data
|
||||
|
||||
//create and write to buffer
|
||||
std::vector<uint8_t> buf;
|
||||
BufferWriter bw{buf};
|
||||
|
||||
//write 2 bits and align
|
||||
bw.writeBits(3u, 2);
|
||||
bw.align();
|
||||
|
||||
unsigned char tmp;
|
||||
BufferReader br1{buf};
|
||||
br1.readBits(tmp,2);
|
||||
//read aligned bits
|
||||
EXPECT_THAT(br1.readBits(tmp,6), Eq(true));
|
||||
EXPECT_THAT(tmp, Eq(0));
|
||||
|
||||
BufferReader br2{buf};
|
||||
//read 2 bits
|
||||
br2.readBits(tmp,2);
|
||||
EXPECT_THAT(br2.align(), Eq(true));
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Created by fraillt on 17.2.7.
|
||||
//
|
||||
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "SerializationTestUtils.h"
|
||||
#include <numeric>
|
||||
#include <deque>
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// Created by fraillt on 17.2.9.
|
||||
//
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "SerializationTestUtils.h"
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
|
||||
using testing::ContainerEq;
|
||||
using testing::Eq;
|
||||
@@ -71,3 +72,68 @@ TEST(SerializeFSArrayStdArray, CustomFunctionThatSerializesAnEmptyByteEveryEleme
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(src.size() * (MyStruct1::SIZE + sizeof(char))));
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
}
|
||||
|
||||
|
||||
TEST(SerializeFSArrayCArray, ArithmeticValues) {
|
||||
SerializationContext ctx;
|
||||
int src[4]{5,9,15,-459};
|
||||
int res[4]{};
|
||||
|
||||
ctx.createSerializer().array(src);
|
||||
ctx.createDeserializer().array(res);
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(std::extent<decltype(src)>::value * sizeof(int)));
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
|
||||
}
|
||||
|
||||
TEST(SerializeFSArrayCArray, ArithmeticValuesSettingValueSizeExplicitly) {
|
||||
SerializationContext ctx;
|
||||
int src[4]{5,9,15,-459};
|
||||
int res[4]{};
|
||||
|
||||
ctx.createSerializer().array<sizeof(int)>(src);
|
||||
ctx.createDeserializer().array<sizeof(int)>(res);
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(std::extent<decltype(src)>::value * sizeof(int)));
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
|
||||
}
|
||||
|
||||
TEST(SerializeFSArrayCArray, CompositeTypes) {
|
||||
SerializationContext ctx;
|
||||
MyStruct1 src[]{
|
||||
MyStruct1{0,1}, MyStruct1{2,3}, MyStruct1{4,5}, MyStruct1{6,7},
|
||||
MyStruct1{8,9}, MyStruct1{11,34}, MyStruct1{5134,1532}};
|
||||
MyStruct1 res[7]{};
|
||||
|
||||
ctx.createSerializer().array(src);
|
||||
ctx.createDeserializer().array(res);
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(std::extent<decltype(src)>::value * MyStruct1::SIZE));
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
}
|
||||
//
|
||||
//
|
||||
TEST(SerializeFSArrayCArray, CustomFunctionThatSerializesAnEmptyByteEveryElement) {
|
||||
SerializationContext ctx;
|
||||
MyStruct1 src[]{
|
||||
MyStruct1{0,1}, MyStruct1{2,3}, MyStruct1{4,5}, MyStruct1{6,7},
|
||||
MyStruct1{8,9}, MyStruct1{11,34}, MyStruct1{5134,1532}};
|
||||
MyStruct1 res[7]{};
|
||||
|
||||
|
||||
auto ser = ctx.createSerializer();
|
||||
ser.array(src, [&ser](auto& v) {
|
||||
char tmp{};
|
||||
ser.object(v).value(tmp);
|
||||
});
|
||||
auto des = ctx.createDeserializer();
|
||||
des.array(res, [&des](auto& v) {
|
||||
char tmp{};
|
||||
des.object(v).value(tmp);
|
||||
});
|
||||
|
||||
EXPECT_THAT(ctx.getBufferSize(), Eq(std::extent<decltype(src)>::value * (MyStruct1::SIZE + sizeof(char))));
|
||||
EXPECT_THAT(res, ContainerEq(src));
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
#ifndef BITSERY_SERIALIZERTESTS_H
|
||||
#define BITSERY_SERIALIZERTESTS_H
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <Deserializer.h>
|
||||
#include <BufferReader.h>
|
||||
#include "BufferWriter.h"
|
||||
#include "Serializer.h"
|
||||
#include <memory>
|
||||
|
||||
struct MyStruct1 {
|
||||
MyStruct1(int v1, int v2):i1{v1}, i2{v2} {}
|
||||
@@ -19,7 +19,7 @@ struct MyStruct1 {
|
||||
bool operator == (const MyStruct1& rhs) const {
|
||||
return i1 == rhs.i1 && i2 == rhs.i2;
|
||||
}
|
||||
static constexpr size_t SIZE = sizeof(i1) + sizeof(i2);
|
||||
static constexpr size_t SIZE = sizeof(MyStruct1::i1) + sizeof(MyStruct1::i2);
|
||||
};
|
||||
|
||||
SERIALIZE(MyStruct1) {
|
||||
@@ -41,7 +41,7 @@ struct MyStruct2 {
|
||||
bool operator == (const MyStruct2& rhs) const {
|
||||
return e1 == rhs.e1 && s1 == rhs.s1;
|
||||
}
|
||||
static constexpr size_t SIZE = MyStruct1::SIZE + sizeof(e1);
|
||||
static constexpr size_t SIZE = MyStruct1::SIZE + sizeof(MyStruct2::e1);
|
||||
};
|
||||
|
||||
SERIALIZE(MyStruct2) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Created by fraillt on 17.2.7.
|
||||
//
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include "SerializationTestUtils.h"
|
||||
using namespace testing;
|
||||
|
||||
@@ -31,7 +32,7 @@ TEST(SerializerText, WhenSizeOfTypeNotEqualsOneThenSetSizeExplicitly) {
|
||||
EXPECT_THAT(res, ContainerEq(t1));
|
||||
}
|
||||
|
||||
TEST(SerializerText, BasicStringAsBufferIsThreadedAsNullTerminatedString) {
|
||||
TEST(SerializerText, BasicStringUseSizeMethodNotNullterminatedLength) {
|
||||
SerializationContext ctx;
|
||||
std::wstring t1(L"some random text\0xxxxxx", 20);
|
||||
std::wstring wres;
|
||||
@@ -44,21 +45,21 @@ TEST(SerializerText, BasicStringAsBufferIsThreadedAsNullTerminatedString) {
|
||||
EXPECT_THAT(wres.size(), Eq(t1.size()));
|
||||
EXPECT_THAT(wres.size(), Gt(std::char_traits<std::wstring::value_type>::length(t1.data())));
|
||||
|
||||
// SerializationContext ctx2;
|
||||
// std::string t2("\0no one cares what is there", 10);
|
||||
// std::string res;
|
||||
// ctx2.createSerializer().text(t2);
|
||||
// ctx2.createDeserializer().text(res);
|
||||
//
|
||||
// EXPECT_THAT(res, StrNe(t2));
|
||||
// EXPECT_THAT(res.size(), Eq(0));
|
||||
//
|
||||
// SerializationContext ctx3;
|
||||
// std::string t3("never ending buffer that doesnt fit in this string", 10);
|
||||
// ctx3.createSerializer().text(t3);
|
||||
// ctx3.createDeserializer().text(res);
|
||||
// EXPECT_THAT(res, StrEq(t3));
|
||||
// EXPECT_THAT(res.size(), Eq(10));
|
||||
SerializationContext ctx2;
|
||||
std::string t2("\0no one cares what is there", 10);
|
||||
std::string res;
|
||||
ctx2.createSerializer().text(t2);
|
||||
ctx2.createDeserializer().text(res);
|
||||
|
||||
EXPECT_THAT(res, StrEq(t2));
|
||||
EXPECT_THAT(res.size(), Eq(t2.size()));
|
||||
|
||||
SerializationContext ctx3;
|
||||
std::string t3("never ending buffer that doesnt fit in this string", 10);
|
||||
ctx3.createSerializer().text(t3);
|
||||
ctx3.createDeserializer().text(res);
|
||||
EXPECT_THAT(res, StrEq(t3));
|
||||
EXPECT_THAT(res.size(), Eq(10));
|
||||
}
|
||||
|
||||
const int CARR_LENGTH = 10;
|
||||
|
||||
@@ -137,7 +137,7 @@ TEST(DeltaSerializer, GeneralConceptTest) {
|
||||
yNew.arr[2] = 0xFFFFFFFF;
|
||||
yNew.carr[1] = 0xFFFFFFFF;
|
||||
yNew.s = "labas dienaABC";
|
||||
yNew.vx[0].s = "very nigga";
|
||||
yNew.vx[0].s = "very opapa";
|
||||
yNew.vx[1].s = "bla";
|
||||
yNew.vx.push_back(X{ 3 });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user