More flexibility when incarnating capsules

April 12, 2022

By capsule incarnation we mean all the things that happen when an instance of a capsule is created. The instance is inserted into a capsule part, it is assigned to a thread responsible for running it, its state machine starts to execute, and so on. All these small but important things are handled by the TargetRTS and until recently there hasn't been many possibilities for customizing this process. However, starting from Model RealTime 11.1 2021.40 the incarnation process is now more flexible and several limitations have been removed. Let's look at these improvements.



Capsule Constructor

Just like any other class, it's now possible to have user-defined constructors in a capsule. Create a capsule constructor using the Project Explorer context menu Add UML - Special Operation - Constructor.

All capsule constructors have two mandatory parameters:

After these parameters you can add your own parameters, to pass arbitrary initialization data to the capsule instance.

A capsule constructor makes it possible to have reference attributes in a capsule. Such an attribute must be initialized and you can use a constructor initializer for that. Just remember to always initialize the base class RTActor first. For example:

You can have as many constructors as you like as long as their signatures are unique. All capsules can have constructors, but the one you will specify as the top capsule for your application can only have a default constructor (i.e. no additional user-defined constructor parameters are allowed there).

Capsule Factory

Ok, so I can create constructors for my capsule, but how do I use them? That is, how can I provide the actual constructor arguments when incarnating a capsule? This is where the concept of a capsule factory comes into the picture.

A capsule factory is responsible for creating and destroying instances of a capsule. The TargetRTS has a default capsule factory which implements the default rules for capsule instance creation and destruction. These rules are:

  1. A capsule instance is created using the new operator and destroyed using the delete operator.
  2. The default capsule constructor is used when creating the instance, i.e. the constructor that just takes the two mandatory parameters mentioned above.
  3. A capsule instance is run by the same thread (i.e. controller) as runs the container capsule instance (unless a specific thread is provided).
  4. A capsule instance is inserted into the first free index of the capsule part (unless a specific index is provided).
  5. The type of the capsule part is used for deciding the dynamic type of the created capsule instance (unless a specific capsule type is provided).

When you incarnate a capsule into an optional capsule part using the Frame service, you can customize rules 3,4 and 5 above by using different overloads of the incarnate function. However, if you want to customize rules 1 and/or 2, or incarnate a fixed capsule part, you need to provide your own capsule factory. This can be done in a few different ways.

You can define a capsule factory by writing a "create function body" and/or a "destroy function body" on a capsule part. For example, assume you have a fixed capsule part "cap1" typed by a capsule "Cap1" for which you have defined a custom capsule constructor that, in addition to the two mandatory constructor parameters, takes an extra boolean parameter. You can then provide "true" as an argument to that constructor by writing the following "create function body" on the capsule part:

The create function gets the default controller (rtg_rts) and capsule part (rtg_ref) as well as the default capsule part index as arguments.

Sometimes it's more convenient to write a capsule factory as a utility class that perhaps is used not just for one capsule part, but for several. Such a capsule factory implementation must implement the RTActoryFactoryInterface from the TargetRTS. You then can reference such a capsule factory object either in the Properties of a capsule part (on the page "C++ Target RTS"), or in a TC (on the page "Code Generation"):

In the latter case you can use a variable $(CAPSULE_CLASS) which expands to the name of the class that is generated from the type of the capsule part. It can be useful to use that as a template parameter in the capsule factory implementation.

Finally, it should be mentioned that it's also possible to provide a capsule factory when using the Frame service to incarnate an optional capsule part programmatically. The function to use is called incarnateCustom and it allows to provide the create behavior as a lambda (in this case the delete behavior cannot be customized). Here is an example where an instance of a capsule "A" is incarnated into a capsule part called "part1", and where a user-defined constructor is used:

RTActorId id = frame.incarnateCustom(part1, 
RTActorFactory([this](RTController * c, RTActorRef * a, int index) {
  return new A_Actor(c, a, 444); // User-defined constructor
})
);

Note that if multiple capsule factories are provided, they will be picked in this priority order:

  1. The capsule factory provided in a call to RTFrame::incarnateCustom()
  2. The capsule factory specified by means of Create and/or Destroy code snippets on the capsule part
  3. The capsule factory specified by the "Capsule Factory" property on the capsule part
  4. The capsule factory specified in the "Capsule Factory" property on the TC

New Possibilities and Recommendations

Finally, let's look at some of the new possibilities we get from the support for capsule constructors and factories, and what recommendations we can conclude from it.

Reduced Need for Optional Capsule Parts

Previously it was necessary to use optional capsule parts as soon as you needed to customize anything related to how a capsule instance was created. Now you can instead use fixed capsule parts and provide a capsule factory for controlling how to create the capsule instances. Fixed capsule parts have the benefit of being incarnated automatically when the container capsule instance is created, and destroyed automatically when it is destroyed.

Capsule Constructors Instead of State Machine Initialization Data

Previously the only way to pass initialization data to a capsule state machine was to use the Frame service and optional capsule parts. A capsule constructor is usually a better choice since the initialization data then will be available already at the time when the capsule instance is created. If your capsule has reference attributes you need to use a capsule constructor for initializing them. Another drawback with using state machine initialization data is that it is untyped and that only one data object can be passed. With capsule constructors it becomes more visible what initialization data the capsule requires, and the compiler will detect if you pass the wrong type of data.

Capsule Memory Management

Frequent allocation of memory for capsule instances using the new operator can sometimes lead to memory fragmentation. A technique for avoiding that is to pre-allocate a "memory pool" (i.e. a contiguous block of memory) and then instead use the placement new operator to create the capsule instances into that memory pool. This is an example where a capsule factory can be used for customizing how capsule instances are created, to obtain a better memory layout. But it's an optimization that certainly not all applications need to think about.


Learn more about capsule constructors and capsule factories in this article.