fpmas 1.6
Public Member Functions | Static Public Member Functions | List of all members
fpmas::io::datapack::BasicObjectPack< S > Class Template Reference

#include <datapack.h>

Public Member Functions

template<typename T >
 BasicObjectPack (const T &item)
 
template<typename T >
BasicObjectPack< S > & operator= (const T &item)
 
template<typename T >
std::size_t size () const
 
template<typename T >
std::size_t size (const T &item) const
 
const DataPackdata () const
 
std::size_t readOffset () const
 
void seekRead (std::size_t offset=0) const
 
std::size_t writeOffset () const
 
void seekWrite (std::size_t offset=0)
 
template<typename T >
void put (const T &item)
 
template<typename T >
get () const
 
void allocate (std::size_t size)
 
void expand (std::size_t size)
 
template<typename T >
void write (const T &item)
 
void write (const void *input_data, std::size_t count)
 
template<typename T >
void read (T &item) const
 
void read (void *output_data, std::size_t count) const
 
template<typename T >
check () const
 
template<typename T >
void check (T &item) const
 
DataPack dump ()
 
BasicObjectPack< S > extract (std::size_t size) const
 

Static Public Member Functions

static BasicObjectPack< S > parse (DataPack &&data_pack)
 
static BasicObjectPack< S > parse (const DataPack &data_pack)
 

Detailed Description

template<template< typename, typename Enable=void > class S>
class fpmas::io::datapack::BasicObjectPack< S >

Base object used to implement the FPMAS binary serialization technique.

The purpose of the BasicObjectPack is to provide an high level API to efficiently read/write data from/to a low level fpmas::api::communication::DataPack.

The BasicObjectPack is the container that provides access to the serialized data. Serialization / deserialization is ensured by the provided serializer S specializations.

The BasicObjectPack provides two serialization techniques.

  1. The Serializer technique is used by BasicObjectPack(const T&), operator=(const T&), put() and get(). In this case, the S<T>::to_datapack() and S<T>::from_datapack() methods are used to perform those serializations, so the corresponding S<T> specializations must be available.
  2. The native std::memcpy technique is used by read() and write(). This method is only valid for members that can safely be copied as raw binary data, i.e. scalar types or associated arrays.
Note
Examples presented here are based on the default ObjectPack implementation, i.e. BasicObjectPack<Serializer>, however the same considerations are valid for any custom Serializer implementation passed as the S template parameter.

allocate(), put() and get()

A BasicObjectPack can be manually allocated using the allocate() and size() methods. put() and get() can then be used to serialize and unserialize data using the Serializer specializations. Notice that specializations are already provided for fundamental types and other C++ standard types and containers.

using namespace fpmas::io::datapack;
int main() {
std::vector<int> vec {4, -12, 17};
std::string str = "Hello";
ObjectPack pack;
pack.allocate(pack.size(vec) + pack.size(str));
pack.put(vec);
pack.put(str);
std::cout << pack << std::endl;
std::cout << "vec: ";
for(auto& item : pack.get<std::vector<int>>())
std:cout << item << " ";
std::cout << std::endl;
std::cout << "str: " << pack.get<std::string>() << std::endl;
}
BasicObjectPack< Serializer > ObjectPack
Definition: datapack.h:1393

Expected output:

pack: 0x3 0 0 0 0 0 0 0 0x4 0 0 0 0xfff4 0xffff 0xffff 0xffff 0x11 0 0 0 0x5 0 0 0 0 0 0 0 0x48 0x65 0x6c 0x6c 0x6f
vec: 4 -12 17
str: Hello

The interest of the BasicObjectPack serialization technique is that the buffer can be allocated in a single std::malloc operation even for very complex data schemes, what is very efficient in terms of memory usage and execution time.

put() and get() must be called in the same order to retrieve data at the appropriate place. Indeed, a get() call automatically advances the internal cursor to point to the next piece of data. readOffset() and seekRead() can eventually be used to handle very specific cases.

In particular, it might be tempting to call get() several times in a constructor for example, but the following example will produce an unexpected behavior, since nothing guarantees in C++ that pack.get<int>() will be called before pack.get<std::string>() (see https://stackoverflow.com/questions/2934904/order-of-evaluation-in-c-function-parameters):

int i = 12;
std::string str = "hello";
pack.allocate(pack.size<int>() + pack.size(str));
pack.put(i);
pack.put(str);
MyData data = {pack.get<int>(), pack.get<std::string>()} // DO NOT DO THIS
const DataPack & data() const
Definition: datapack.h:390

MyData can be constructed as follows instead, to respect call orders:

int u_i = pack.get<int>();
std::string u_str = pack.get<std::string>();
MyData data = {u_i, u_str};

Constructor and operator=

The allocation operator can be performed automatically using the BasicObjectPack(const T&) constructor or the operator=(const T&).

int main() {
std::string str = "Hello world";
ObjectPack pack = str;
std::cout << "pack: " << pack << std::endl;
std::cout << "str: " << pack.get<std::string>() << std::endl;
}

Expected output:

pack: 0xb 0 0 0 0 0 0 0 0x48 0x65 0x6c 0x6c 0x6f 0x20 0x77 0x6f 0x72 0x6c 0x64
str: Hello world

The disadvantage of this method is that nothing can be added a priori to the BasicObjectPack once it as been assigned. But this method can be efficiently combined with put() and get() to define custom data serialization rules.

Custom Serializer

Custom serialization rules can be specified thanks to Serializer specializations. For any type T, the static methods specified in the Serializer documentation must be implemented.

using namespace fpmas::io::datapack;
struct Data {
std::unordered_map<int, std::string> map {{4, "hey"}, {2, "ho"}};
double x = 127.4;
};
namespace fpmas { namespace io { namespace datapack {
template<>
struct Serializer<Data> {
static std::size_t size(const ObjectPack& pack, const Data& item) {
// Computes the ObjectPack size required to serialize data
return pack.size(item.map) + pack.size(item.x);
}
static void to_datapack(ObjectPack& pack, const Data& item) {
// The buffer is pre-allocated, so there is no need to call
// allocate()
pack.put(item.map);
pack.put(item.x);
}
static Data from_datapack(const ObjectPack& pack) {
Data item;
item.map = pack.get<std::unordered_map<int, std::string>>();
item.x = pack.get<double>();
return item;
}
};
}}}
int main() {
Data item;
// The required size is automatically allocated thanks to the custom
// Serializer<Data>::size() method.
ObjectPack pack = item;
std::cout << "pack: " << pack << std::endl;
Data unserial_item = pack.get<Data>();
std::cout << "item.map: ";
for(auto& item : unserial_item.map)
std::cout << "(" << item.first << "," << item.second << ") ";
std::cout << std::endl;
std::cout << "item.x: " << unserial_item.x << std::endl;
}
std::size_t size() const
Definition: datapack.h:367
Definition: fpmas.cpp:3

Expected output:

pack: 0x2 0 0 0 0 0 0 0 0x2 0 0 0 0x2 0 0 0 0 0 0 0 0x68 0x6f 0x4 0 0 0 0x3 0 0 0 0 0 0 0 0x68 0x65 0x79 0xff9a 0xff99 0xff99 0xff99 0xff99 0xffd9 0x5f 0x40
item.map: (4,hey) (2,ho)
item.x: 127.4

Once a Serializer specialization has been specified for a type T, it can then be implicitly used to serialize compound types that use T. For example std::vector<Data> can be serialized. But even in this case, an ObjectPack pack = std::vector<Data>(...) operation only performs a single contiguous memory allocation, thanks to the size() method trick.

write() and read() operations

An read() / write() serialization technique can be used for types that can be directly copied using an std::memcpy operation. This is a low level feature that is not required in general use cases.

using namespace fpmas::io::datapack;
int main() {
std::uint64_t i = 23000;
char str[6] = "abcde";
ObjectPack pack;
pack.allocate(sizeof(i) + sizeof(str));
pack.write(i);
pack.write(str, 3);
std::cout << "pack: " << pack << std::endl;
std::uint64_t u_i;
char u_str[3];
pack.read(u_i);
pack.read(u_str, 3);
std::cout << "u_i: " << u_i << std::endl;
std::cout << "u_str: ";
for(std::size_t i = 0; i < 3; i++)
std::cout << u_str[i];
std::cout << std::endl;
}

Expected output:

pack: 0xffd8 0x59 0 0 0 0 0 0 0x61 0x62 0x63 0 0 0
u_i: 23000
u_str: abc
Note
The BasicObjectPack design is widely insprired by the nlohmann::basic_json class.
Template Parameters
Sserializer implementation (e.g.: Serializer, LightSerializer, JsonSerializer...)

Constructor & Destructor Documentation

◆ BasicObjectPack()

template<template< typename, typename Enable=void > class S>
template<typename T >
fpmas::io::datapack::BasicObjectPack< S >::BasicObjectPack ( const T &  item)
inline

Serializes item in a new BasicObjectPack.

The BasicObjectPack pack is allocated in a single operation, calling allocate(S<T>::size(*this, item)).

The data is then serialized into the BasicObjectPack using the S<T>::to_datapack(BasicObjectPack<S>&, const T&) specialization.

Parameters
itemitem to serialize

Member Function Documentation

◆ operator=()

template<template< typename, typename Enable=void > class S>
template<typename T >
BasicObjectPack< S > & fpmas::io::datapack::BasicObjectPack< S >::operator= ( const T &  item)
inline

Serializes item in the current BasicObjectPack.

Existing data is discarded, and the BasicObjectPack pack is reallocated in a single operation, calling allocate(S<T>::size(*this, item)).

The data is then serialized into the BasicObjectPack using the S<T>::to_datapack(BasicObjectPack<S>&, const T&) specialization.

Parameters
itemitem to serialize
Returns
reference to this BasicObjectPack

◆ size() [1/2]

template<template< typename, typename Enable=void > class S>
template<typename T >
std::size_t fpmas::io::datapack::BasicObjectPack< S >::size ( ) const
inline

Returns the buffer size required to serialize any instance of T.

This is usable only if the S<T>::size(const BasicObjectPack<S>&) specialization is available.

This method can be used only when the required buffer size does not depend on the instance of T to serialize. For example, size<std::uint64_t>() might always return 64, independently of the integer to serialize.

Returns
buffer size required to serialize any instance of T

◆ size() [2/2]

template<template< typename, typename Enable=void > class S>
template<typename T >
std::size_t fpmas::io::datapack::BasicObjectPack< S >::size ( const T &  item) const
inline

Returns the buffer size required to serialize an instance of T.

This method returns the result of the S<T>::size(const BasicObjectPack<S>&, const T&) specialization.

Parameters
itemitem to serialize
Returns
buffer size required to serialize an instance of T

◆ data()

template<template< typename, typename Enable=void > class S>
const DataPack & fpmas::io::datapack::BasicObjectPack< S >::data ( ) const
inline

Returns a reference to the internal DataPack, where serialized data is stored.

◆ readOffset()

template<template< typename, typename Enable=void > class S>
std::size_t fpmas::io::datapack::BasicObjectPack< S >::readOffset ( ) const
inline

Returns the current read cursor position, i.e. the index of the internal DataPack at which the next read() operation will start.

Returns
read cursor position

◆ seekRead()

template<template< typename, typename Enable=void > class S>
void fpmas::io::datapack::BasicObjectPack< S >::seekRead ( std::size_t  offset = 0) const
inline

Places the current read cursor to the specified offset.

Can be used in conjunction with readOffset() to perform several read() operation at the same offset.

Parameters
offsetnew read cursor position

◆ writeOffset()

template<template< typename, typename Enable=void > class S>
std::size_t fpmas::io::datapack::BasicObjectPack< S >::writeOffset ( ) const
inline

Returns the current write cursor position, i.e. the index of the internal DataPack at which the next write() operation will start.

Returns
write cursor position

◆ seekWrite()

template<template< typename, typename Enable=void > class S>
void fpmas::io::datapack::BasicObjectPack< S >::seekWrite ( std::size_t  offset = 0)
inline

Places the current write cursor to the specified offset.

Can be used in conjunction with writeOffset() to override a previous write() operation.

Parameters
offsetnew read cursor position

◆ put()

template<template< typename, typename Enable=void > class S>
template<typename T >
void fpmas::io::datapack::BasicObjectPack< S >::put ( const T &  item)
inline

Serializes item into the current BasicObjectPack using the S<T>::to_datapack(BasicObjectPack<S>&, const T&) specialization.

Parameters
itemitem to serialize

◆ get()

template<template< typename, typename Enable=void > class S>
template<typename T >
T fpmas::io::datapack::BasicObjectPack< S >::get ( ) const
inline

Unserializes data from the current BasicObjectPack, using the S<T>::from_datapack(const BasicObjectPack<S>&) specialization.

Template Parameters
Ttype of data to unserialize
Returns
unserialized object

◆ allocate()

template<template< typename, typename Enable=void > class S>
void fpmas::io::datapack::BasicObjectPack< S >::allocate ( std::size_t  size)
inline

Allocates size bytes in the internal DataPack buffer to perform write() operations.

Parameters
sizecount of bytes to allocate

◆ expand()

template<template< typename, typename Enable=void > class S>
void fpmas::io::datapack::BasicObjectPack< S >::expand ( std::size_t  size)
inline

Expands the internal DataPack buffer by size bytes.

The existing memory area is left unchanged, so that this method can safely be called from the serialization process (i.e. within to_datapack() methods).

Example workflow:

pack.allocate(16);
std::uint8_t i0 = 0x12;
pack.put(i0);
pack.expand(sizeof(std::uint32_t));
// The buffer size is now 16+32, and only the 8 first bytes are
// already written (pack.writeOffset() == 8)
// Writes data to the 32 next bytes
std::int32_t i1 = 0x12345678;
pack.put(i1);
// Writes data to the last 8 bytes
i0 = 0x34;
pack.put(i0);
Parameters
sizesize to allocate in addition to the current buffer size

◆ write() [1/2]

template<template< typename, typename Enable=void > class S>
template<typename T >
void fpmas::io::datapack::BasicObjectPack< S >::write ( const T &  item)
inline

Low level write operation of a scalar value or associated array into the internal buffer.

A call to this method assumes that the internal buffer as been allocated so that sizeof(T) bytes are available from the current writeOffset(). item is then copied using an std::memcpy operation, and the internal writeOffset() is incremented by sizeof(T).

Parameters
itemitem to write

◆ write() [2/2]

template<template< typename, typename Enable=void > class S>
void fpmas::io::datapack::BasicObjectPack< S >::write ( const void *  input_data,
std::size_t  count 
)
inline

Low level write operation of count bytes from input_data into the internal buffer.

A call to this method assumes that the internal buffer as been allocated so that count bytes are available from the current writeOffset(). Data in the range [input_data[0], input_data[count]) is then copied using an std::memcpy operation, and the internal writeOffset() is incremented by count.

Parameters
input_datainput buffer to copy data from
countcount of bytes to copy

◆ read() [1/2]

template<template< typename, typename Enable=void > class S>
template<typename T >
void fpmas::io::datapack::BasicObjectPack< S >::read ( T &  item) const
inline

Low level read operation of a scalar value or associated array from the internal buffer.

A call to this method assumes that sizeof(T) bytes are available in the internal buffer from the current readOffset(). Data is directly copied to &item using an std::memcpy operation, and the internal readOffset() is incremented by sizeof(T).

Parameters
itemitem to write

◆ read() [2/2]

template<template< typename, typename Enable=void > class S>
void fpmas::io::datapack::BasicObjectPack< S >::read ( void *  output_data,
std::size_t  count 
) const
inline

Low level read operation of count bytes into output_data from the internal buffer.

A call to this method assumes that count bytes are available in the internal buffer from the current readOffset(), and that output_data is properly allocated to receive count bytes. Data is directly copied to output_data using an std::memcpy operation, and the internal readOffset() is incremented by count.

Parameters
output_datainput buffer to copy data from
countcount of bytes to copy

◆ check() [1/2]

template<template< typename, typename Enable=void > class S>
template<typename T >
T fpmas::io::datapack::BasicObjectPack< S >::check ( ) const
inline

Constructs a new instance of T using the default constructor, and reads data into the new instance with check(T&).

Returns
read data

◆ check() [2/2]

template<template< typename, typename Enable=void > class S>
template<typename T >
void fpmas::io::datapack::BasicObjectPack< S >::check ( T &  item) const
inline

Calls read(T&) and replaces the internal read cursor at its original position.

Parameters
itemitem into which data is read

◆ dump()

template<template< typename, typename Enable=void > class S>
DataPack fpmas::io::datapack::BasicObjectPack< S >::dump ( )
inline

Returns by move the internal DataPack buffer for memory efficiency.

The current BasicObjectPack is left empty.

Returns
internal DataPack buffer

◆ parse() [1/2]

template<template< typename, typename Enable=void > class S>
static BasicObjectPack< S > fpmas::io::datapack::BasicObjectPack< S >::parse ( DataPack &&  data_pack)
inlinestatic

Constructs a new BasicObjectPack from the input DataPack, the is moved into the BasicObjectPack instance, for memory efficiency.

Parameters
data_packinput datapack
Returns
BasicObjectPack containing the input datapack

◆ parse() [2/2]

template<template< typename, typename Enable=void > class S>
static BasicObjectPack< S > fpmas::io::datapack::BasicObjectPack< S >::parse ( const DataPack data_pack)
inlinestatic

Constructs a new BasicObjectPack from the input DataPack.

Parameters
data_packinput datapack
Returns
BasicObjectPack containing the input datapack

◆ extract()

template<template< typename, typename Enable=void > class S>
BasicObjectPack< S > fpmas::io::datapack::BasicObjectPack< S >::extract ( std::size_t  size) const
inline

Extracts size bytes from the current BasicObjectPack pack and return them as a new BasicObjectPack.

The read cursor is incremented by size.

This can be useful to extract a value from the current BasicObjectPack for later unserialization.

Parameters
sizebyte count to extract
Returns
extracted BasicObjectPack

The documentation for this class was generated from the following file: