Can I use a user defined structure in RTMaps?

Yes, absolutely! Inputs and outputs are not bounded to RTMaps types, you can use your own structure in there. This article shows how to do it.

Simple structure

Let's suppose you want to use this structure:

#pragma pack(push,1)

struct Foo { MAPSInt32 m_age; MAPSInt32 m_note; };

#pragma pack(pop)

NB: note the #pragma directives which are used to disable memory alignment for the compiler. This way the instances of such structure can be handled as a binary blobs (in files, over communication channels, etc.) whatever the platform and compiler settings and versions.

Using the MAPS_OUTPUT_USER_STRUCTURE or MAPS_OUTPUT_USER_STRUCTURES_VECTOR, you can declare an output using this Foo structure easily. Here is an example of two outputs of type Foo, where the second is a 16-length vector of type Foo!

MAPS_OUTPUT_USER_STRUCTURE("foo", Foo)
MAPS_OUTPUT_USER_STRUCTURES_VECTOR("foo_vector", Foo, 16) 

In order to read this output type on an input pin of some downstream component, you have to define a special filter, using MAPS_FILTER_USER_STRUCTURE, then declare an input with such filter.

const MAPSTypeFilterBase MAPSFilterMyNewStructure = MAPS_FILTER_USER_STRUCTURE(Foo);
MAPS_BEGIN_INPUTS_DEFINITION(MyComponent) MAPS_INPUT("input",MAPSFilterMyNewStructure,MAPS::FifoReader) MAPS_END_INPUTS_DEFINITION

In the Core() method, the reading is implemented as follows:

MAPSIOElt* elt = StartReading(Input(0));
int vector_size = elt->VectorSize() / sizeof(Foo);
Foo* struct_in = static_cast<Foo*>(elt->Data());

The vector size returned by VectorSize is in bytes. As a consequence, we have to divide by the size of the structure to get the number of elements in the vector. Finally, accessing the element is done via a cast of the raw data to the right type : Foo.

Houston, we have a problem here ! What happen if we put a dynamically re-allocable element (like MAPSString) in our structure?

No, it won't work since all is considered static here. To achieve that, we have to allocate a memory area and manage it ourselves.

A step forward to the full freedom

Let's suppose we have this structure:

struct Foo2
{
    int                       m_age;
    MAPSString                m_name;
    MAPSArray<MAPSString>     m_friends;
};

This is almost the same structure as before, except that we have dynamic content here. Indeed, MAPSString has a non fixed size (uses memory that can be reallocated dynamically), and so does MAPSArray. So we can't use the same method as before, because MAPS_OUTPUT_USER_STRUCTURE uses the sizeof operator to determinate the size of the structure, and uses malloc to allocate the appropriate size, without calling the constructors. So, we need to do the allocation / deallocation ourselves. Fortunately, RTMaps has a ready to use class for this purpose: MAPSCustomDynamicStructureComponent<T, struct_name, output_name>. Let's see how it works!

Write a custom structure

Let's take an example: one component outputs data using the Foo2 structure, and one other component reads the Foo2 and splist the fields so that we can display them in a viewer.

Here is the class declaration:

namespace
{
	char c_output_name[] = "output"; // The output will be called "output"
	char c_struct_name[] = "Foo2"; // The structure is called "Foo2"
}
class MAPStest : public MAPSCustomDynamicStructureComponent<Foo2, c_struct_name, c_output_name> 
{
	MAPS_CHILD_COMPONENT_HEADER_CODE(MAPStest, parent_class)
private :
	// Your private code here
};

As you can see, we inherit from MAPSCustomDynamicStructureComponent, and we specify the template arguments:

  • The structure type: Foo2 here
  • The structure name: c_struct_name here.
  • The output name: c_output_name here.

Note that I put the two constants here in an anonymous namespace to avoid name conflicts, but you are not forced to do so.

In the implementation, just don't forget to call the mother class:

void MAPStest::Birth()
{
	parent_class::Birth();
}

A simple Core function to fill and send a Foo2 structure is simple to write:

void MAPStest::Core() 
{
	parent_class::Core();
	Rest(1000000);	
	MAPSIOElt* ioeltout = StartWriting(Output(0));
	Foo2* f = static_cast<Foo2*>(ioeltout->Data() );
	f->m_age = 24;
	f->m_name = "Roger";
	f->m_friends.Push("Richard");
	f->m_friends.Push("David");
	f->m_friends.Push("Nick");
	ioeltout->VectorSize() =  sizeof(Foo2); //For non-standard datatypes, by convention
	ioeltout->Timestamp() = MAPS::CurrentTime();
	StopWriting(ioeltout);
}

And the death member function:

void MAPStest::Death()
{
    parent_class::Death();
}

That's all!

Read a custom structure

The declaration of the input is not the same as for a simple, non-dynamic structure as described in the top of the document: it needs to use MAPS_FILTER_USER_DYNAMIC_STRUCTURE: 

const MAPSTypeFilterBase MAPSFilterMyNewStructure = MAPS_FILTER_USER_DYNAMIC_STRUCTURE(Foo2);

The input type filter declaration is the most significant difference with the non-dynamic case. The rest of the reader code is very similar. Below is the reader's inputs and outputs declaration:

MAPS_BEGIN_INPUTS_DEFINITION(MAPSread_test)
    MAPS_INPUT("input",MAPSFilterMyNewStructure,MAPS::FifoReader)
MAPS_END_INPUTS_DEFINITION
// Use the macros to declare the outputs
MAPS_BEGIN_OUTPUTS_DEFINITION(MAPSread_test)
    MAPS_OUTPUT("age",MAPS::Integer32,NULL,NULL,1)
	MAPS_OUTPUT("name",MAPS::TextAscii,NULL,NULL,20)
	MAPS_OUTPUT("friends",MAPS::TextAscii,NULL,NULL,10*256)
MAPS_END_OUTPUTS_DEFINITION

Now we have to read the input in the Core, and dispatch it in the appropriate output:

void MAPSread_test::Core() 
{
	MAPSIOElt* elt = StartReading(Input(0));
	Foo2* f = static_cast<Foo2*>(elt->Data());
	{
		MAPSIOElt* out_elt = StartWriting(Output("age"));
		out_elt->Integer32() = f->m_age;
		out_elt->Timestamp() = elt->Timestamp();
		StopWriting(out_elt);
	}
	{
		MAPSIOElt* out_elt = StartWriting(Output("name"));
		MAPSString& str = f->m_name;
		MAPS::Strcpy(out_elt->TextAscii(), str.Beginning());
		out_elt->VectorSize() = str.Length();
		out_elt->Timestamp() = elt->Timestamp();
		StopWriting(out_elt);
	}
	{
		MAPSIOElt* out_elt = StartWriting(Output("friends"));
		MAPSArray<MAPSString>& data = f->m_friends;
		MAPSString concat_chain;
		for (int i = 0; i < data.Size(); i++)
		{
			concat_chain += data[i] + ", ";
		}
		MAPS::Strcpy(out_elt->TextAscii(), concat_chain);
		out_elt->VectorSize() = concat_chain.Length();
		out_elt->Timestamp() = elt->Timestamp();
		StopWriting(out_elt);
	}
}

Don't forget to set the VectorSize!