Special features: Box messaging for developers

  • NB: Document updated for OpenViBE 0.18.0.

Introduction

Boxes in OpenViBE can be augmented with capabilities to pass arbirtrary messages between each other. These messages are processed immediately and can contain data of various types. This document describes the technical aspects of developing new boxes that utilize the messaging feature. For an introduction to the feature from the viewpoint of an user using Designer, please see the box messaging basics page.

Configuring a box for messaging

First, a box that wants to send or receive messages must be configured to do so in the box prototype (typically in the box class header). For this, IboxProto interface has flags that can be used to manage the message connections :

virtual OpenViBE::boolean getBoxPrototype(
                       	OpenViBE::Kernel::IBoxProto& rBoxAlgorithmPrototype) const
{

//To add a message input connector you need to specify its name
rBoxAlgorithmPrototype.addMessageInput(OpenViBE::CString("message input"));

//To add a message output connector you need to specify its name
rBoxAlgorithmPrototype.addMessageOutput(OpenViBE::CString("message output"));

//To be able to add message inputs 
rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_CanAddMessageInput);

//To be able to add message outputs 
rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_CanAddMessageOutput);

//To be able to modify message inputs
rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_CanModifyMessageInput);

//To be able to modify message outputs 
rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_CanModifyMessageOutput);

}

Sending a message

A message is an object that a sending box may request from the kernel during the scope of process() or processClock(). The box may add arbitrary number of [keyString,value] pairs to the message. The value must be either an unsigned integer, a float, a CString or a CMatrix. You can then send the message to a certain message output index by calling the box context sendMessage() function.

IMessage& message = boxContext->createMessage();

message->setValueCString(“Identifier”, “MyDarlingMessage”);
message->setValueIMatrix(“theData1”, l_oMyIMatrix);
message->setValueFloat64(“coefficient”, 1.4);
message->setValueUInt64(“integer”, 42);

bool ok = boxContext->sendMessage(message, outputIndex);
if(!ok) { // ... }; // Rats, something went wrong!

The box immediately sends the message before leaving the scope. During the call, the kernel forwards the message immediately, essentially causing a processMessage() call for each receiving box.

Call graph during sendMessage() Call graph during sendMessage()

Note that in order for a box to send a message to another box, the user must have the boxes appropriately connected in the scenario.

Receiving a message

As mentioned earlier, the kernel will call processMessage() of each receiving box. If you want your box to have message processing capabilities, you must implement the following member function.

virtual OpenViBE::boolean processMessage(const OpenViBE::Kernel::IMyMessage& msg, OpenViBE::uint32 inputIndex);

The content of the message is internally stored in four different containers, one for each type of value the message can hold. To retrieve a particular value, you must know the key under which it is stored.

You can also get the keys for each type of value by calling the getFirst[Type]Token() and then getNext[Type]Token() where [Type] may be Cstring, IMatrix, Float64 or Uint64.

bool processMessage(const IMessage &message, int inputIndex);
{
	//You can get the first key of the CString map by calling this function
	const CString *l_sKey = msg.getFirstCStringToken();
	//You can then go through all the keys by calling 	
	// getNextCStringToken(previousKey)
	//it will return NULL when reaching the end of the map
	while(l_sKey!=NULL)
	{
		l_sKey = msg.getNextCStringToken(*l_sKey);
		// use l_sKey to access the data ...
	}
	//You have similar function for the other maps : 
	const CString *l_sMatrixKey = msg.getFirstCMatrixToken();
	while(l_sMatrixKey!=NULL)
	{
		l_sMatrixKey = msg.getNextCMatrixToken(*l_sMatrixKey);
		// ...
	}
	const CString *l_sUint64Key = msg.getFirstUint64Token();
	while(l_sUint64Key!=NULL)
	{
		l_sUint64Key = msg.getNextUint64Token(*l_sUint64Key);
		// ...
	}
	const CString *l_sFloat64Key = msg.getFirstFloat64Token();
	while(l_sFloat64Key!=NULL)
	{
		l_sFloat64Key = msg.getNextFloat64Token(*l_sFloat64Key);
		// ...
	}
	//...
}

Once you have the key under which the information you are interested in is stored, you can access the corresponding data by calling getValue[Type]().

The boolean output value true of the getters indicates that the retrieval was successful. For Type Uint64 and Float64, the functions return the value in the output variable. For CString and IMatrix, the functions return a pointer to the value held by the message. If you wish to use the retrieved CString or IMatrix outside the scope of processMessage(), you must make your own copy of it, as the message will be deleted after leaving the scope.

In case of failure, integer and float output parameters will not be changed, and pointer outputs will be set to NULL.

Message receiving inside box code ( we assume the message received is the one sent in the ‘Sending a message’ example ):

bool processMessage(const IMessageWithData &message, int inputIndex);
{
	// Two communicating boxes can establish message identity in a way
	// the want, or alternatively, using inputIndex to identify the sender. 
	CString *id = NULL;
	bool ok = message.getValueCString(“Identifier”, &id);
	if(ok && id && *id!=CString("MyDarlingMessage")) {
		this->getLogManager() << LogLevel_Information << "Got spam.\n";
		return false;	// I didn't like it
	} else { // ... } 

	float64 coeff = 0;
	ok = message.getValueFloat64(“coefficient”, coeff);
	if(ok) { // ... } 

	const CMatrix* data = NULL;
	ok = message.getValueCMatrix(“theData”, &data);
	if(ok && data) { // ... } 

	// Do something with the values

	// note: messages are auto-deprecated and the ownership of the returned
	// pointers is not transferred. If the user wants to do something
	// with any of the message contents, he should copy them manually.
	
	return true; // if success
}

To help users not to make inappropriate message connections, processMessage() should return false in case the received message is a wrong one (i.e. doesn't contain the expected keys). Additionally, its good policy to write a note to the LogManager if this happens.

Please note that inside the processMessage() function, you will be unable to send further messages in order to avoid potential deadlocks.

Coding examples

Boxes Message Sender, Message Receiver and Message Spy can be taken as working examples of boxes using messaging. Remember to enable 'show unstable' in Designer to see them.

This entry was posted in Box plugins, Developer documentation, Documentation. Bookmark the permalink.