Daqling Data Types

In this section the concepts of inner and outer datatypes will be used.

Outer datatype: Wrapper-types managing the lifetime of the inner datatype. Derivatives of the DataType class.

Inner datatype: Datatypes storing the actual data fragments. passed as template parameter to an outer Datatype.

Inner datatypes

Inner objects of type T, must have the following functions available:

  • size():size_t - returning the size of the object in bytes.
  • data():void* - returning a pointer to the memory section containing the inner objects data to be sent.
  • T(void* _data, size_t _size) - constructor initializing the object with the data contained in the memory block pointed to by _data with the size _size.
  • T() - default constructor.

Outer datatypes

The DAQling DataType class provides a common abstract baseclass to use for all types of datastructures, that is to be used along with the DAQling connections and queues.

This class has three important features:

  • The queues and connections are based on this baseclass, and made to work with all properly implemented subclasses.
  • The Datatype class provides a detach() function, which releases ownership of the internal datastructure. Along with this function comes the free() function, which should manually be called after a detach() call to deallocate the internal data. If detach isn’t called, the destructor of the datatype will deallocate the internal memory.
  • The Datatype class provides the void * data() and size_t size() functions, which are used to give access to the memory chunk of the internal data. These are used by the specific connection implementations, to send the actual data between modules.

DAQling provides two generic standard Datatype subclasses. The DataFragment<T> and TemplatedSharedDataType<T>. These classes manages the lifetime of an inner T object.

DataFragment<T>

Data type made for holding a an object which is stored contiguous in memory.

The DataFragment is a wrapper that handles the lifetime issues of the inner object. Meanwhile the object is easily accessible, through pointer semantics (operator-> and get() functions).

TemplatedSharedDataType<T>

Roughly similar to DataFragment<T>, but stores the inner object in a std::shared_ptr.

This allows for light copying utilizing shared_ptr functionality, which can be used in cases where light copying can save resources, but you still want the object available afterwards, so std::move is not an option. A common case of this is when trying to send an object from the modules in a while-loop until the send() succeeds. Instead of copy-creating a new copy of the inner object and passing it to send(), repeating this until send() succeeds, copying the shared_ptr to the inner data is a better solution. This is done by first invoking the make_shared() method of the TemplatedSharedDataType, and afterwards using copy assignment/construction on the object.

The downside of this functionality is that the shared_ptr used to keep track of when the inner object can be safely deallocated, has increased overhead compared to the raw pointer implementation of DataFragment<T>. Therefore TemplatedSharedDataType<T> should only be used in cases where you can benefit from the shared_ptr semantics, and even then, there is no guarantee that this will be more efficient for your particular use case.

Class diagram of the DAQling Data Types:

Registration and use of parameterized DataTypes

To use a parameterized type of one of the subclasses, just create the type in a DAQling module, and pass it to the send or receive method of the daqling::core::ConnectionSubManager instance. E.g.:

struct myDataStruct{
public:
    myDataStruct(){};
    myDataStruct(void* data,size_t size):msg(data,data+size){}
    std::string msg;    
    inline size_t size() { return sizeof(msg);}
    inline void *data() { return this; }
};
auto msg = DataFragment<myDataStruct>(new myDataStruct());
msg->msg="Hello world";
uint channel_no = 0;
m_connections.send(channel_no,msg);

It is possible for developers to derive directly from DataType or one of the subclasses, to create custom data structures, or to extend these, to allow for conversion between a receiving and sending type for example.