DevOps Model RealTime allows you to customize how capsule instances are created in a realtime application by means of dependency injection. This sample application uses build variants as the means for configuring the dependency injection.
The application consists of a pinger which emits 10 ping messages at a certain speed, and a logSystem which prints a log message each time it receives one of those ping messages. By default the ping messages are emitted slowly with 2 seconds between each message, and the log messages just contain a simple "Pinged" message.
Here is the composite structure diagram of the Top capsule that shows the simple structure of this application:
Assume that we want to configure two things in this simple application:
- the speed at which
pingmessages are emitted - whether log messages should contain a timestamp or not
We can implement these variations by means of capsules that inherit from the capsules that implement the default behavior:
For an optional capsule part we can type it with an abstract capsule, and then at run-time decide which sub capsule to incarnate the part with. The capsule part logger in LogSystem is such an example. It's typed by AbstractLogger and by default it gets incarnated with an instance of the NoTimestampLogger. However, by defining another sub capsule TimestampLogger we can implement the logging in a different way, including a timestamp for each log message.
A fixed capsule part is normally typed with a concrete capsule, but we can still define a sub capsule with a modified behavior that we can choose to use instead. Pinger is such an example, and it has a sub capsule FastPinger which emits the ping messages at a faster pace (0.5 seconds between each).
The TargetRTS provides a class RTInjector which allows create functions to be registered for capsule parts. Whenever a capsule instance is created in a capsule part for which such a create function has been registered, the TargetRTS will call that function to let it create the capsule instance. For all other capsule parts, the default capsule instantiation takes place. In the sample, this logic is implemented in the Artifact CapsuleFactory which defines a capsule factory object that is referenced from the TC property capsuleFactory.
The registration of create functions must happen early, at least before the first capsule instance gets created which we may want to customize by means of dependency injection. In this application we do it in the constructor of the Top capsule.
There are different ways how you can implement the dependency injection configuration. In this application we use Build Variants which allows us to configure at build-time which capsules to use. If you prefer to configure dependency injection without having to rebuild the application, you can for example use a configuration file instead. Read the configuration file at application start-up and let it decide which create functions to register.
Open the model in Model RealTime and go to Window - Preferences - RealTime Development - Build/Transformations - C++. Click the Workspace button for the preference Use build variants for build configuration and browse to the file build_variants.js. Then build the transformation configuration top.tcjs (you may first have to change to a different target configuration depending on your platform and C++ compiler).
In the Build dialog that appears you can now specify which Pinger and which Logger implementations to use:
The build variants are implemented by means of two compilation macros:
- TIMESTAMP_LOGGER If set, the
TimestampLoggercapsule will be used instead of the defaultNoTimestampLogger. - FAST_PINGER If set, the
FastPingercapsule will be used instead of the defaultPinger.
The macros are used in the Top capsule constructor to configure dependency injection:
#ifdef TIMESTAMP_LOGGER
RTInjector::getInstance().registerCreateFunction("/logSystem:0/logger",
[this](RTController * c, RTActorRef * a, int index) {
return new TimestampLogger_Actor(c, a);
}
);
#endif
#ifdef FAST_PINGER
RTInjector::getInstance().registerCreateFunction("/pinger",
[this](RTController * c, RTActorRef * a, int index) {
return new FastPinger_Actor(c, a);
}
);
#endifRemember that you need to rebuild the application each time you change the build variant configuration. Do a Clean build to ensure everything gets rebuilt.
You can either run the application from the transformation configuration context menu (Run As - RealTime Application) or from the command-line:
<target-folder>/default> executable -URTS_DEBUG=quitThe application prints 10 log messages with a 2 or 0.5 seconds interval, either with or without timestamps.


