Time management: Wait and Sleep

In RTMaps you can wait for a duration or a timestamp. This is done via the MAPS::Sleep, MAPSModule::Rest or MAPSModule::Wait. All durations and timestamps are expressed in micro seconds.

  • MAPS::Sleep(delay_µs): pauses the current thread during the specified time. It is based on the real timebase (independent from the virtual RTMaps time).
  • MAPSModule::Rest(delay_µs): pauses the current thread during the specified time. This function is based on the RTMaps virtual timebase: if the VCR is replaying at 50% speed, a call to Rest(1000000) will pause for 2 real seconds.
  • MAPSModule::Wait(timestamp_µs): this function pauses the current thread until the specified date is reached. It is thus based on the RTMaps virtual timebase.
  • MAPS::CurrentTime(): returns the current date according to the RTMaps virtual timebase.
  • MAPS::Timestamp2String and MAPS::TimestampFromString are the necessary functions to use dates represented as MAPSString like hh:mm:ss.mmm

Thread management

Well, there are two ways of creating threads in RTMaps. They both are named CreateThread but not in the same scope : one is in the global namespace (MAPS::CreateThread) while the other is in the MAPSModule class.

If you plan to share resources between threads, we invite you to read this article : MAPSMutex

MAPSModule::CreateThread

Use MAPSModule::CreateThread when you are developing a RTMaps component and you want to execute a function in a dedicated thread. Here is the CreateThread prototype :

void CreateThread(MAPSThreadFunction);

where MapsThreadFunction is a typedef :

typedef void (MAPSModule::*MAPSThreadFunction)(void);

As you can see, the target function must be a member function (part of the MAPSModule class) without any argument.

Example : a Birth code that launch the member function PollData in a separated thread.

// The Birth() method
void MyComponent::Birth()
{
    m_appointment =  MAPS::CurrentTime();
    // other code
    CreateThread( (MAPSThreadFunction)(&MyComponent::PollData) );
}

where PollData is just a simple function that periodically get some data.

void MyComponent::PollData()
{
	while (!IsDying() )
	{
		m_appointment += m_sample_rate;
		Wait(m_appointment);
		AskForData();
	}
}

When the user shutdowns the diagram, Death is not called directly as usual. Indeed, the engine waits for the thread termination before calling Death. So here are two solutions to end your thread properly :

  • If your function on the thread has no blocking calls, but simply loop periodically, you can read the IsDying() boolean and exit when true. This is what we have done in the previous PollData function.
  • If your function has a blocking call, then the IsDying() function is almost useless since the blocking function might block forever. The solution is to use the Banzai() function which is called before Death() and perform the resource liberation from here.

MAPS::CreateThread

As opposed to MAPSModule::CreateThread, MAPS::CreateThread launches a thread without any control. If used inside a component, the user is responsible for managing the thread, Banzai() and Death() will be called respectively without waiting for any threads created this way.

MAPS::CreateThread can be useful if you want a code to be executed continuously and separated from the life of the component. But unless you are sure about what you you want to do, we hardly suggest you use MAPSModule::CreateThread instead.

Subtype properties

MAPS_PROPERTY allows to declare properties. But you can also define special subtypes thanks to the macro MAPS_PROPERTY_SUBTYPE.

The macro MAPS_PROPERTY_SUBTYPE has one more argument at the end: the subtype flag.

MAPS_PROPERTY_SUBTYPE(
    name,
    value,
    needs2BeInitialized,
    canBeChangedDuringExecution,
    subtype)

Here is a real example:

MAPS_PROPERTY_SUBTYPE(
    "color_on",
    MAPS_RGB(255,0,0),
    false,
    true, 
    MAPS::PropertySubTypeColor)

This is the exhaustive list of the RTMaps subtypes:

  • PropertySubTypeTime: represents a time. It has special values like "infinite" or "-1".
  • PropertySubTypeCode: represents a multi line code.
  • PropertySubTypeColor: represents a color (BGR). It is stored internally as a 32 bit integer. RTMaps displays a special button which spawns a color palette.
  • PropertySubTypeHexa: represents a normal integer, but with an hexadecimal display.
  • PropertySubTypeFile: represents a file. RTMaps displays a special button which shows a file browser.
  • PropertySubTypePath: same as PropertySubTypeFile, but for a folder.
  • PropertySubTypeMustExist: represents a file or folder that must exist. If the property is not empty, then RTMaps checks for its the existence of the file or folder when the diagram is loaded. If it does not exist, a dialog box is shown to set a new value.

Set or DirectSet ?

The virtual function Set (see Dynamic or Set ?) allows the program to receive property changes. But sometimes we have to avoid to trigger a callback. This is the role of DirectSet.

DirectSet : Preventing Set call

Set is called anytime a property is changed. But what if we have two correlated properties, one modifying the other and vice-versa. It could lead to an infinite loop! Happily, there is DirectSet !

Here is an example of two properties sampling time and frequency:

MAPS_BEGIN_PROPERTIES_DEFINITION(MyComponent)
    MAPS_PROPERTY("frequency",10.0,false,true)
    MAPS_PROPERTY("samplingTime",0.1,false,true)
MAPS_END_PROPERTIES_DEFINITION

Those two properties must be kept synchronized. Indeed, frequency = 1 / samplingTime. So if we modify the frequency we must update the sampling time and vice versa. Here is the set code :

void MyComponent::Set(MAPSProperty& p, MAPSFloat64 value)
{
	MAPSComponent::Set(p,value); // Call the parent class
	if (&p == &Property("frequency")) // Case frequency
	{
		DirectSet(Property("samplingTime"), 1/value);
	}
	else if (&p == &Property("samplingTime")) // Case sampling time
	{
		DirectSetProperty("frequency", 1/value);
	}
}

We are using DirectSet or DirectSetProperty (same behavior) here. If we have used Set or SetProperty, the program would have looped forever...

DirectSet : Setting a read only property

If you have a read-only property, DirectSet is the only way to modify it. For example, here is a read-only property :

MAPS_PROPERTY_READ_ONLY("dontTouchThis",2)

To modify it, you have to call DirectSet:

DirectSetProperty("dontTouchThis", 24);

MAPSMutex - interthread synchronization

When sharing resources across threads, the programmer must ensure that those resources are not accessed in writing mode at the same time. A mutex (acronym for MUTual EXclusion) is a program object that is created so that multiple threads can take turns sharing the same resource, such as access to a file.

In RTMaps, the mutex is called MAPSMutex. It has 3 member functions:

  • Lock : locks the mutex. If the mutex is free, then the current thread locks it and continue its normal execution. If the mutex is not free, then the current thread blocks and waits for the mutex to be released.
  • Release : releases the mutex. This function frees the mutex so that an other thread can lock it.
  • Reset : resets the mutex, leaving it in a free state. If you have reasons to think that the mutex is locked in a dead thread, you will have to Reset it to avoid a deadlock at the next Lock!

MAPSEvent - interthreads communication

MAPSEvent is an event-based mechanism to communicate between threads.

A MAPSEvent has two states : on or off. MAPSEvent has mainly two member functions:

  • Set : sets the event (on). Any threads waiting on this event will be "unlocked".
  • Reset : resets the internal state of the event (off). This function has to be called before any other Set, otherwise the next Set will not be taken into account. Indeed, the MAPSEvent state has to be set to "off" before it can become "on" again.

The MAPSModule has a member function Wait4Event, so that any component can wait for a particular MAPSEvent.

Wait4Event(m_event); // Wait for the m_event event !


MAPSEvent is thread safe, don't bother accessing it from different threads, and no need for mutexes !

Component “foo” refuses to die after 10 seconds...

When the user asks for shutdown, all components must quit peacefully. If a component is still running 10 seconds after shutdown, RTMaps terminates it and shows this error in the console. Of course, this component might be left in a unstable state.

The two possible reasons are:

  • Your component is blocked in the Core. Make sure you do not have a blocking call in progress.
  • You have a thread which is still alive. If you created a thread via MAPSModule::CreateThread, then RTMaps waits for its death before calling Death (see Thread management). You should terminate your thread as soon as you can when shutdown is asked.

Dynamic or Set?

In order to interact with properties, you can override the virtual function MAPSComponent::Dynamic() or MAPSModule::Set. But which one should you use, and in which case?

Dynamic

Dynamic is a virtual function called:

  • Just after the constructor (so when the component enters the diagram)
  • Whenever a property is changed in the component (design phase only)
  • Whenever a connection is wired / unwired (inputs and outputs)

Dynamic is never called while the diagram is running.

The Dynamic function allows to monitor properties or even to create or delete inputs, outputs and properties.

Monitor properties

Dynamic can control the value of a property. Indeed, if you want a value to be superior than a threshold or between two limits, you can use Dynamic to get the value and check its validity. For example, here is a code that checks if the "input" property is greater than 1:

void MyComponent::Dynamic()
{
	int nbInputs=(int)GetIntegerProperty("nbInputs");
	if (nbInputs < 1)
	{
		nbInputs = 1;
		ReportWarning("Number of inputs must be at least 1");
		DirectSetProperty("nbInputs", 1);
	}
	inputs.SetSize(nbInputs);
}

Remember that Dynamic is never called while the diagram is running. If you need to monitor a property while the diagram is running, use Set as described below.

Create inputs, outputs or properties

Dynamic allows to dynamically create inputs, outputs or properties via the function NewInput, New Output and NewProperty!

Here is an example of inputs creation on demand:

// Use the macros to declare the inputs
MAPS_BEGIN_INPUTS_DEFINITION(MAPSdata_viewer)
	MAPS_INPUT("static_input",MAPS::FilterFloat,MAPS::FifoReader)
	MAPS_INPUT("other_static_input",MAPS::FilterInteger,MAPS::FifoReader)
	MAPS_INPUT("dynamic_input",MAPS::FilterAny,MAPS::FifoReader)
MAPS_END_INPUTS_DEFINITION
// Use the macros to declare the properties
MAPS_BEGIN_PROPERTIES_DEFINITION(MAPSdata_viewer)
	MAPS_PROPERTY("nbInputs",1,false,false)
MAPS_END_PROPERTIES_DEFINITION
MAPS_COMPONENT_DEFINITION(MAPSdata_viewer,"data_viewer","1.0",128,
						  MAPS::Threaded,MAPS::Threaded,
						  2, //The first 2 properties will always be instantiated
						  0,
						  -1,
						  0)
void MyComponent::Dynamic()
{
	int nbInputs=(int)GetIntegerProperty("nbInputs");
	if (nbInputs==NbInputs()) 
	{
		DynamicConfirm(); // Nothing changed
	} 
	else 
	{
		if (nbInputs < 1)
		{
			nbInputs = 1;
			ReportWarning("Number of inputs must be at least 1");
		}
		myInputs.SetSize(nbInputs);
		for (int i=0;i<nbInputs;i++) 
		{
			MAPSStreamedString str;
			str << "input_" << i;
			myInputs[i]=&NewInput("dynamic_input", str);
		}
	}
}

The dynamic_input declaration is a pattern that will be reused on input creation, with a different name. The property nbInputs is a normal property as you have seen many before! Don't forget to set the number of static properties in the MAPS_COMPONENT_DEFINITION macro (see commented code above). This enables you to specify that the first n declared inputs will be static, i.e. automatically instantiated on component creation and always present.

In the above code, we start to test the nbInputs value relative to the previous one. If this value has not changed, we call DynamicConfirm to load the previous configuration. It means we have not touched anything, so RTMaps has just to rebuild inputs, outputs and properties as before the call to Dynamic. If we don't call DynamicConfirm, we have to create dynamic inputs outputs and properties. Why not, but it takes time for nothing so it's best to use DynamicConfirm.

The next part of the code tests the value of nbInputs. If it is valid (>1), then we set the array size of myInputs (declared as MAPSArray<MAPSInput*>) to the nbInputs size and then we call NewInput as many times as we need. Note that we pass a name string to NewInput so that the first input will be called input_1, the second input_2, etc. The myInputs array is used to store the references to the inputs so we can use them later, notably in StartReading functions.

The NewOutput and NewProperty functions work in the same way...

Set

Set is a virtual function that is called only when a property has changed, even during the diagram execution. Set has many prototypes corresponding to the property types it can monitor:

Set prototypeMonitored property type
virtual void Set (MAPSProperty&p, bool value) boolean
virtual void Set (MAPSProperty&p, MAPSInt64 value) integer or enum
virtual void Set (MAPSProperty&p, MAPSFloat64 value) float
virtual void Set (MAPSProperty&p, const MAPSString &value) string or enum
virtual void Set (MAPSProperty&p, const MAPSEnumStruct &enumStruct) enum

Example: monitoring a float property

If you want to monitor a specific property, you have to override the right function. Suppose we want to monitor this float property:

MAPS_PROPERTY("triggerLevel",0.0,false,true)

All we have to to is overriding the float Set prototype:

void MyComponent::Set(MAPSProperty& p, MAPSFloat64 value)
{
	MAPSComponent::Set(p,value); // Call parent class
	mTriggerLevel = GetFloatProperty("triggerLevel"); // Update internal value
}

The first thing to do is calling the parent class. Because in a virtual call, only the virtual function of the derived class (the real type actually) is called. Of course, if an RTMaps component inherits from another one, both deserve to be notified of a property change. So the parent implementation must be called. In our case, MAPSComponent::Set has to be called otherwise Dynamic won't be called in design mode and the new value will not be taken into account!

Next, you can do whatever you need with the new property value. In the above example, we store it in a member variable for quick access.

Another possibility is to check the value of the property and only confirm it (by calling MAPSComponent::Set) if the value is correct (e.g. inside an acceptable range).

Well, the other types work in the same way, you just have to choose the right Set prototype. The only exception is the enum, which can be called in several Set prototypes as explained hereunder.

Special case: the enum value

Suppose we have this property:

MAPS_PROPERTY_ENUM("triggerSlope","any|positive|negative",SLOPE_POSITIVE,false,true)

The enum (see Enum properties) is quite special, because it can be seen as a string, an int or an enum. So we have 3 callbacks to implement, with the value parameter representing different entities:

  • virtual void Set (MAPSProperty&p, MAPSInt64 value): value is the zero-based index of the selection in the enum list.
  • virtual void Set (MAPSProperty&p, const MAPSString &value): value is the full enum string in the following form:
     [number of choices|zero-based selected index|choice 0|choice 1|...|choice N]
  • virtual void Set (MAPSProperty&p, const MAPSEnumStruct &enumStruct): enumStruct is a MAPSEnumStruct type containing information about possible and selected choices.

 In the next example, we will retrieve the integer value of the enum (the zero-based index of the selected choice), in each Set:

void MyComponent::Set(MAPSProperty& p, MAPSInt64 value)
{
    MAPSComponent::Set(p,value);
    if (&p == &Property("triggerSlope") ) // Test if p is "triggerSlope"
    {
        mTriggerSlope = (int)value;
    }
    // Other code
}
void MyComponent::Set(MAPSProperty& p, const MAPSString& value)
{
    MAPSComponent::Set(p,value);
    mTriggerSlope = (int)GetIntegerProperty("triggerSlope");
}
void MyComponent::Set(MAPSProperty& p, const MAPSEnumStruct& enumStruct)
{
    MAPSComponent::Set(p,enumStruct);
    mTriggerSlope = (int)GetIntegerProperty("triggerSlope");
}

Note that in the case where multiple properties match the "Set" prototype, you will have to check which property has been modified. This is done via the if (&p == &Property("triggerSlope") ) test. In the other two Set callbacks, there is no other properties of the same type (MAPSString and MAPSenumStruct) so there is no need to test.

Conclusion

In conclusion, Dynamic and Set have common points and both allow to do the same things (monitor properties in design mode). But they also have differences: Dynamic allows to create inputs, outputs and properties dynamically according to a model, while Set allows to monitor properties even during the diagram execution.

Enum properties

Enum properties offer a limited set of choices to the user in a drop-down list. They are defined via the MAPS_PROPERTY_ENUM macro.

The MAPS_PROPERTY_ENUM macro has one more argument than the MAPS_PROPERTY macro:

MAPS_PROPERTY_ENUM(
    name, // Name of the property
    enumstr, // List of all possible values separated with "|"
    selected, // Zero-based index of the default property value
    needs2BeInitialized,
    canBeChangedDuringExecution)

Here is a real example of an enum property called triggerChannel:

MAPS_PROPERTY_ENUM("triggerChannel","A|B",0,false,true)

This property will be displayed in a drop-down list with two values: A and B.

To learn how intercept value changes of an enum property, see Dynamic or Set ?

UTF-8 in RTMaps

RTMaps supports Unicode and thus international character sets! The UTF-8 encoding is used internally. So if you want to interact with other API that may have different encoding policies, you will have to use the MAPSIconv class.

All the functions in RTMaps use UTF-8 encoding: the MAPSString class (see RTMaps string), the const char* strings, etc. UTF-8 is a very interesting encoding because it implements Unicode and it is backward compatible with ASCII. It means that as long as you use only ASCII characters (without any accents), you are fully compatible with UTF-8!

As encoding is not standard on all platforms and libraries, you will probably one day or another have to convert strings in other formats (system locale as Latin1 or UTF-16 or UTF-32). Happily, MAPSIconv allows to convert strings from and to several formats.

From RTMaps format to external API:

  • static localeChar* MAPSIconv::UTF8ToLocale(const char *utf8String)
  • static wideChar* MAPSIconv::UTF8ToWide(const char *utf8Stringt)

And from other formats to UTF-8:

  • static char * MAPSIconv::localeToUTF8(const localeChar *localeString)
  • static char * MAPSIconv::wideToUTF8(const wideChar *wideString)

After you use these methods, you have to free the memory allocated for the conversion. Here are the functions to do that (never use free or delete!):

  • releaseLocale(localeChar* string)
  • releaseWide(wideChar* string)
  • releaseUTF8(char* string)

When converting into UTF-8 (RTMaps encoding), you can directly store the result in a MAPSString. In that case you have no memory to free!

  • MAPSIconv::localeToUTF8(const localeChar *localeString, MAPSString& result)
  • MAPSIconv::wideToUTF8(const wideChar *wideString, MAPSString& result)

 

Here is an example where we feed a string entered by the user in a property to a third-party library function:

const char* propertyLocale = MAPSIconv::UTF8ToLocale(GetStringProperty("Name"));
someLibraryFunctionExpectingLocaleString(propertyLocale);
MAPSIconv::releaseLocale(propertyLocale);