Custom capsule constructors

Like other classes a capsule may have constructors. However, contrary to passive class constructors a capsule constructor always has two mandatory parameters:

Usually a capsule instance is not directly created by user code (by a direct call of the new operator). Instead, capsule instances are created by the TargetRTS. For example, capsule instances in a fixed capsule part are automatically created when the container capsule instance is created, and a capsule instance in an optional capsule part is created by the TargetRTS when you invoke the RTFrame::incarnate() function on that capsule part.

But what if you need to pass initialization data to the created capsule instance? One option could be to use a version of RTFrame::incarnate() that allows you to pass a data object to the created capsule instance. However, such a data object will not be available to the capsule instance until its state machine runs the initial transition. Sometimes this is too late and you instead need the data to be available already when constructing the capsule instance. For example, if the capsule has an attribute with C++ reference type, it needs to be initialized using a constructor initializer.

So how to create a custom capsule constructor in DevOps Model RealTime? The easiest and recommended is to use the context menu Add UML - Special Operations - Constructor, just as you would for a passive class. A constructor will then be created and it will already have the two mandatory capsule constructor parameters. You can then add the necessary additional parameters you need to that constructor (remember to add them after the mandatory two). Of course you can create as many capsule constructors as you like. Here is an example of a capsule having two constructors, one of which takes an additional unsigned int parameter.

Note: Do not modify the two mandatory capsule constructor parameters. The model compiler uses them for recognizing the capsule constructor.

In generated C++ the first two mandatory constructor parameters will just be passed to the RTActor base class constructor, while your implementation of the constructor can do whatever it wants with the additional parameters. For example:

A_Actor::A_Actor( RTController * rtg_rts, RTActorRef * rtg_ref, unsigned int p )
    : val( 0 )
    , RTActor( rtg_rts ,rtg_ref )
{
//{{{USR platform:/resource/custom_capsule_constructor_4/CPPModel.emx#_CMIIMPm2EeuyltVeQ2ic0w
val = p;
//}}}USR
}

Note that if you add your own initializers for a capsule constructor you must remember to also add an RTActor initializer as shown above so that the base class constructor gets correctly invoked.

Now that we know how to create custom capsule constructors, we next need to learn how to make the TargetRTS invoke them (remember that capsule instance creation is the responsibility of the TargetRTS). There are actually a few different ways to do that. Let's start with one of the easiest approaches, which is to specify a Create Function Body code snippet for the capsule part which will host the created capsule instance. In that code snippet we can specify the code which the TargetRTS will invoke for creating an instance of the capsule. For our example above, it could look like this:

If you look in the generated code you will notice that the Create Function Body implements the "create" function of a so called Capsule Factory (in the TargetRTS "actor" is just another name for "capsule").

// Top_Actor.h
class Factory_part1 : public RTDefaultActorFactory< A_Actor >
{
    public:
        RTActor * create( RTController * rtg_rts, RTActorRef * rtg_ref, int index ) override;
};

// Top_Actor.cpp
RTActor * TOP_Actor::Factory_part1::create( RTController * rtg_rts, RTActorRef * rtg_ref, int index )
{
//{{{USR
return new A_Actor(rtg_rts,rtg_ref,18);
//}}}USR
}

A capsule factory decides how to create and destroy a capsule instance, and the RTDefaultActorFactory implements the default behavior which is to create the instance using the new operator with the default capsule constructor arguments, and to destroy it using the delete operator. By specifying a Create Function Body and/or a Destroy Function Body code snippet on a capsule part we can customize the "create" and/or the "destroy" behavior of the default capsule factory.

As a side note it should be mentioned that there are also other scenarios, except for invoking custom capsule constructors, where it makes sense to use capsule factories. For example, you may want to allocate memory for capsule instances in a memory pool instead of on the heap. In such scenarios you can implement your own capsule factory object. It must implement the RTActorFactoryInterface from the TargetRTS which specifies the signature of the "create" and "destroy" functions. There is a "Capsule Factory" property available both for capsule parts (in the "C++ Target RTS" property tab) and in a transformation configuration (in the Code Generation tab). The latter allows to specify a global capsule factory that will be used whenever no more specific capsule factory is specified (either using the "Capsule Factory" property or the "Create/Destroy Function Body" code snippets). For a global capsule factory you can use a variable $(CAPSULE_CLASS) which will be expanded to the name of the actual capsule that is the type of a capsule part. For example:

A capsule factory that is specified for a capsule part will be used for creating all capsule instances that will be hosted in that capsule part. Such a statically specified capsule factory apply both for fixed and optional capsule parts. However, for optional capsule parts it is possible to override a statically specified capsule factory with one that is provided dynamically, when incarnating the optional capsule part using the Frame service. To avoid too many overloads of RTFrame::incarnate() another function RTFrame::incarnateCustom() is defined.

RTActorId  incarnateCustom( RTActorRef & cp, RTActorFactory& factory, int index = -1);

Note here that the type of the capsule factory is RTActorFactory instead of the more general RTFactoryInterface. The reason is that in this particular context it's only possible to customize how the capsule instance should be created. Allowing customization also of the "destroy" function would require keeping track of, for each capsule instance in the optional capsule part, which capsule factory that was used for creating it. This would often mean a too big memory overhead.

RTActorFactory allows you to specify the "create" function inline using a lambda expression, which often is convenient. Here is an example:

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

To summarize, here are the ways in which a capsule factory can be provided, listed in descending priority order:

  1. Providing a capsule factory in a call to RTFrame::incarnateCustom(). Only applicable when creating capsule instances in optional capsule parts, and only the "create" function can be specified.
  2. Specifying a Create and/or Destroy Function Body code snippet on the capsule part.
  3. Specifying a pointer to a capsule factory object using the "Capsule Factory" property on a capsule part.
  4. Specifying a pointer to a capsule factory object using the "Capsule Factory" property on the transformation configuration.

If no capsule factory is specified in any of these ways, the capsule instance will be created in the default way (i.e. using the new operator and with only the two mandatory constructor arguments).

Finally, note that there are a few cases where a capsule factory is required for incarnating a capsule part:

In these cases the TargetRTS cannot know how to create an instance of the capsule without "guidance" from a capsule factory. For example, the capsule factory "create" function could specify a concrete subcapsule that inherits from the abstract capsule and/or it can provide the required additional parameters for the custom capsule constructor to be used. A capsule factory can also be used for incarnating a fixed capsule part using another thread than the default one.