diff --git a/docs/releases/CHANGELOG/index.html b/docs/releases/CHANGELOG/index.html index aea4aa7..20df7ce 100644 --- a/docs/releases/CHANGELOG/index.html +++ b/docs/releases/CHANGELOG/index.html @@ -20,7 +20,7 @@ -
RTPredefined.art
and all files from the TargetRTS. Having these files read-only helps to avoid accidental modifications, but if you anyway want to edit them you can do so by updating the setting files.readonlyInclude
.We appreciate your interest in contributing to Code RealTime. This article guides to propose changes in Code RealTime public repository on GitHub, and join the development process.
"},{"location":"contributing/#prerequisites","title":"Prerequisites","text":"Navigate to the Code RealTime repository: secure-dev-ops/code-realtime.
Click the \"Fork\" button in the top-right corner. This creates a copy of the repository in your GitHub account.
git clone
command to create a local copy of your forked repository in your workspace. Replace <your-username>
with your GitHub username: git clone https://github.com/<your-username>/code-realtime.git
cd <your-local-directory>
git checkout -b contribute-feature-x
where contribute-feature-x is your new branch name.git add <filename1> <filename2> ...
git commit -m \"Proposed a new change in feature X\"
git push origin contribute-feature-x
Visit your forked repository on GitHub (e.g., 'https://github.com/your-username/code-realtime').
Locate the Pull requests tab and click the New pull request button.
Select your branch containing the changes (e.g., contribute-feature-x
) and compare it with the main branch of the upstream repository secure-devops/code-realtime.
Provide a clear and concise title and description for your pull request. In the description, explain the purpose of your changes and how they address an issue or improve the project.
Click Create pull request.
The Art Language - State Machine - Transition - Frequent Transition
"},{"location":"draft-documentation/#frequent-transition","title":"Frequent Transition","text":"Sometimes you may have a state where one or a few outgoing transitions can be expected to execute much more frequently than others. You can then set a frequent
property on the transition trigger that you expect will trigger the transition frequently. The Art compiler uses this information to optimize generated C++ code so that such transition triggers are evaluated before other triggers that are expected to trigger the transition less frequently.
interrupted: Working -> Stopped on [[rt::properties(\n frequent=true\n )]] external.interrupt\n `\n // Interrupted while working...\n `;\n
Note
The frequent property relies on optimization features in the C++ compiler that may or may not be available depending on which target compiler that is used. Only use frequent transitions if profiling has shown that you have a need to do this optimization.
======================================================== The Art Language - Property
"},{"location":"draft-documentation/#frequent","title":"frequent","text":"Triggers for which this property is true
will lead to generated code that handles these triggers faster than other triggers. This is done by placing their if-statements early in the rtsBehavior
function to ensure that as little code as possible needs to execute when dispatching a message for a frequent trigger.
| Capsule, Class | generate_file_header | Boolean | true | Capsule, Class | generate_file_impl | Boolean | true | Capsule, Class, Protocol, Port,
| Class, Protocol | version | Integer | 0 | Class | generate_descriptor | Enumeration (true, false, manual) | true | Class | kind | Enumeration (_class, struct, union) | _class | Class | generate_class | Boolean | true | Class | generate_statemachine | Boolean | true | Class | const_target_param_for_decode | Boolean | false | Class | default_constructor_generate | Boolean | true | Class | default_constructor_explicit | Boolean | false | Class | default_constructor_inline | Boolean | false | Class | default_constructor_default | Boolean | false | Class | default_constructor_delete | Boolean | false | Class | default_constructor_visibility |
"},{"location":"draft-documentation/#generate_file_header","title":"generate_file_header","text":"By default a capsule or class is translated to one header file (.h
) and one implementation file (.cpp
). Set this property to false
to prevent generation of the header file, for example if you prefer to write it manually.
By default a capsule or class is translated to one header file (.h
) and one implementation file (.cpp
). Set this property to false
to prevent generation of the implementation file, for example if you prefer to write it manually.
By default a type descriptor will be generated for each class. The TargetRTS uses the type descriptor to know how to initialize, copy, move, destroy, encode or decode an instance of that class. Set this property to false
for classes that don't need a type descriptor. Set it to manual
if the class needs a type descriptor but you want to implement it manually rather than using the implementation that is generated by default. Note that even if you set this property to true
so that a default type descriptor is generated, you can still override individual type descriptor functions for the class.
By default a class is translated to a C++ class. You can use this property to instead translate it to a struct
or union
.
If set to false
no C++ code will be generated for the class.
If set to false
code generation for the class' state machine will be suppressed. You can use this if the state machine is informal, and you prefer to implement it manually in another way.
By default a decode function uses a non-const target
parameter. This is because usually a decode implementation must call non-const functions on the decoded object to populate it with data from the decoding. However, if it doesn't need to call such functions you can set this property so that the target
parameter is declared as const.
If set to false
a default (i.e. parameterless) constructor will not be generated for the class.
If set to true
the default (i.e. parameterless) constructor will be declared as explicit.
If set to true
the default (i.e. parameterless) constructor will be declared as inline. It's implementation will then be generated into the header file.
If set to true
the default (i.e. parameterless) constructor will be declared as defaulted. This tells the compiler to synthesize a default constructor even if one normally would not be synthesized (for example because there is a user-defined constructor with parameters).
If set to true
the default (i.e. parameterless) constructor will be declared as deleted. This will cause the compiler to generate an error if it is invoked. This can be used for preventing objects of the class to be created.
This property can be used for setting the visibility of the default (i.e. parameterless) constructor. By default it will be public
but you can change it either to protected
or private
.
Code RealTime can be installed on top of Visual Studio Code or Eclipse Theia.
The latest version of Code RealTime is available on the Visual Studio Marketplace and on the Open VSX Registry. To install that version into Visual Studio Code or Eclipse Theia follow these steps:
1) Click \"Extensions\" in the activity bar to open the Extensions view.
2) Type \"Code RealTime\" in the search field.
3) Click the \"Install\" button to install the Code RealTime extension
Once the installation is finished you will see Code RealTime appear in the \"Installed\" section of the Extensions view:
The screenshot above also shows that an extension for working with C/C++ has been installed. See Setup C++ Build Tools for more information.
After you have installed Code RealTime it's recommended to restart Visual Studio Code or Eclipse Theia, or at least to perform the command Developer: Reload Window
which is available in the Command Palette (Ctrl+Shift+P).
Another way to install Code RealTime is to use a .vsix file. This can be useful if you want to install another version than the latest. You can download .vsix files for all released versions of Code RealTime from both the Visual Studio Marketplace and the Open VSX Registry (click \"Version History\"). Once you have downloaded the .vsix file follow these steps to install it:
1) If you already have a version of Code RealTime installed, you can manually uninstall it first (see Uninstalling). Note that this step is usually not required since the newly installed version of the extension will automatically replace the old one.
2) Open the menu of the Extensions view and select the command \"Install from VSIX\".
3) In the file dialog that appears, select the .vsix file to install.
If the installation completes successfully you should see the following message:
If instead the installation fails, this message will tell you the reason. One common reason for failure is that your version of Visual Studio Code or Eclipse Theia is not compatible (i.e. too old) for Code RealTime.
It should also be noted that it's possible to directly install any published version of Code RealTime by using the \"Install Another Version\" command that is available in the context menu of an extension shown in the \"Installed\" section.
"},{"location":"installing/#install-from-docker-image","title":"Install from Docker Image","text":"Yet another way to install Code RealTime is to use the Docker image that is available on DockerHub. This image contains Eclipse Theia with the latest version of Code RealTime installed. Run the docker image using this command:
docker run -p <host-port>:<container-port> -e isDocker=true baravich/theia-code-realtime:1.0
Replace <host-port>
with a port that is available on your computer, and <container-port>
with the port you want the Docker container to use. For example, if you run this command
docker run -p 4000:3000 -e isDocker=true baravich/theia-code-realtime:1.0
then after less than a minute you can access Code RealTime from a web browser at http://localhost:4000.
"},{"location":"installing/#viewing-installation-information","title":"Viewing Installation Information","text":"If you are unsure about which version of Code RealTime you have installed, you can see the version in the extension's tooltip, and the full build version is available in the page that appears if you double-click the extension:
You can also see the version and the exact date of the installed Code RealTime in the Changelog that is present on the extension's page. There you can also see what has been fixed and improved compared to older releases. Note that for Theia this information is not present on the extension's page, but you can see it if you double-click on the extension's name (the web page of the extension will then open).
"},{"location":"installing/#portable-mode-installation","title":"Portable Mode Installation","text":"You can install multiple versions of Code RealTime by using the portable mode of Visual Studio Code. See Portable Mode for how to install Visual Studio Code in portable mode, which will allow you to install a version of Code RealTime that won't affect other Visual Studio Code installations on the machine. Portable mode also allows to move or copy an installation from one machine to another, which makes it useful in scenarios where installs should be centralized in an organization.
"},{"location":"installing/#post-installation-configuration","title":"Post-Installation Configuration","text":"After a successful installation you need to perform a few configuration steps before you can start to use Code RealTime.
"},{"location":"installing/#setup-java","title":"Setup Java","text":"Code RealTime uses a Java language server and hence needs a Java Virtual Machine (JVM). It's required to use a JVM for Java 17 or newer. If an appropriate JVM cannot be found when the Code RealTime extension is activated (which for example happens the first time you open an Art file), you will receive an error message.
Code RealTime follows the steps below in priority order when it looks for an appropriate JVM to use:
1) The setting code-rt.languageServer.jvm
is examined. If it specifies a path to a JVM it will be used. You can edit this setting by invoking File - Preferences - Settings and then type the setting id mentioned above in the filter box.
2) The environment variable JAVA_HOME
is examined. If it specifies a path to a JVM it will be used.
3) An attempt is made to launch the java
command without using a path. The first JVM found in the system path, if any, will be used.
You may also need to adjust the arguments for the JVM. By default the JVM is launched with the below argument:
-Xmx4024m
To change the JVM arguments set the setting code-rt.languageServer.jvmArgs
shown in the image above.
When the Code RealTime extension is activated information about which Java that is used is printed to the Art Server output channel.
Here you will also see if the launching of the language server for some reason failed.
"},{"location":"installing/#setup-c-build-tools","title":"Setup C++ Build Tools","text":"When Code RealTime builds generated C++ code it uses C++ build tools such as a make tool, a C++ compiler, a C++ linker etc. These tools need to be in the path when you start Visual Studio Code or Eclipse Theia. If you have multiple C++ build tools installed, make sure the correct ones are present in the path before launching Visual Studio Code or Eclipse Theia. For example, if you use the Microsoft C++ compiler, it's recommended to launch from a Visual Studio native tools command prompt with the correct version (e.g. 32 bit or 64 bit). Build errors caused by inconsistent versions of C++ build tools being used can be tricky to find.
You also need to install an extension for C/C++ development into Visual Studio Code or Eclipse Theia. Even if you can use any such extension, Code RealTime provides the best integration with either C/C++ for Visual Studio Code or clangd.
"},{"location":"installing/#uninstalling","title":"Uninstalling","text":"To uninstall Code RealTime follow these steps:
1) Click \"Extensions\" in the left side-bar.
2) Find the Code RealTime extension in the \"Installed\" section and invoke the \"Uninstall\" command (in Visual Studio Code the command is available in the context menu, while in Theia it shows up as a button to click).
Once the uninstallation is finished you will no longer see Code RealTime in the \"Installed\" section.
"},{"location":"overview/","title":"Overview","text":"Code RealTime lets you create stateful, event-driven realtime applications in C++.
It runs as an extension of Visual Studio Code or Eclipse Theia. Follow the installation instructions for installing it.
Code RealTime supports the Art language which extends the C++ language with high-level concepts useful when designing stateful, event-driven realtime applications. Examples of such concepts include capsules, state machines and protocols. Art is a textual language, but also provides a graphical notation that includes class, state and structure diagrams.
Code RealTime translates Art files into efficient C++ code which can be compiled on any target system. The generated code makes use of the Target RunTime System which is a C++ library that implements the concepts of the Art language.
Watch this video to get an overview of how Code RealTime uses the Art language for implementing stateful, event-driven realtime applications.
"},{"location":"overview/#art-history","title":"Art History","text":"The Art language as implemented in Code RealTime builds on a foundation with a long history in industry. In the early 1990s the Canadian company ObjecTime Limited developed a language called ROOM to address the challenges of building realtime applications consisting of communicating state machines. ROOM introduced concepts such as capsules, protocols and ports and was first implemented in the tool ObjecTime Developer. This tool got adopted in a wide range of industrial domains for example telecom and embedded systems development.
In 2000 ObjectTime was acquired by Rational Software and ObjecTime Developer was merged with Rational Rose, a UML modeling tool. The result was Rational Rose RealTime (Rose RT). At the same time many of the ROOM language concepts made its way into the, by then, new modeling language called UML-RealTime.
In 2003 Rational Software was acquired by IBM which at the time was investing heavily in the Eclipse platform. As a result, work started to create an Eclipse-based tool as the successor of Rose RT. This new product got the name Rational Software Architect RealTime Edition (RSARTE) and was first released in 2007.
In 2016 HCL entered a partnership with IBM, which led to a rebranded version of RSARTE called HCL RTist. A few years later both RSARTE and RTist were renamed to DevOps Model RealTime.
Work on Code RealTime began in 2020 with the aim of supporting other IDEs than Eclipse. As part of this effort a textual language syntax, Art, was developed. Hence it's fair to describe the Art language as a new syntax for concepts that have a rather old history and have already been used in the industry for more than 30 years. It should also be mentioned that the Target RunTime System used in Code RealTime is the same as is used in Model RealTime. In fact, the implementation of this C++ library started with ObjectTime Developer and has since then been gradually extended and modernized.
"},{"location":"settings/","title":"Settings","text":"Code RealTime provides several settings that can be used for configuring many aspects of how it works. To view these settings perform File - Preferences - Settings and then select Extensions - Code RealTime.
Below is a table that lists all Code RealTime settings. Each setting is described in a section of its own below the table.
Setting Id Purpose Language Server - Jvm code-rt.languageServer.jvm Set the JVM to use for running the Code RealTime language server Language Server - Jvm Args code-rt.languageServer.jvmArgs Set arguments for the JVM that runs the Code RealTime language server Validation - Rule Configuration code-rt.validation.ruleConfiguration Customize which validation rules to run on Art files and their severity Build - Output Folder code-rt.build.outputFolder Set the location where to place generated code Build - Cancel On Error code-rt.build.cancelOnError Cancel a launched build if errors exist in TCs or Art files Diagram - Show Junction Names code-rt.diagram.showJunctionNames Show junction names on state diagrams Diagram - Show Choice Names code-rt.diagram.showChoiceNames Show choice names on state diagrams Diagram - Show Entry Exit Point Names code-rt.diagram.showEntryExitPointNames Show entry/exit point names on state diagrams Diagram - Show Transition Names code-rt.diagram.showTransitionNames Show transition names on state diagrams Diagram - Show Diagnostics code-rt.diagram.showDiagnostics Show error, warning and information icons on diagrams"},{"location":"settings/#language-server","title":"Language Server","text":"Settings related to running the Code RealTime language server.
"},{"location":"settings/#jvm","title":"Jvm","text":"When the Code RealTime extension gets activated it will attempt to launch its language server. If this setting holds a valid location of a Java VM (JDK or JRE) it will be used for running the language server. Otherwise the JAVA_HOME
environment variable will be used. If that is also not set, it's required to have java
in the path. See Setup Java for more information.
By default the JVM is launched with the argument -Xmx4024m
. Refer to the documentation of your JVM for a list of available JVM arguments.
Settings related to validation of Art files.
"},{"location":"settings/#rule-configuration","title":"Rule Configuration","text":"This setting can be used for customizing which validation rules that should run when you edit Art files. You can also completely disable those validation rules you don't want to run. For more information see this page.
"},{"location":"settings/#build","title":"Build","text":"Settings related to building Art files, via C++ code, to libraries or executables.
"},{"location":"settings/#output-folder","title":"Output Folder","text":"This setting specifies a folder where all generated code will be placed. More precisely, it's used for resolving relative paths specified in TCs (using the TC property targetFolder
). If you leave this setting unset, relative paths will instead be resolved against the location of the TCs. The Output Folder
must be specified as an absolute path that points at a writable folder in the file system.
When a TC is built the Problems view is scanned to see if there are errors reported on the built TC or its prerequisites, as well as all Art files that will be built. If at least one such error is found it's recommended to cancel the build, fix the errors and then redo the build again. The default value for this setting is Prompt
which means you will be prompted by a dialog where you can choose if you want to cancel the build, or proceed anyway. In the latter case you may encounter compilation errors when generated code is compiled or run-time problems when the built executable is run. Therefore you should only proceed if you are confident that the errors are safe to ignore. You may set this setting to Always
to suppress the dialog and always cancel the build when errors are present. You can also (but this is not recommended) set the setting to Never
to always ignore any errors and proceed with the build anyway.
Settings related to graphical diagrams that visualize elements of Art files.
"},{"location":"settings/#show-junction-names","title":"Show Junction Names","text":"Junctions usually have short and uninteresting names, and are therefore by default not shown on state diagrams. Turn on this setting to make them visible. Note that a certain state diagram may override this setting by means of setting the corresponding diagram property in the diagram's Properties view. See Diagram Filters for more information.
"},{"location":"settings/#show-choice-names","title":"Show Choice Names","text":"Choices usually have short and uninteresting names, and are therefore by default not shown on state diagrams. Turn on this setting to make them visible. Note that a certain state diagram may override this setting by means of setting the corresponding diagram property in the diagram's Properties view. See Diagram Filters for more information.
"},{"location":"settings/#show-entry-exit-point-names","title":"Show Entry Exit Point Names","text":"Entry and exit points usually have short and uninteresting names, and are therefore by default not shown on state diagrams. Turn on this setting to make them visible. Note that a certain state diagram may override this setting by means of setting the corresponding diagram property in the diagram's Properties view. See Diagram Filters for more information.
"},{"location":"settings/#show-transition-names","title":"Show Transition Names","text":"If you feel that showing the names of transitions makes state diagrams too cluttered you can turn off this setting. By default they are shown. Note that a certain state diagram may override this setting by means of setting the corresponding diagram property in the diagram's Properties view. See Diagram Filters for more information.
"},{"location":"settings/#show-diagnostics","title":"Show Diagnostics","text":"By default diagram elements will be decorated by icons corresponding to diagnostics generated by validation rules. Turn off this setting if you don't want to see these icons on diagrams.
There are three kinds of diagnostic icons corresponding to the problem severity levels Error, Warning and Information. See Problem Severity for more information.
"},{"location":"support/","title":"Support Procedures","text":"If you find a bug in Code RealTime please report it with a GitHub Issue. Please include steps to reproduce and any additional files that can help in troubleshooting. For example, it can be good to include all log files. You can find the location of these logs by invoking the command Developer: Open Logs Folder
. You can zip the entire logs folder and attach it to the issue. You can also check these logs using the Output view. In particular, the following two output logs are relevant:
Code RealTime checks for semantic problems in your application. It does this by running a large number of validation rules each time an Art file or a TC file has been changed. The rules run automatically as soon as you have made a change to the file (even before saving it). This ensures that errors and warnings (i.e. potential problems) are found as early as possible.
"},{"location":"validation/#problem-severity","title":"Problem Severity","text":"Each validation rule has a default severity which will be used for the problems that are reported by the rule:
Error An error is a problem that is severe enough to prevent building a correct application. Errors must be fixed, and it will not be possible to build the Art files into a C++ application until all errors have been resolved.
Warning A warning is a potential problem, which you may or may not choose to fix. It can for example indicate a deviation from common conventions and best practises and it can indicate that the application will not behave as you may expect.
Information An information is just a message that you should be aware of. It doesn't really indicate a problem, and you don't need to fix it.
You can customize the default severity of any validation rule, and you can also choose to completely disable a certain validation rule that you don't think provides any value. See Configuring Validation for more information.
"},{"location":"validation/#problem-reporting","title":"Problem Reporting","text":"When a validation rule has found a problem in an Art file, it is marked by underlining one or several Art elements in the file. The underlining is red for errors, orange for warnings and blue for information messages. For example, in the capsule shown below one warning and two errors have been found.
You can hover the cursor over these underlinings to get a tooltip with information about the problem. Every problem has a message that describes it. Often this message gives enough information for understanding how to fix the problem. If this is not the case you can go to the documentation about the validation rule to find more information, examples and suggestions for how the problem can be fixed. To easily find the documentation click the hyperlink that consists of the unique id of the validation rule (it starts with a prefix such as \"ART_\", followed by a 4 digit number and a name). Alternatively you can search for the validation rule id on this page.
Often a problem may be associated with more than one Art element. There is a main element on which the problem will be shown, but there often also are other elements that are related to the problem in one way or another. You can navigate to related elements to get a better understanding of why a problem is reported and how to fix it. In the screenshot above the problem has a single related element (the capsule Another
) but in general a problem can have an arbitrary number of related elements.
Problems are also reported by means of icons in diagrams. Below are three states with problems of different severity:
A problem icon has a tooltip that shows the message of the problem. You can disable problem reporting in diagrams by means of a configuration setting code-rt.diagram.showDiagnostics
.
For a TC file, all properties it contains will be validated, and problems that are found during this validation are shown by underlining TC properties.
"},{"location":"validation/#problems-view","title":"Problems View","text":"Too see all problems found in all Art files and all TC files in the workspace, open the Problems view. The total number of problems found are shown in the Problems view heading. By default problems are shown in a tree grouped by the files where they were found. However, you can also view them as a flat table instead (but note that related elements can only be seen when using the tree view).
If there are many problems, it can help to filter the Problems View by typing some text in the filter box. For example, you can filter using a regular expression that matches only some of the files in the workspace, to reduce the number of problems shown.
"},{"location":"validation/#quick-fix","title":"Quick Fix","text":"Some problems have one or several typical solutions that are possible to apply automatically by means of \"code actions\". If a problem has at least one such code action defined, a yellow light bulb icon will appear and a Quick Fix command will be available in the problem tooltip.
Note that most semantic errors cannot be automatically resolved like this, but in some simple cases it's possible.
"},{"location":"validation/#configuring-validation","title":"Configuring Validation","text":"Validation can be configured to change which rules that should run, and what severity they should report found problems with. By default every validation rule is enabled and uses a predefined severity level. Validation rules can be configured either globally by means of a setting, or locally by means of a property rule_config. In both cases the rule configuration consists of a comma-separated list of 5 letter strings where the first letter specifies if the rule is disabled or it's severity (X,I,W,E) and remaining letters specify the rule id. For example, the rule configuration X0003,I0004,W0009,E0005
means the following:
To configure validation rules globally, use the configuration setting code-rt.validation.ruleConfiguration
. A global configuration will apply for all Art files in the workspace, and all Art elements within those files, unless a local rule configuration has been set on an element.
To configure validation rules locally, set the property rule_config on an Art element. It will affect the validation of that Art element itself, as well as all elements contained within that Art element. Here is an example of how to disable the validation rule ART_0003_nameShouldStartWithUpperCase on a capsule. Note that it also will disable this rule for elements contained within the capsule, such as states.
capsule customCapsule // no warning even if capsule name is not capitalized\n[[rt::properties(\n rule_config=\"X0003\"\n)]]{\n\n statemachine {\n state customState; // no warning here too\n initial -> customState;\n };\n};\n
Note
Certain validation rules cannot be disabled or have their severity changed. These are known as \"core validation rules\" and they run before semantic validation starts (which is why they cannot be customized).
Note
Local configuration of validation rules is only supported for Art files. For TC validation you cannot provide a local rule configuration in the TC file.
"},{"location":"validation/#validation-rules","title":"Validation Rules","text":"This chapter lists all validation rules which Code RealTime checks your Art application against. These rules find problems in Art files and all problems found have the \"ART_\" prefix.
"},{"location":"validation/#art_0001_invalidnamecpp","title":"ART_0001_invalidNameCpp","text":"Severity Reason Quick Fix Error An Art element has a name that is not a valid C++ name, or a name that will cause a name clash in the generated code. Prepend UnderscoreArt elements are translated to C++ elements without changing the elements' names. Hence you need to choose names for Art elements that are valid in C++. For example, C++ keywords cannot be used.
Furthermore, names of such Art elements must not clash with global names used by the TargetRTS or names within generated C++ files.
If you ignore this error you can expect errors when compiling the generated C++ code.
A Quick Fix is available that will fix the problem by adding an underscore to the beginning of the name, in order to make it a valid C++ name.
protocol InvalidNameProtocol {\n in virtual(); // ART_0001 (\"virtual\" is a C++ keyword)\n};\n\ncapsule Exception { // ART_0001 (\"Exception\" is a name reserved for use by the TargetRTS)\n\n};\n
"},{"location":"validation/#art_0002_duplicatenamesinscope","title":"ART_0002_duplicateNamesInScope","text":"Severity Reason Quick Fix Error Two or more Art elements in the same scope have the same names or signatures. N/A Names of Art elements must be unique within the same scope. The following is checked:
All elements with clashing names or signatures will be reported as related elements. Use this to find the element(s) that need to be renamed.
protocol DupProto { // ART_0002 (name clash for inEvent1)\n in inEvent1(); \n in inEvent1(); \n out inEvent1(); // OK (symmetric event)\n};\n\nclass DNIS {\n trigger op1(`int` p);\n trigger op1(); // OK (signatures are unique)\n statemachine {\n state State;\n initial -> State;\n };\n};\n
Note that inheritance brings inherited elements into a scope and this can cause name clashes too. However, a problem is only reported if at least one of the elements with conflicting names is defined locally.
capsule B0002 {\n statemachine {\n state State;\n initial -> State;\n junction j1;\n state Composite {\n state Nested;\n };\n };\n};\n\ncapsule D0002 : B0002 {\n statemachine { // ART_0002 (name clashes with B0002::State and B0002::j1)\n state State; \n choice j1; \n state redefine Composite { // ART_0002 (name clash with B0002::Composite::Nested)\n state Nested; \n };\n }; \n};\n\ncapsule C0002 : D0002 {\n\n statemachine { // OK. Even if this capsule inherits elements with conflicting names from D0002, none of them are defined in this state machine.\n };\n};\n
"},{"location":"validation/#art_0003_nameshouldstartwithuppercase","title":"ART_0003_nameShouldStartWithUpperCase","text":"Severity Reason Quick Fix Warning An Art element's name doesn't follow the naming convention to start with uppercase. Capitalize Name Just like in most languages Art has certain conventions on how elements should be named. The following elements should have names that start with an uppercase letter:
A Quick Fix is available that will fix the problem by capitalizing the name. Note, however, that it will only update the element's name, and not all references. If you have references to the element you may instead want to fix the problem by performing a rename refactoring (context menu command Rename Symbol).
capsule myCapsule { // ART_0003\n statemachine {\n state sstate; // ART_0003\n initial -> sstate; \n };\n};\n
In this context an underscore (_
) is considered a valid upper case character, so all names that start with underscore are accepted by this validation rule.
Just like in most languages Art has certain conventions on how elements should be named. The following elements should have names that start with a lowercase letter:
A Quick Fix is available that will fix the problem by decapitalizing the name. Note, however, that it will only update the element's name, and not all references. If you have references to the element you may instead want to fix the problem by performing a rename refactoring (context menu command Rename Symbol).
protocol LowerCaseTestProtocol {\n out MyEvent(); // ART_0004\n};\n
In this context an underscore (_
) is considered a valid lower case character, so all names that start with underscore are accepted by this validation rule.
If no outgoing transition of a choice is enabled at runtime (because no outgoing transition has a guard condition that is fulfilled) then the state machine will get stuck in the choice for ever. To avoid this you should ensure that at least one outgoing transition is enabled. A good way to do this is to use 'else' as the guard condition for one of the outgoing transitions. Such an else-transition will then execute if no other outgoing transition of the choice is enabled.
capsule ChoiceSample {\n statemachine {\n state State;\n initial -> State;\n choice x; // ART_0005\n State -> x;\n x -> State when `return getVal() == 5;`;\n };\n};\n
Note that a transition without any guard condition is equivalent to a transition with a guard condition that is always fulfilled (i.e. a guard condition that returns true). An outgoing transition from a choice or junction without any guard is therefore also an else-transition.
"},{"location":"validation/#art_0006_choicewithoutoutgoingtransitions","title":"ART_0006_choiceWithoutOutgoingTransitions","text":"Severity Reason Quick Fix Error A choice has no outgoing transitions. N/AA choice should typically have at least two outgoing transitions to be meaningful. Having only one outgoing transition is possible if it is an else-transition (i.e. a transition with an 'else' guard, or without any guard at all). However, a choice without any outgoing transition is not allowed since the state machine always will get stuck when reaching such a choice.
capsule ChoiceSample {\n statemachine {\n state State;\n initial -> State;\n choice x; // ART_0006\n State -> x; \n };\n};\n
"},{"location":"validation/#art_0007_choicewithtoomanyelsetransitions","title":"ART_0007_choiceWithTooManyElseTransitions","text":"Severity Reason Quick Fix Error A choice has more than one outgoing else-transition. N/A It's good practise to have an outgoing else-transition (i.e. a transition with an 'else' guard, or without any guard at all) for a choice since it will prevent the state machine from getting stuck in the choice at runtime. However, there should not be more than one such else-transition defined, since otherwise it's ambiguous which one of them to trigger in the case none of the other outgoing transitions from the choice are enabled.
capsule ChoiceSample {\n statemachine {\n state State, State2;\n initial -> State;\n choice x; // ART_0007\n State -> x;\n x -> State;\n x -> State2 when `else`;\n };\n};\n
"},{"location":"validation/#art_0008_initialtransitioncount","title":"ART_0008_initialTransitionCount","text":"Severity Reason Quick Fix Error A state machine has too many initial transitions, or no initial transition at all. N/A A state machine of a capsule or class must have exactly one initial transition. A common reason for this error is that you have introduced inheritance between two capsules which both have state machines with an initial transition. Because of that the derived capsule will have two initial transitions (the one it defines itself locally plus the one it inherits from the base capsule). In this case the error can be fixed by either deleting or excluding the initial transition from the derived capsule, or to let it redefine the initial transition from the base capsule.
capsule InitTransCap2 {\n statemachine {\n state State;\n initial -> State;\n };\n};\n\ncapsule InitTransCap3 : InitTransCap2 {\n statemachine { // ART_0008 (1 local initial transition and 1 inherited)\n state State2;\n initial -> State2; \n };\n};\n\ncapsule NoInitTrans { \n statemachine { // ART_0008 (no initial transition)\n state State;\n };\n};\n
Note that if the initial transition in the base capsule has no name, the derived capsule cannot exclude or redefine it. It's therefore good practise to name the initial transition if you expect your capsule to be inherited from.
The validation rule also checks nested state machines. Such a state machine doesn't need any initial transition, since the composite state can be entered via an entrypoint which takes the nested state machine to its first state. However, if the nested state machine has an initial transition there cannot be more than one.
capsule InitTransCap2 {\n statemachine { \n initial -> Composite;\n\n state Composite {\n state NS;\n initial -> NS;\n };\n };\n};\n\ncapsule InitTransCap3 : InitTransCap2 {\n statemachine {\n state redefine Composite { // ART_0008 (3 local initial transitions and 1 inherited)\n state S1, S2, S3;\n initial -> S1; \n initial -> S2; \n _ini: initial -> S3; \n };\n };\n};\n
"},{"location":"validation/#art_0009_invalidproperty","title":"ART_0009_invalidProperty","text":"Severity Reason Quick Fix Error A non-existing property is set for an element. Remove Property Most Art elements have properties that can be set to change their default values. Different elements have different properties and if you get this error it means you have referenced a non-existing property for an Art element.
A Quick Fix is available for removing the setting of the invalid property. Use Content Assist (Ctrl+Space) to get a list of valid properties for an Art element.
protocol IP_PROTO [[rt::properties(\n no_property = 4 // ART_0009 (a protocol has no property called \"no_property\")\n)]] {\n};\n\ncapsule CN \n[[rt::properties(\n colour=\"#ff0000\" // ART_0009 (misspelled property \"color\")\n)]]\n{\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0010_invalidpropertyvalue","title":"ART_0010_invalidPropertyValue","text":"Severity Reason Quick Fix Error A property is set to a value of incorrect type. N/A Most Art elements have properties and every property has a type that is either boolean, integer, string or an enumeration. The type of the value assigned to a property must match the property's type. For example, you cannot assign an integer value to a boolean property.
capsule IPV_Cap [[rt::properties(\n generate_file_header=4 // ART_0010 (\"generate_file_header\" is a boolean property)\n)]]{\n statemachine {\n state State;\n initial -> State;\n };\n}; \n
"},{"location":"validation/#art_0011_propertysettodefaultvalue","title":"ART_0011_propertySetToDefaultValue","text":"Severity Reason Quick Fix Warning A property is set to its default value. Remove Property Most Art elements have properties and every property has a default value. It's unnecessary to explicitly set a property to its default value.
A Quick Fix is available for removing the setting of the property.
capsule C_PropDefaultValue [[rt::properties(\n generate_file_header=true // ART_0011\n)]]{\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0012_invalidcodesnippet","title":"ART_0012_invalidCodeSnippet","text":"Severity Reason Quick Fix Error A code snippet is invalid in one way or the other. Remove Code Snippet A code snippet's kind is specified after the prefix rt::
. Different Art elements may have different kinds of code snippets. Also, some Art elements may have multiple code snippets of a certain kind, while others only may have one code snippet of each kind.
A Quick Fix is available for removing the invalid code snippet.
[[rt::header_preface]] // ART_0012 (code snippet for capsule/class placed at file level)\n`\n // YourCodeHere\n`\n\ncapsule Name {\n [[rt::unknown]] // ART_0012 (non-existing kind of code snippet)\n `\n // YourCodeHere\n `\n\n part x : OtherCap \n [[rt::create]]\n `\n return new DemoCap(rtg_rts, rtg_ref);\n `\n [[rt::create]] // ART_0012 (duplicated code snippet)\n `\n return new DemoCap(rtg_rts, rtg_ref);\n `;\n\n statemachine {\n state State;\n initial -> State;\n };\n}; \n
"},{"location":"validation/#art_0013_partmultiplicityerror","title":"ART_0013_partMultiplicityError","text":"Severity Reason Quick Fix Error The part's lower multiplicity must be less than its upper multiplicity. N/A If a part has a multiplicity that specifies a range (i.e. both a lower and upper multiplicity), then the lower multiplicity must be less than the upper multiplicity.
capsule PME_Cap {\n part myPart : OtherCap [2..2]; // ART_0013\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0014_partkindmultiplicityinconsistency","title":"ART_0014_partKindMultiplicityInconsistency","text":"Severity Reason Quick Fix Warning The part's kind is inconsistent with its multiplicity. N/A The multiplicity of a capsule part must match the part's kind. The following is checked:
In case any of these inconsistencies is detected, the faulty multiplicity will be ignored and a default multiplicity (see Part) will be used instead.
capsule PKMI_Cap { \n fixed part myPart : OtherCap [0..2]; // ART_0014 (Fixed part should not have lower multiplicity 0)\n optional part myPart2 : OtherCap [1..5]; // ART_0014 (Optional part should not have lower multiplicity > 0)\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0015_internaltransitionoutsidestate","title":"ART_0015_internalTransitionOutsideState","text":"Severity Reason Quick Fix Error An internal transition is defined outside a state, in the top state machine. N/A An internal transition specifies events that can be handled while a state machine is in a certain state without leaving that state. Hence it's only possible to define an internal transition inside a state. It does not make sense to define an internal transition directly in the top state machine.
capsule IntTransOutsideState {\n behavior port timer : Timing;\n\n statemachine {\n state State {\n t1 : on timer.timeout ` `; \n };\n initial -> State;\n terror : on timer.timeout ` `; // ART_0015\n };\n};\n
"},{"location":"validation/#art_0016_circularinheritance","title":"ART_0016_circularInheritance","text":"Severity Reason Quick Fix Error A capsule, class or protocol inherits from itself directly or indirectly. N/A When you use inheritance for capsules, classes and protocols you need to ensure there are no inheritance cycles. Cyclic inheritance means that an element would inherit from itself, directly or indirectly, which is not allowed.
Note
Both capsules and classes, but not protocols, may have C++ base classes specified by means of C++ code snippets. Such inheritance relationships are not checked by this validation rule, but by the C++ compiler.
The elements that form the inheritance cycle will be reported as related elements. Use this to decide how to break the inheritance cycle.
protocol PR1 : PR2 { // ART_0016\n\n};\n\nprotocol PR2 : PR1 { // ART_0016\n\n};\n\nclass C1 { \n statemachine {\n state State;\n initial -> State;\n };\n};\n\nclass C2 : C1 {\n statemachine {\n state State;\n initial -> State;\n };\n};\n\nclass C3 : C2, C4 { // ART_0016\n statemachine {\n state State;\n initial -> State;\n };\n};\n\nclass C4 : C3 { // ART_0016\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0017_circularcomposition","title":"ART_0017_circularComposition","text":"Severity Reason Quick Fix Error A capsule contains itself through a cycle in the composition hierarchy. N/A Parts of a capsule must form a strict composition hierarchy. At run-time the root of this hierarchy is the top capsule instance, and all other capsule instances in the application must be directly or indirectly owned by that capsule instance. For a fixed part the creation of contained capsule instances happen automatically when the container capsule is incarnated. It's therefore possible to statically analyze the fixed parts and check for cycles in the composition hierarchy.
Note
Only the static type of fixed capsule parts are used when looking for composition cycles. If a part has a capsule factory that specifies a create function using C++ code, then a different dynamic type may be specified for the created capsule instances for that part. This opens up for more possibilities of introducing cycles in the composition hierarchy that will not be detected by this validation rule.
The fixed parts that form the composition cycle will be reported as related elements. Use this to decide how to break the composition cycle.
capsule CComp2 { \n fixed part p2 : CComp3; // ART_0017\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n\ncapsule CComp3 { \n part p3 : CComp2; // ART_0017\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0018_circulartransitions","title":"ART_0018_circularTransitions","text":"Severity Reason Quick Fix Error A state machine has a cycle in the transitions that execute when leaving a junction or entry/exit point. N/A A junction or an entry or exit point can split an incoming transition flow into multiple outgoing transition flows based on evaluating guard conditions for the outgoing transitions. If care is not taken it's possible to introduce cycles in the outgoing transition flows. Even if the code generator also detects such cycles and prevents them from leading to infinite recursion at run-time, the transitions that form the cycle will not reach a state or choice when (or if, depending on the guard conditions) they execute. If guard conditions on such transitions have side-effects (which guard conditions should not have), it's possible that the application can work correctly even if there is a transition cycle. But it's very much recommended to change the state machine so it doesn't have any transition cycles.
The transitions that form the cycle will be reported as related elements. Use this to decide how to break the transition cycle.
capsule CT_cap {\n statemachine { // ART_0018\n state S1;\n initial -> S1;\n junction j1, j2;\n t1: S1 -> j1;\n t2: j1 -> j2;\n t3: j2 -> j1;\n };\n};\n
With entry and exit points a transition cycle can involve transitions in a nested state machine too.
capsule Cap0018 {\n statemachine { // ART_0018\n state State {\n entrypoint ep;\n exitpoint ex;\n junction j1;\n state N1;\n ep -> j1;\n j1 -> ex;\n };\n initial -> State.ep;\n State.ex -> State.ep; \n state Final;\n State.ex -> Final when `true`;\n };\n};\n
With state machine inheritance it's possible to introduce transition cycles when an inherited state machine is redefined or extended. This validation rule will also detect such cycles and report them on the inheriting state machine that contains the cycle.
"},{"location":"validation/#art_0019_unwiredportbothpublisherandsubscriber","title":"ART_0019_unwiredPortBothPublisherAndSubscriber","text":"Severity Reason Quick Fix Error An unwired port is declared as being both a subscriber and publisher at the same time. Make Publisher, Make SubscriberAn unwired port can at runtime be connected to another unwired port. One of the connected ports will be a publisher port (a.k.a SPP port) while the other will be a subscriber port (a.k.a SAP port). An unwired port can either be statically declared as being a publisher or subscriber port, or it can be dynamically decided at port registration time if the port should be a publisher or subscriber. The same port can not be both a subscriber and a publisher port at the same time.
Two Quick Fixes are available for making the port a publisher or a subscriber port by removing either the subscribe
or publish
keyword.
capsule UnwiredCapsule { \n subscribe publish behavior port p1 : UnwiredProtocol; // ART_0019\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0020_wiredportwithunwiredproperties","title":"ART_0020_wiredPortWithUnwiredProperties","text":"Severity Reason Quick Fix Warning A property that only is applicable for an unwired port is specified for a wired port. N/A An unwired port may have properties that control how it will be registered at runtime (see registration and registration_name). These properties have no meaning and will be ignored for wired ports.
capsule UnwiredCapsule2 {\n behavior port p1 [[rt::properties(\n registration_name=\"hi\"\n )]]: UnwiredProtocol; // ART_0020\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0021_unwiredportregnamesameasportname","title":"ART_0021_unwiredPortRegNameSameAsPortName","text":"Severity Reason Quick Fix Warning An unwired port is set to use a registration name that equals the name of the port. N/A When an unwired port is registered a name is used that by default is the name of the port. The property registration_name can be used for specifying another name. It's hence unnecessary to use that property for specifying the name of the port, since it is the default name that anyway would be used.
capsule UnwiredCapsule3 {\n unwired publish behavior port p1~ [[rt::properties(\n registration_name = \"p1\"\n )]]\n : UnwiredProtocol; // ART_0021 \n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0022_ruleconfigproblem","title":"ART_0022_ruleConfigProblem","text":"Severity Reason Quick Fix Warning The rule_config property has a malformed value. N/A The rule_config property can be set on Art elements to configure which validation rules to run for that element (and for all elements it contains). It can also be used for setting a custom severity for those rules. The value of the rule_config property should be a comma-separated list of 5 letter strings where the first letter specifies if the rule is disabled and it's severity (X,I,W,E) and remaining letters specify the rule id. See Configuring Validation for more information and examples.
capsule RCP [[rt::properties(\n rule_config=\"X0000\" // ART_0022 (a validation rule with id 0000 does not exist)\n)]]{\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0023_entryexitcodecount","title":"ART_0023_entryExitCodeCount","text":"Severity Reason Quick Fix Error A state has too many entry and/or exit actions. N/A A state can at most have one entry and one exit action. Solve this problem by merging all entry and exit actions of the state to a single entry and exit action that performs everything that should be done when the state is entered and exited.
capsule CX {\n statemachine {\n state Composite {\n entry // ART_0023\n `\n entry1();\n `;\n entry // ART_0023\n `\n entry2();\n `;\n };\n initial -> Composite;\n };\n};\n
"},{"location":"validation/#art_0024_unwiredportnotbehavior","title":"ART_0024_unwiredPortNotBehavior","text":"Severity Reason Quick Fix Error An unwired port is not defined as a behavior port. Make Behavior Port, Make Wired Port An unwired port cannot be connected to another port by means of a connector. Hence, it's required that an unwired port is defined to be a behavior port. Otherwise it would not be possible for the owner capsule to send and receive events on an unwired port.
capsule Pinger {\n service publish unwired port p1 : PROTO; // ART_0024\n\n statemachine {\n state State1;\n initial -> State1;\n };\n};\n
Two Quick Fixes are available for fixing this problem. Either the port can be turned into a behavior port, or it can be turned into a wired port.
"},{"location":"validation/#art_0025_portconnectionerror","title":"ART_0025_portConnectionError","text":"Severity Reason Quick Fix Error A wired port is not properly connected, or an unwired port is connected. N/AAn unwired port must not be connected to another port by means of a connector. Instead you should register such a port dynamically so that it can be connected at runtime with another matching port.
A wired port, however, should be connected. A service port that is not a behavior port should be connected both on the \"inside\" and on the \"outside\" by two connectors. That is because the purpose of such a relay port is to simply relay communication from one port to another. By \"inside\" we mean the composite structure of the capsule that owns the port, and by \"outside\" we mean the composite structure to which the part that is typed by the capsule belongs. If the service port is instead a behavior port, it should only be connected on the \"outside\".
The sample below contains several unwired ports that are connected (although they should not be). The validation rule also detects the problem that the service behavior port p1
is connected on the \"inside\". (The fact that it's also not connected on the \"outside\" is reported by ART_0039_portPartMultiplicityMismatch).
protocol PROTO {\n in in1();\n out out1();\n};\n\ncapsule Top { \n part ping : Pinger, // ART_0025 (unwired_port1 is connected in capsule Top) \n pong : Ponger; // ART_0025 (unwired_port2 is connected in capsule Top)\n\n part a : Another; // ART_0025 (behavior port p1 is connected on the inside)\n // ART_0039 (behavior port p1 is not connected on the outside)\n\n connect ping.unwired_port1 with pong.unwired_port2;\n\n service publish behavior port unwired_port1 : PROTO; // ART_0025 (publish implies unwired and unwired ports must not be connected)\n service subscribe behavior port unwired_port2~ : PROTO; // ART_0025 (subscribe implies unwired and unwired ports must not be connected)\n connect unwired_port1 with unwired_port2; \n\n unwired behavior port unwired_port3~ : PROTO; // ART_0025 (unwired ports must not be connected)\n\n connect ping.p2 with unwired_port3; \n\n statemachine {\n state T21;\n initial -> T21;\n };\n};\n\ncapsule Pinger {\n service publish behavior port unwired_port1 : PROTO;\n\n service port p2 : PROTO;\n\n statemachine {\n state State1;\n initial -> State1;\n };\n};\n\ncapsule Ponger {\n service subscribe behavior port unwired_port2~ : PROTO;\n\n statemachine {\n state State1; \n initial -> State1;\n };\n};\n\ncapsule Another {\n service behavior port p1 : PROTO; \n part inner : Inner;\n connect p1 with inner.p; \n};\n\ncapsule Inner {\n service behavior port p : PROTO;\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0026_connectedportswithincompatibleconjugations","title":"ART_0026_connectedPortsWithIncompatibleConjugations","text":"Severity Reason Quick Fix Error A connector connects two ports with incompatible conjugations. N/A Ports connected by a connector must have compatible conjugations. If the ports are at the same level in the capsule's structure (e.g. both ports belong to capsules typing capsule parts owned by the same capsule), then the connected ports must have opposite conjugations. This is because events that are sent out from one of the ports must be able to be received by the other port. However, if the ports are at different levels in the capsule's structure (e.g. one of them belongs to a capsule typing a capsule part owned by the capsule and the other belongs to the capsule itself), then the ports must have the same conjugation. This is because in this case events are simply delegated from one capsule to another.
capsule Top { \n part ping : Pinger, pong : Ponger; \n connect ping.p1 with pong.p2; // ART_0026 (same port conjugations but should be different)\n\n statemachine {\n state T21;\n initial -> T21;\n };\n};\n\ncapsule Inner {\n service behavior port p : PROTO;\n\n statemachine {\n state State;\n initial -> State;\n };\n};\ncapsule Pinger { \n service port p1~ : PROTO; \n part inner : Inner;\n connect p1 with inner.p; // ART_0026 (different port conjugations but should be same)\n\n statemachine {\n state State1;\n initial -> State1;\n };\n};\n\ncapsule Ponger {\n service behavior port p2~ : PROTO; \n\n statemachine {\n state State1; \n initial -> State1;\n };\n};\n
Here we see that both connectors are invalid. Port p2
and port p1
are at the same level in Top
's structure so their conjugations should be different, while port p1
and port p
are at different levels in Top
's structure so their conjugations should be the same.
Ports connected by a connector must have compatible protocols. For Code RealTime this means that the protocols must be the same.
Note
Model RealTime uses a different criteria for protocol compatibility. There two protocols are compatible if all events that can be sent by a port typed by the source protocol can be received by the other port typed by the target protocol. Also in Model RealTime the most common case is that the source and target protocols are the same, but they can also be different as long as all their events (both in-events and out-events) match both by name and parameter data type. This is a legacy behavior which is not recommended to use, and hence not supported by Code RealTime.
protocol PROTO1 { \n in pong(); \n out ping();\n};\n\nprotocol PROTO2 {\n in pong();\n out ping();\n};\n\nprotocol PROTO3 { \n in pong(); \n out ping3();\n};\n\ncapsule Top {\n service port p1 : PROTO1; \n service port p2~ : PROTO2;\n service port p3~ : PROTO3;\n\n connect p1 with p2; // ART_0027 (but OK in Model RealTime)\n connect p1 with p3; // ART_0027 (also not OK in Model RealTime due to event ping3)\n\n statemachine {\n state T21;\n initial -> T21;\n };\n};\n
"},{"location":"validation/#art_0028_unwiredportwithautoregneitherpublishernorsubscriber","title":"ART_0028_unwiredPortWithAutoRegNeitherPublisherNorSubscriber","text":"Severity Reason Quick Fix Error An unwired port is registered automatically but is neither specified as a publisher or subscriber. Make Publisher, Make Subscriber An unwired port is either a publisher (SPP) or subscriber (SAP) at run-time. Unless the port has the registration property set to application
it will be registered automatically when the container capsule instance gets created. Hence it's necessary in this case to declare the port either as a publisher or subscriber.
capsule CCXX {\n unwired behavior port pu // ART_0028\n [[rt::properties(\n registration=automatic_locked\n )]]\n : PPX;\n\n unwired service behavior port pu2 : PPX; // ART_0028 (default registration is \"automatic\")\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
Two Quick Fixes are available for fixing this problem. Either the port can be declared as a publisher (keyword publish
) or as a subscriber (keyword subscribe
).
If a composite state is entered without using an entry point, the behavior may be different the first time the state is entered compared to subsequent times it's entered. The first time the initial transition of the composite state will execute, while after that it will be entered using deep history (i.e. directly activate the substate that was previously active in the composite state). This difference in behavior is not evident just by looking at the state diagram, and can therefore be surprising and cause bugs. It's therefore recommended to always enter a composite state using an entry point. See Hierarchical Statemachines for more information.
capsule Cap {\n statemachine {\n state BS {\n entrypoint ep1;\n initial -> Nested;\n state Nested;\n };\n _Initial: initial -> BS; // ART_0029\n };\n};\n
"},{"location":"validation/#art_0030_transitiontocompositestatenoentrynoinitialtrans","title":"ART_0030_transitionToCompositeStateNoEntryNoInitialTrans","text":"Severity Reason Quick Fix Error A composite state is entered without using an entry point, and its state machine has no initial transition. N/A This validation rule is related to ART_0029_transitionToCompositeStateNoEntry. If a composite state is entered without using an entry point, and the nested state machine of the composite state has no initial transition, then it is undefined what to do when entering the state. This is therefore not allowed.
capsule Cap {\n statemachine {\n state BS {\n entrypoint ep1; \n state Nested;\n };\n _Initial: initial -> BS; // ART_0030\n };\n};\n
"},{"location":"validation/#art_0031_portbothnonserviceandnonbehavior","title":"ART_0031_portBothNonServiceAndNonBehavior","text":"Severity Reason Quick Fix Error A port is both a non-service and a non-behavior port at the same time. Make Behavior Port, Make Service Port A port that is not a service port is internal to a capsule. For such a port to be useful it must be a behavior port; otherwise the capsule cannot send and receive events on the port. Hence, a non-service port cannot at the same time be a non-behavior port.
Quick Fixes are available for either declaring the port as a behavior or service port.
capsule C31 {\n port fp : Proto; // ART_0031\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0032_unrecognizedcolor","title":"ART_0032_unrecognizedColor","text":"Severity Reason Quick Fix Warning A color is specified for an element but the color was not recognized. N/A A color can be assigned to most elements and will be used when showing the element on a diagram. Colors should be specified as RGB values using 6 hexadecimal digits. In case the color value is on another format it will not be recognized and will be ignored when rendering the diagram.
capsule C32 {\n behavior port frame [[rt::properties(color=\"#gd1d1d\")]]: Frame; // ART_0032 (invalid hex digit 'g')\n behavior port p [[rt::properties(color=\"#cc\")]] : Proto; // ART_0032 (too few digits)\n\n statemachine {\n state State [[rt::properties(color=\"#5d04040\")]]; // ART_0032 (too many digits)\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0033_connectednonserviceportonpart","title":"ART_0033_connectedNonServicePortOnPart","text":"Severity Reason Quick Fix Error A connector connects a port on a part but the port is not a service port. N/A A port is only visible from the outside of a capsule if it is a service port. Hence, a connector cannot connect a port on a part unless the port is a service port.
capsule C33 {\n optional part thePart : Other;\n behavior port bp~ : Proto; \n connect bp with thePart.bp2; // ART_0033\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n\ncapsule Other {\n behavior port bp2 : Proto;\n\n statemachine {\n state State;\n initial -> State;\n };\n}\n
In a structure diagram this error means that a connector \"crosses a capsule boundary\" by connecting two ports at different levels in the composite structure.
The solution here is to either make bp2
a service port, or to create another service port in the Other
capsule and then connect that port both to bp
on the \"outside\" and to bp2
on the \"inside\".
A service port is part of the externally visible communication interface for a capsule. Hence the protocol that types a service port should have at least one event, otherwise the service port doesn't add any value. The only exception is a notification port which receives the rtBound
and rtUnbound
events when the port gets connected or disconnected to another port at runtime. This means that a notification port can be useful even if its protocol doesn't contain any events.
protocol EmptyProtocol {\n};\n\ncapsule C34 {\n service port myPort : EmptyProtocol; // ART_0034\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0035_timerserviceport","title":"ART_0035_timerServicePort","text":"Severity Reason Quick Fix Warning A timer port is a declared to be a service port. Make Non-Service Behavior Port A timer port is typed by the predefined Timing protocol. It has one event timeout
which is sent to the port after a certain timeout period (either once or periodically). Other capsules cannot send the timeout
event to the capsule that owns the timer port. Hence a timer port should always be a non-service behavior port.
A Quick Fix is available that will remove the service
keyword for the port and if necessary also add the behavior
keyword.
capsule C35 {\n service port t : Timing; // ART_0035\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0036_unexpectedtriggers","title":"ART_0036_unexpectedTriggers","text":"Severity Reason Quick Fix Error A transition that originates from a pseudo state must not have triggers, but still has at least one. Remove Triggers A transition can only have triggers if it originates from a state, because it's only when the state machine is in a state that a received and dispatched message can trigger a new transition to execute. A transition that originates from a pseudo state (such as a choice or junction) must therefore not have any triggers.
Note that even if entry and exit points are pseudo states, the rule mentioned above does not apply for them unless they are connected with an incoming transition. If there is no incoming transition, an entry/exit point represents the enclosing state and the transition that leaves it can (in fact, should) have triggers. Read more about this here.
A Quick Fix is available that will remove the triggers of the transition (hence converting it from a triggered to a non-triggered transition).
capsule C36 {\n behavior port t : Timing;\n statemachine { \n state State, State1, State2;\n state Composite {\n entrypoint ep, ep2;\n exitpoint ex, ex2;\n state Nested;\n ep -> Nested on t.timeout; // OK (transition originates from entry point without incoming transition)\n ep2 -> Nested on t.timeout; // ART_0036 (transition originates from entry point with an incoming transition) \n Nested -> ex on t.timeout;\n };\n junction j;\n choice c;\n initial -> j;\n State -> Composite.ep2 on t.timeout;\n t1 : j -> State on t.timeout; // ART_0036 (transition originates from junction)\n t2 : State1 -> c on t.timeout;\n t3: c -> State2 on t.timeout; // ART_0036 (transition originates from choice)\n t4: Composite.ex -> State2 on t.timeout; // ART_0036 (transition originates from exit point)\n t5: Composite.ex2 -> State2 on t.timeout; // OK (transition originates from exit point without incoming transition)\n };\n};\n
In case the state machine is inherited, and the problem is detected for an inherited transition, then it is reported on the state machine instead. The inherited transition with the unexpected trigger(s) will be reported as a related element. In this case the Quick Fix can not be used for removing the triggers. Here is an example that shows how inheritance can cause a transition that is correct in a base state machine to become incorrect in a derived state machine:
capsule B2 {\n behavior port t : Timing;\n statemachine {\n state State;\n state Composite {\n entrypoint ep;\n state Nested;\n t1 : ep -> Nested on t.timeout; // OK (ep has no incoming transition here)\n };\n initial -> State;\n };\n};\n\ncapsule D2 : B2 { \n statemachine { // ART_0036 (here ep has an incoming transition which makes the inherited t1 incorrect)\n tx : State -> Composite.ep on t.timeout; \n };\n};\n
"},{"location":"validation/#art_0037_missingtriggers","title":"ART_0037_missingTriggers","text":"Severity Reason Quick Fix Error A transition that originates from a state must have at least one trigger, but has none. N/A A transition that originates from a state is only meaningful if it specifies as least one trigger. Otherwise the transition cannot be triggered and would be useless. As mentioned in this chapter an entry or exit point without incoming transition represents the state that owns the entry or exit point. A transition that originates from such an entry or exit point must therefore have a trigger.
capsule C37 {\n behavior port t : Timing; \n statemachine { \n state State, State1, State2, State3;\n state Composite {\n entrypoint ep, ep2;\n exitpoint ex, ex2;\n state Nested; \n t8: ep -> Nested; // ART_0037 (transition originates from entry point without incoming transition)\n ep2 -> Nested; // OK (transition originates from entry point with incoming transition)\n t6: Nested -> ex2; // ART_0037 (transition originates from state)\n }; \n t7: State -> Composite.ep2; // ART_0037 (transition originates from state)\n junction j;\n choice c;\n initial -> j;\n t1 : j -> State;\n t2 : State1 -> c; // ART_0037 (transition originates from state)\n t3: c -> State2; \n t4: Composite.ex -> State2; // ART_0037 (transition originates from exit point without incoming transition)\n t5: Composite.ex2 -> State3; // OK (transition originates from exit point with incoming transition)\n };\n};\n
In case the state machine is inherited, and the problem is detected for an inherited transition, then it is reported on the state machine instead. The inherited transition with the missing trigger will be reported as a related element. Here is an example that shows how inheritance can cause a transition that is correct in a base state machine to become incorrect in a derived state machine:
capsule BB2 {\n behavior port t : Timing;\n statemachine {\n state State;\n state Composite {\n entrypoint ep;\n state Nested;\n t1 : ep -> Nested; // OK (ep has an incoming transition here)\n };\n initial -> State;\n tx : State -> Composite.ep on t.timeout; \n };\n};\n\ncapsule DD2 : BB2 { \n statemachine { // ART_0037 (here ep has no incoming transition which makes the inherited t1 incorrect)\n exclude tx;\n };\n};\n
"},{"location":"validation/#art_0038_portwithpredefinedprotocolnotcorrectlydeclared","title":"ART_0038_portWithPredefinedProtocolNotCorrectlyDeclared","text":"Severity Reason Quick Fix Warning A port with a predefined protocol is declared in a way that is only applicable for ports with a user-defined protocol. N/A Ports typed by a predefined protocol (Timing, Log, External, Exception or Frame) cannot be used in the same way as ports that are typed by user-defined protocols. For example, it does not make sense to connect such ports with connectors since no events can be sent to them. Because of this, the following keywords are not applicable for such ports: notify
, publish
, subscribe
, unwired
.
If you use one or many of these keywords when declaring a port with a predefined protocol, they will be ignored and this problem will be reported. The problem will also be reported if you declare such a port as conjugated.
capsule C38 {\n unwired publish behavior port x : Timing; // ART_0038 ('unwired' and 'publish' not applicable)\n behavior port log~ : Log; // ART_0038 (cannot be conjugated)\n notify behavior port frame : Frame; // ART_0038 ('notify' not applicable)\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0039_portpartmultiplicitymismatch","title":"ART_0039_portPartMultiplicityMismatch","text":"Severity Reason Quick Fix Warning The multiplicities of two connected ports are inconsistent, or a port is missing an expected connector. N/A Wired ports should be connected, and connected in a way that is consistent with their multiplicities. The following problems can otherwise occur at runtime:
In the example below a connector is missing between port p2
of part pong
and port p1
of part ping
. Without this connector no runtime connection can be established between ports p2
and p
which means that any message sent on those ports will be lost.
capsule Top { \n part ping : Pinger, // ART_0039 (not connected in capsule Top)\n pong : Ponger; // ART_0039 (not connected in capsule Top) \n\n statemachine {\n state T21;\n initial -> T21;\n };\n};\n\ncapsule Inner {\n service behavior port p : PROTO; \n\n statemachine {\n state State;\n initial -> State;\n };\n};\n\n\ncapsule Pinger {\n service port p1 : PROTO; \n part inner : Inner;\n connect p1 with inner.p; \n\n statemachine {\n state State1;\n initial -> State1;\n };\n};\n\ncapsule Ponger {\n service behavior port p2~ : PROTO; \n\n statemachine {\n state State1; \n initial -> State1;\n };\n};\n
In the example below the connectors connect ports and ports with parts where the multiplicities don't match.
capsule Top { \n part ping : Pinger; \n\n behavior port topP1 : PROTO[2]; // ART_0039\n behavior port topP2~ : PROTO; // ART_0039\n\n connect topP1 with topP2;\n\n statemachine {\n state T21;\n initial -> T21;\n };\n};\n\ncapsule Inner {\n service behavior port p~ : PROTO[2]; \n\n statemachine {\n state State;\n initial -> State;\n };\n};\n\ncapsule Pinger { \n part inner : Inner [4]; // ART_0039\n\n behavior port px : PROTO[3]; // ART_0039\n connect px with inner.p;\n behavior port px2 : PROTO[4]; // ART_0039\n connect px2 with inner.p;\n\n statemachine {\n state State1;\n initial -> State1;\n };\n};\n
Note that the runtime capacity of port p
is 8 (the port multiplicity times the part multiplicity; 2 * 4 = 8). This port is connected to two other ports (px
and px2
) with a total capacity of 4 + 3 = 7. Raising the multiplicity of px
to 4 will remove the inconsistency.
Some problems in an Art file cannot be detected until it's translated to C++ code. The code generator implements validation rules for detecting and reporting such problems.
Note
When an Art file is edited in the UI code generation validation rules run just after the Art validation rules. However, they will only run if there is an active TC, since otherwise code generation will not happen.
When you run the Art Compiler and the Art validation rules found at least one problem with Error severity, then code generation will not happen and hence these validation rules will not run.
Validation rules that are related to code generation can be enabled and disabled, and have their severity customized, in the same way as the Art validation rules. Code generation validation rules have the prefix \"CPP\" and ids in the range starting from 4000 and above. They are listed below.
"},{"location":"validation/#cpp_4000_eventtypewithouttypedescriptor","title":"CPP_4000_eventTypeWithoutTypeDescriptor","text":"Severity Reason Quick Fix Warning An event type has a type for which no type descriptor could be found. N/AIf an event has a data parameter the type of this parameter must have a type descriptor. Otherwise the TargetRTS doesn't know how to copy or move the data at run-time when the event is sent. The TargetRTS provides type descriptors for most predefined C++ types. For user-defined types the code generator assumes a type descriptor will be available for it (either automatically generated by means of the rt::auto_descriptor
attribute, or manually implemented). It's necessary that such a user-defined type is defined so that it can be referenced from the event with a simple name without use of qualifiers, type modifiers or template arguments. Use a typedef or type alias to give a simple name to an existing type that for example is defined in a different namespace.
If the code generator doesn't find a type descriptor for the event parameter type, CPP_4000 will be reported, and the C++ function that is generated for the event will have void type (i.e. the same as if the event doesn't have a data parameter).
protocol PROT {\n out e1(`MyClass*`); // ART_4000 (type modifier present)\n out e2(`std::string`); // ART_4000 (qualified name)\n out e3(`TplClass<int>`); // ART_4000 (template parameter present) \n};\n
"},{"location":"validation/#cpp_4001_unreachabletransition","title":"CPP_4001_unreachableTransition","text":"Severity Reason Quick Fix Warning One or many transitions are unreachable due to ambiguous triggers. N/A If there are multiple outgoing transitions from a state with identical triggers (i.e. having the same port and the same event, and no guard condition), then it's ambiguous which one of them that will be triggered. In this situation the code generator will pick one of them to execute, and report the other ones as unreachable. The unreachable transitions will be reported as related elements so you can navigate to them and decide how to resolve the ambiguity.
Common solutions for this problem is to add a guard condition, either on the trigger or the transition. With a guard condition the ambiguity is resolved, but it's of course then important to ensure that guard conditions are mutually exclusive so that only one of the transitions can execute. The code generator cannot ensure this, since guard conditions are evaluated at run-time.
capsule XCap { \n behavior port t : Timing;\n statemachine {\n state State1, State2;\n initial -> State1 \n `\n t.informIn(RTTimespec(1,0));\n `;\n t1: State1 -> State2 on t.timeout;\n t2: State1 -> State2 on t.timeout; // CPP_4002\n };\n};\n
Code for calling unreachable transitions will still be generated, but preceeded with a comment. Here is an example:
case Timing::Base::rti_timeout:\n chain2_t1( );\n return ;\n // WARNING: unreachable code;\n chain3_t2( );\n return ;\n
"},{"location":"validation/#cpp_4002_guardedinitialtransition","title":"CPP_4002_guardedInitialTransition","text":"Severity Reason Quick Fix Error The initial transition leads to a junction where all outgoing transitions have guard conditions. N/A The initial transition is the first transition that executes in a state machine. It must always lead to the activation of a state where the state machine will stay until it receives its first event. It is allowed to use junctions in the initial transition to let guard conditions decide which state that should be activated. However, in this case it's required that there is at least one junction transition without a guard condition (or with an else
guard). If all transition paths are guarded there is a risk that none of the guard conditions will be fulfilled, which would mean that no state will be activated. That would effectively break the functioning of the state machine.
capsule N { \n statemachine {\n state State;\n junction j;\n initial -> j;\n j -> State when `x == 5`; // CPP_4002\n };\n};\n
"},{"location":"validation/#tc-validation-rules","title":"TC Validation Rules","text":"TC files are validated to detect problems related to TC properties. The rules that perform this validation can be enabled and disabled, and have their severity customized, in the same way as the Art validation rules (with the exception that it's only possible to configure these rules globally). They use the prefix \"TC\" and ids in the range starting from 7000 and above. These rules are listed below.
"},{"location":"validation/#tc_7000_wrongvaluetype","title":"TC_7000_wrongValueType","text":"Severity Reason Quick Fix Error A TC property has the wrong type of value. N/AEach TC property has a type as shown in this table. The value provided for a TC property must have the expected type. Here are some examples where TC property values have types that don't match the types of these TC properties:
tc.copyrightText = true; // TC_7000 (expects a string, and not a boolean)\ntc.cppCodeStandard = 98; // TC_7000 (expects an enum string such as \"C++ 98\", and not a number)\ntc.sources = ''; // TC_7000 (expects a list of strings, and not a single string)\n
"},{"location":"validation/#tc_7001_tcpropertynotyetsupported","title":"TC_7001_tcPropertyNotYetSupported","text":"Severity Reason Quick Fix Warning A TC property is assigned a value, but Code RealTime does not yet support this property. N/A TC files are not only used by Code RealTime but also by Model RealTime. Even if the format of TC files is the same in these products, there are certain TC properties which are supported by Model RealTime, but not yet supported by Code RealTime. You can still assign values to such properties but they will be ignored.
tc.compilationMakeInsert = ''; // TC_7001\n
"},{"location":"validation/#tc_7002_propertynotapplicableforlibrarytc","title":"TC_7002_propertyNotApplicableForLibraryTC","text":"Severity Reason Quick Fix Warning A TC property that is only applicable for an executable TC is used in a library TC. N/A Certain TC properties are only meaningful if used in a TC that builds a library. For example, setting linkCommand
does not make sense on a library TC since a library is not linked.
let tc = TCF.define(TCF.CPP_TRANSFORM);\n// The \"topCapsule\" property is not set, which means this is a library TC\ntc.linkCommand = 'ld'; // TC_7002\n
"},{"location":"validation/#tc_7003_prerequisitepatherror","title":"TC_7003_prerequisitePathError","text":"Severity Reason Quick Fix Error A prerequisite TC cannot be resolved. N/A TCs that are specified as prerequisites must exist. If the path cannot be resolved to a valid TC file then it must either be corrected or deleted. A relative path is resolved against the location of the TC where the prerequisites
property is set.
tc.prerequisites = [\"../../TestUtils/testlibX.tcjs\"]; // TC_7003 (referenced TC file does not exist)\n
"},{"location":"validation/#tc_7004_invalidtopcapsule","title":"TC_7004_invalidTopCapsule","text":"Severity Reason Quick Fix Error The specified top capsule cannot be found. N/A The topCapsule
property is mandatory for executable TCs. In fact, it's the presence of this property that makes it an executable TC. A capsule with the specified name must exist in one of the Art files that is built by the TC (directly or indirectly). If you specify a top capsule that cannot be found, make sure you have spelled it correctly (use Content Assist in the TC editor so you can avoid typos). Also make sure that the Art file where the top capsule is defined is not excluded from the build by use of the sources
property.
tc.topCapsule = 'NonExistentCapsule'; // TC_7004 (referenced top capsule does not exist)\n
"},{"location":"validation/#tc_7005_invalidunitname","title":"TC_7005_invalidUnitName","text":"Severity Reason Quick Fix Error The unitName
property contains characters that are illegal in a file name. N/A The unitName
property specifies the name of the generated unit files (by default called UnitName.h
and UnitName.cpp
). Hence, it cannot contain characters that are not allowed in a file name. Different operating systems have different rules that a valid file name must adhere to.
tc.unitName = 'UnitName:1'; // TC_7005 (colon is not a valid file name character on Windows)\n
"},{"location":"validation/#tc_7006_invalidtargetrtslocation","title":"TC_7006_invalidTargetRTSLocation","text":"Severity Reason Quick Fix Error The specified path to the TargetRTS does not exist or is invalid. N/A The targetRTSLocation
property must specify a folder that exists and contains a TargetRTS to compile generated code against.
tc.targetRTSLocation = \"C:\\\\MyTargets\\\\\"; // TC_7006 (if that folder does not exist)\n
"},{"location":"validation/#tc_7007_invalidtargetconfig","title":"TC_7007_invalidTargetConfig","text":"Severity Reason Quick Fix Error The specified target configuration does not exist. N/A The targetConfiguration
property must specify the name of a target configuration that exists in the folder specified by the targetRTSLocation
property. If you specify a target configuration that cannot be found, make sure you have spelled it correctly (use Content Assist in the TC editor so you can avoid typos).
tc.targetConfiguration = \"WinT.x64-MinGW-12.2.0\"; // TC_7007 (misspelled \"MinGw\")\n
"},{"location":"validation/#tc_7008_invalidcodestandard","title":"TC_7008_invalidCodeStandard","text":"Severity Reason Quick Fix Error The specified C++ code standard does not exist. N/A The cppCodeStandard
property must specify a valid C++ code standard. If you specify a code standard that cannot be found, make sure you have spelled it correctly (use Content Assist in the TC editor so you can avoid typos).
tc.cppCodeStandard = \"C++ 18\"; // TC_7008 (there is no C++ language standard C++ 18)\n
"},{"location":"validation/#tc_7009_invalidtargetfolder","title":"TC_7009_invalidTargetFolder","text":"Severity Reason Quick Fix Error The specified target folder is invalid. N/A The targetFolder
property specifies the folder where to place generated files. The folder doesn't have to exist, since it will be created automatically by the code generator if needed. However, it's required that the folder has a name that is valid. Different operating systems have different rules that a valid folder name must adhere to.
tc.targetFolder = 'capsule_cpp_inheritance_target:'; // TC_7009 (invalid character ':' in target folder)\n
"},{"location":"validation/#tc_7010_physicalthreadwithoutlogicalthread","title":"TC_7010_physicalThreadWithoutLogicalThread","text":"Severity Reason Quick Fix Warning A physical thread has no logical thread mapped to it. N/A A physical thread is referenced through a logical thread that is mapped to it in the TC. Multiple logical threads can be mapped to the same physical thread, but if a physical thread has no logical threads mapped to it, it's useless since it then cannot be referenced by the application. Note that this rule does not apply for the default MainThread and TimerThread.
Solve this problem either by deleting the Thread object that represents the physical thread from the TC, or map a logical thread to it using the logical
property.
See the threads
property for more information.
tc.threads = [\n{\n name: 'MyThread',\n logical: [ ] // TC_7010\n}\n];\n
"},{"location":"validation/#tc_7011_duplicatephysicalthreadname","title":"TC_7011_duplicatePhysicalThreadName","text":"Severity Reason Quick Fix Error There are multiple physical threads with the same name. N/A The names of physical threads in an application must be unique. See the threads
property for more information.
tc.threads = [\n{\n name: 'MyThread', \n logical: [ 'L1' ] \n},\n{\n name: 'MyThread', // TC_7011 (MyThread already defined)\n logical: [ 'L2' ] \n}\n];\n
"},{"location":"validation/#tc_7012_duplicatelogicalthreadname","title":"TC_7012_duplicateLogicalThreadName","text":"Severity Reason Quick Fix Error There are multiple logical threads with the same name. N/A The names of logical threads in an application must be unique. In a library TC the logical threads are specified as a list of strings in the threads
property and this list should not contain duplicates. In an executable TC the logical threads are instead defined implicitly when mapping them to physical threads using the logical
property on the Thread object. Also in this case, the same logical thread name should not be used more than once.
tc.threads = [\n{\n name: 'MyThread', \n logical: [ 'L1', 'L1', 'L2' ] // TC_7012 (L1 defined (and mapped to MyThread) twice)\n},\n{\n name: 'MyThread2', \n logical: [ 'L2' ] // TC_7012 (L2 already mapped to MyThread above)\n}\n];\n
A special situation is when an executable TC has several library TCs as prerequisites (direcly or indirectly). These library TCs may define logical threads with clashing names. You must make sure that names of logical threads in all prerequisite libraries are unique. One way to accomplish this could be to prefix a logical thread in a library with the name of the library.
"},{"location":"validation/#tc_7013_physicalthreadsinlibrary","title":"TC_7013_physicalThreadsInLibrary","text":"Severity Reason Quick Fix Warning Physical threads are defined in a library TC. N/AIn a library TC you should only define logical threads. These must be mapped to physical threads in the executable TC which has the library TC as a prerequisite. If you anyway define physical threads in a library TC, they will be ignored.
See the threads
property for more information.
// tc.topCapsule not defined, i.e. this is a library TC\ntc.threads = [ // TC_7013\n{\n name: 'MyThread', \n logical: [ 'L1' ] \n}\n];\n
"},{"location":"validation/#tc_7014_incorrectthreadproperty","title":"TC_7014_incorrectThreadProperty","text":"Severity Reason Quick Fix Error A thread property is incorrectly specified. N/A This problem is reported if the threads
property contains a value of unexpected type. For an executable TC this property should be set to a list of Thread objects representing physical threads, while for a library TC it should be set to a list of strings which are the names of the logical threads defined in the library.
The problem is also reported if a Thread object for a physical thread has a property of unexpected type. All properties of such a Thread object should be of string type, except logical
which should be a list of strings.
tc.threads = [ \n{\n name: 'MyThread',\n priority: 20000, // TC_7014 (the priority should be specified as a string and not a number)\n logical: [ 'L1' ] \n}\n];\n
"},{"location":"validation/#tc_7015_librarythreadnotmappedtophysicalthread","title":"TC_7015_libraryThreadNotMappedToPhysicalThread","text":"Severity Reason Quick Fix Error A logical thread in a library TC is not mapped to a physical thread. N/A A library TC uses the threads
property for specifying logical threads. When an executable TC uses a library TC as its prerequisite, all logical threads of the library must be mapped to physical threads. Read more about library threads here.
// In a library TC lic.tcjs:\ntc.threads = [ 'LibThread1', 'LibThread2' ];\n\n// In an executable TC exe.tcjs:\ntc.prerequisites = [\"lib.tcjs\"];\ntc.threads = [ \n{\n name: 'MyThread',\n logical: [ 'LibThread1' ] // TC_7015 (library logical thread 'LibThread2' not mapped)\n}\n];\n
"},{"location":"validation/#core-validation-rules","title":"Core Validation Rules","text":"There are certain core rules that run before the semantic validation rules mentioned above. They are responsible for making sure that the Art file is syntactically correct and that all references it contains can be successfully bound to valid Art elements.
Since these rules run before semantic validation rules they cannot be disabled or have their severity changed. Core validation rules have ids in the range starting from 9000 and above and are listed below.
"},{"location":"validation/#art_9000_syntaxerror","title":"ART_9000_syntaxError","text":"Severity Reason Quick Fix Error Parsing of the Art file failed due to a syntax error. Remove Extraneous InputIf an Art file contains a syntax error, it cannot be parsed into valid Art elements and the parser will then report this error. There are many ways to introduce syntax errors in an Art file and the error message from the parser will usually help you understand what is wrong by telling you both what incorrect thing was encountered and what is instead expected at that position.
If the syntax error is caused by extra characters at a place where no extra characters are expected a Quick Fix can be used for removing the \"extraneous input\".
capsule A { \n state machine // ART_9000 (mismatched input 'state' expecting 'statemachine')\n};\n\ncapsule B { \n statemachine {\n state State;\n initial -> State;\n };\n prt // ART_9000 (extraneous input 'prt' expecting '}')\n};\n
"},{"location":"validation/#art_9001_unresolvedreference","title":"ART_9001_unresolvedReference","text":"Severity Reason Quick Fix Error A referenced Art element cannot be found. N/A For an Art file to be well-formed, all references it contains must be possible to resolve to valid Art elements (located either in the same Art file, or in another Art file in the workspace). Aside from simple spelling mistakes, the most common reason for this error is that you forgot to add the folder that contains the Art file with the target Art element into the workspace. Also note that if the referenced Art element is in a different workspace folder you must have an active TC which specifies a TC in that workspace folder as a prerequisite.
capsule C { \n port p : Unknown; // ART_9001 (Couldn't resolve reference to Protocol 'Unknown'.)\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#internal-errors","title":"Internal Errors","text":"A special validation rule is used for detecting and reporting so called internal errors. These are errors that should never occur, but if they still do they are caused by a defect in Code RealTime. If you encounter an internal error please report it as described here.
"},{"location":"validation/#art_9999_internalerror","title":"ART_9999_internalError","text":"Severity Reason Quick Fix Error An internal error has occurred. N/AInternal errors may arise from bugs and often result from unexpected situations. While it may be possible to workaround an internal error, the problem can only be fully solved by updating Code RealTime. Therefore, the first thing you should do if you get an internal error is to make sure you are running the latest version of Code RealTime (see Releases). If you don't, then please uplift to the latest version as there is a chance the problem has been fixed in that version. If that doesn't help, please report the internal error as described here.
"},{"location":"art-lang/","title":"The Art Language","text":"Art is a language for developing stateful and event-driven realtime applications. By stateful we mean that the application consists of objects whose behavior can be described with state machines. By event-driven we mean that these objects communicate with each other by sending events, which can cause their state machines to transition from one state to another when received.
The Art language provides high-level concepts not directly found in the C++ language. All these high-level concepts are transformed into C++ code by the Art compiler. Generated code uses a run-time library known as the TargetRTS (Target RunTime System). The TargetRTS is a C++ library that acts as a layer between the generated code and the underlying platform (hardware, operating system etc) on which the realtime application runs.
Art is well suited for describing both the behavior and structure of a realtime application, but it uses C++ as expression and action language. C++ is also used for declaring types, variables, functions etc. As a rule of thumb, Art uses C++ for everything where C++ is a good fit, and only provides new language concepts where no appropriate constructs exist in C++. This means that if you already know C++, you can quickly learn Art too, and existing C++ code you have already written can be used in your Art application.
Note that the translation of Art to C++ also involves analysis of the C++ code that is present in the Art files. The code generator supports certain C++ extensions in such embedded C++ code and will \"expand\" them to C++ code as part of code generation for an Art file.
"},{"location":"art-lang/#concepts-and-terminology","title":"Concepts and Terminology","text":"In Art the concept of a capsule is central. A capsule is like a C++ class, but with a few differences and extensions. A C++ class is passive in the sense that a caller can access its public member functions and variables at any time. Hence a C++ object always executes in the context of the caller, and when a member function is called, the caller is blocked until the function call returns. A capsule, however, is active and has its own execution context. This means that we never call a capsule member function or access a capsule member variable from outside the capsule itself. Instead we communicate with the capsule by sending events to it. Each capsule instance has a queue of events it has received and those events will be dispatched to the capsule instance one by one. The sender of the event is not blocked, as the event will be handled by the capsule instance asynchronously when it is later dispatched.
The picture below shows 3 capsule instances each holding a queue with events that have been received, but not yet dispatched. Note that this picture is conceptual. In a real implementation several performance optimizations are applied, for example it's common to let a single thread drive more than one capsule instance, and several capsule instances can share a common event queue. But from a conceptual point of view each capsule instance has its own queue of events that are waiting to be dispatched to it. Events have a priority which determines how they are ordered in the queue. Events with high priority are placed before events with lower priority, and if two events have the same priority they are ordered according to when they arrive.
A capsule may have ports. A port is typed by a protocol which defines the events that may be sent in to the port (these are known as in-events), as well as the events the capsule itself may send out through the port for others to receive (these are called out-events). Ports can be used both for internal and external communication. A port used for external communication is called a service port. Together, the service ports constitute the communication interface of the capsule, and decide what \"services\" the capsule provides for other capsules to use.
A simple capsule which only handles a small number of events, may be able to handle all these events using a single state machine. However, when new ports are added (or new events in protocols typing existing ports), the capsule interface grows and the state machine has to grow with it, since there will be more events for it to handle. Eventually a point is reached where it will not be practical for a capsule to handle any more events in its own state machine, because it has grown too large or complex. If not before, this is the time to define a composite structure for the capsule.
A composite structure is created by decomposing a capsule using capsule parts. A capsule part (or, for simplicity, just part) is typed by another capsule and is a way for a capsule to delegate some of its responsibilities to other capsules. Such a decomposition is purely an implementation detail that is not visible from the outside of the capsule. When you send an event to a capsule you cannot know if the capsule will handle the event itself, or if it will forward the event to another capsule typing one of its capsule parts. The ability to decompose a capsule into parts is important for managing complexity. When a capsule has grown too big and complex you can decompose it into capsule parts without changing the communication interface of the capsule.
Ports of capsules typing capsule parts are connected to each other by means of connectors. A connector is a conceptual construct for showing how events are routed in the composite structure of a capsule. At run-time connectors don't exist, and ports are directly connected to each other. Because of this, it's not mandatory to use connectors. You can also choose to dynamically connect (and disconnect) ports at run-time. Although this provides for more flexibility, it has the drawback of making it impossible to statically visualize the communication paths of a capsule. Ports that connect statically to other ports via connectors are called wired ports. Ports that are connected dynamically without use of static connectors are called unwired ports.
The picture below shows the structure of a capsule Top
which consists of two capsule parts ping
and pong
each holding a capsule instance (a Pinger
capsule and a Ponger
capsule respectively). The connector between the wired ports p
on these capsules makes it possible for these capsules to communicate with each other. Communication can also happen using the unwired ports q1
and q2
if they are connected at run-time. The picture also shows that the capsule Ponger
is further decomposed using a capsule part inner
. All events sent to port p
of Ponger
will be further routed to port i
of the Internal
capsule.
Regardless if ports are statically connected by connectors (wired ports), or dynamically connected at run-time (unwired ports), they must be compatible with each other. This means that the out-events of one port must match the in-events of the other port, for the ports to be possible to connect. This constraint ensures that events are never lost when traveling between two connected ports. To make it possible to describe the events that may be sent between two connected ports using a single protocol, one of the ports can be declared as conjugated. For a conjugated port the meaning of in-events and out-events are swapped, so that the in-events are the events that may be sent out through the port, and the out-events are the ports that may be sent to the port. In the picture above port q1
is non-conjugated () while port q2
is conjugated ().
Both capsule parts and ports may have multiplicity. You can think about a capsule part with multiplicity > 1 as an array that holds capsule instances at run-time. In the same way you can think about a port with multiplicity > 1 as an array that holds connections to port instances at run-time. The multiplicity of ports and parts must match when connecting two ports with each other. Once again, this constraint ensures that events will not be lost when traveling between the connected ports at run-time. The picture below shows a capsule with a part and a port that both have multiplicity > 1. In structure diagrams such parts and ports are shown as \"stacked boxes\".
In addition to regular C++ member functions a capsule may have a state machine as its behavior. A state machine describes how an instance of the capsule may move between different states through its life-time. A transition that connects a source state with a target state may be triggered when a received event from a capsule's event queue is dispatched. Several conditions must hold true for the transition to trigger. For example, the event must match a trigger that specifies the expected type of event and the port on which it was received. It's also possible to associate a boolean guard condition with the transition and/or with the trigger which must be true for the transition to trigger. A transition may have an effect, which is a piece of C++ code that executes when the transition gets triggered.
The picture below shows a state machine containing a few states and transitions. The presence of transition guard code is shown with a yellow dot and the presence of transition effect code is shown with a blue dot. Both these are C++ code snippets that are embedded in the Art file.
When a capsule instance is created (this is sometimes referred to as capsule incarnation), it's state machine starts to execute by triggering the transition that goes out from the initial state (the circular blue symbol to the left in the above diagram). Each state machine must have exactly one such initial state with an outgoing transition. Since this initial transition is triggered automatically when the capsule instance is created it cannot have constraints such as triggers and guard conditions. The initial transition is an example of a non-triggered transition since it cannot have triggers.
The path from the source state to the target state can sometimes consist of more than one transition. In that case only the first of these is a triggered transition that may have triggers that specify when it will trigger. Once the first transition in this path has triggered, subsequent non-triggered transitions will always execute, one by one according to how they are connected in the state machine. However, also non-triggered transitions (with the exception of the initial transition) may have guards. Such guards are usually evaluated before the triggered transition triggers to ensure that they all are enabled, so that it's guaranteed that the target state can be reached. There is one exception to this rule, for transitions that leave a choice. Such guards are only evaluated once the choice has been reached to dynamically decide which outgoing transition to take next. This also means that guards of such transitions must be written so that at least (and at most) one outgoing transition is enabled, or there is a risk that the state machine will get stuck in the choice.
In the state machine shown below the transitions t2
and t5
are triggered transitions, while other transitions are non-triggered. Transition t5
can only be triggered if either the guard of t7
or t6
is true, while t2
can be triggered even if neither the guard of t3
nor t4
is true. The target of transition t5
is a junction which is used for either splitting or merging transition paths depending on evaluated guard conditions.
A state may be decomposed by a sub state machine. Such a state is called a composite state and a state machine that has composite states is called a hierarchical state machine. Transitions enter a composite state through an entry point and exit it through an exit point.
Usually an entry point is connected to a nested state inside the state machine of the composite state, but it can also connect to a deep history. Reaching the deep history of a composite state means that all sub states that were previously active will become active again. Hence, deep history is a way to restore a composite state so all its nested states will be reactivated again recursively.
The picture below shows a state machine with a composite state Composite
containing two nested states S1
and S2
. When this state machine starts to execute state S1
first becomes active since Composite
is entered using the ep1
entry point. Later, when leaving S2
through the ex1
exit point, state X
becomes active. Then when leaving X
through the transition that connects to the ep2
entry point the state S1
once again becomes active since ep2
is connected to the deep history. Of course, whenever a nested state is active, the enclosing composite state is also active. At any point in time a state machine has an active state configuration, which consists of the set of currently active states.
A state may have an entry action and/or exit action which is a C++ code snippet that gets executed whenever the state is entered or exited. Note that state entry actions for nested states also run when those states are entered because of a deep history. In state diagrams the presence of entry and/or exit actions are shown by icons just below the state name. In the state machine shown below state S1
has an entry action, state S2
has an exit action and state S3
has both an entry and an exit action.
A transition where the source and target state is the same state is called a self-transition. A special kind of self-transition is an internal transition, which is a transition that when triggered doesn't leave the current state. Hence, when an internal transition is triggered the active state configuration remains unchanged, and neither the entry nor exit action of the state gets executed. In the state machine shown below the state has two self-transitions; t
which is a regular self-transition (a.k.a. external self-transition) and it
which is an internal transition. Since a state may have a large number of internal transitions they are not shown inside the state symbol, but if you select the state symbol you can see them in the Properties view. An icon is shown in the upper right corner of states that contain internal transitions.
State machines can not only be defined for capsules but also for regular classes. This can be useful if you want a plain passive C++ class to have a state machine. Contrary to a capsule a class may not have ports and doesn't execute in its own context. It's therefore common to associate such a class with a capsule that it can use for sending events through its ports. Transitions of a passive class state machine are triggered by calling trigger operations on the class. Such operations have no code, but just trigger transitions in the class state machine.
The realtime application needs to designate one capsule as the top capsule. This is done in the transformation configuration, which is a file containing all the properties used for building the application (e.g. code generator options, compiler settings etc.). There is no language construct in Art for defining a top capsule; any capsule that you define can act as the top capsule. However, in practise you typically decide at an early stage which capsule that will be the top capsule.
The top capsule is the entry point of the realtime application. When it starts to execute one instance of the top capsule will be automatically created, and its state machine starts to execute. If you build a library rather than an executable you don't have a top capsule.
"},{"location":"art-lang/#embedded-c-code","title":"Embedded C++ Code","text":"Art uses C++ as action and expression language. It also uses C++ for defining types, variables and functions. A C++ code snippet can be embedded into an Art file at many places by enclosing it with backticks. Here is an example of how to write the code that should execute when a transition triggers:
S1 -> S2 on timer.timeout\n`\n std::cout << \"Hello World!\" << std::endl;\n`;\n
Here is another example that shows how to include some C++ code as the implementation preface of a capsule:
capsule BrewControl {\n [[rt::impl_preface]]\n `\n #include <iostream>\n `\n};\n
If the code snippet needs to contain the backtick character, for example in a comment or a string literal, you need to escape it by preceeding it with a backslash (\\
). For example:
capsule StringProcessor {\n [[rt::decl]]\n `\n // A member variable with default value \\`\n char escapeChar = '\\`'; \n `\n statemachine {\n state State;\n initial -> State;\n };\n}; \n
"},{"location":"art-lang/#file-level-code-snippets","title":"File-level Code Snippets","text":"Code snippets can not only be associated with Art language constructs as in the above two examples, but can also be placed at the Art file level. There are two such file-level code snippets:
May contain arbitrary C++ declarations. All these code snippets will be generated into a C++ header file with the same name as the Art file.
May contain arbitrary C++ implementations. All these code snippets will be generated into a C++ implementation file with the same name as the Art file.
As an example, assume we have an Art file sample.art
with the following contents
[[rt::decl]]\n`\n typedef C* Cptr;\n Cptr func1();\n`\n[[rt::impl]]\n`\n Cptr func1() {\n return nullptr;\n }\n`\n
Two C++ files will be generated from this Art file:
sample.art.h
typedef C* Cptr;\nCptr func1();\n
sample.art.cpp
#include \"sample.art.h\"\n\nCptr func1() {\n return nullptr;\n}\n
File-level code snippets are useful whenever you need to include some C++ code in your application that doesn't naturally belong to any particular Art element. They can for example be used for declaring and implementing utility functions or types that are needed by many different Art elements. To use the declared elements from an Art element, you need to add an #include
for the generated header file using a code snippet on the Art element. Note that an #include
is needed even if the Art element is located in the same Art file as the declared elements it wants to use.
Below is an example that shows how a protocol and a capsule can use the type Cptr
defined in sample.art
by adding #include
s:
protocol MyEvents {\n [[rt::header_preface]]\n `\n #include \"sample.art.h\"\n `\n out alert(`Cptr`);\n};\n\ncapsule Cx {\n [[rt::header_preface]]\n `\n #include \"sample.art.h\"\n `\n [[rt::decl]]\n `\n protected:\n Cptr m_ptr; \n `\n // ...\n};\n
Here an rt::header_preface
code snippet is used for making the generated capsule and protocol header files include sample.art.h
while an rt::decl code snippet is used for declaring a member variable m_ptr
for the capsule. See the documentation of the different Art elements below to learn about what code snippets that are available for each kind of Art element.
Hint
As an alternative to placing common C++ code in an Art file you can also use regular C++ files and then include these into the build. See Non-Generated C++ Files for more information.
"},{"location":"art-lang/#art-files-and-folders-and-reference-binding","title":"Art Files and Folders and Reference Binding","text":"Any but the simplest of applications will consist of multiple Art files organized into folders. You can create as many Art files as you like, and every Art file may contain one or several Art elements. Art files containing Art elements that are related to each other should be grouped in a folder. For example, if you build a library from certain Art elements it makes sense to put the Art files with those elements in their own folder.
Folders with Art files should be added as workspace folders, either using the command File - Add Folder to Workspace (to add a folder to an existing workspace), or using the command File - Open Workspace from File (to open an existing workspace from a file that defines the workspace folders). If your application consists of more than a couple of workspace folders use of a workspace file is recommended as it makes it quick and easy to add all workspace folders in one go with a single command.
Note
Art files must be on the top level in a workspace folder. Do not place them in subfolders.
When an Art file contains a reference to an Art element that cannot be found within the same file, other Art files in the workspace will be searched for an Art element with the referenced name. This search starts with the Art files in the same workspace folder. If a matching Art element is found in one of these files, the reference is bound to it. Otherwise an active transformation configuration (TC) is required, which specifies one or several prerequisites. The Art files in the workspace folders where the prerequisite TCs are located will then be searched. The search continues recursively if the prerequisite TC itself has prerequisites.
If a matching Art element cannot be found in any of these locations the reference will be unresolved and an error will be reported. For example:
Couldn't resolve reference to Protocol 'UnknownPort'. (ART_9001_unresolvedReference)\n
For more information about unresolved references, see this validation rule.
"},{"location":"art-lang/#textual-and-graphical-notations","title":"Textual and Graphical Notations","text":"The Art language is a textual language, but many parts of it also have a graphical notation. For example, a state machine can be shown using a graphical state diagram, and the composite structure of a capsule can be shown in a structure diagram. Relationships between capsules, protocols and classes, such as inheritance, can be shown in class diagrams.
Below are examples of these three kinds of diagrams:
Diagrams are automatically updated when the corresponding Art file is modified. They use automatic layout to avoid the need for manual tidy-up of diagrams when something changes. This also significantly reduces the need for storing diagram specific properties in the Art files, such as coordinates or symbol dimensions. However, there are some properties used when rendering diagrams that are stored in the Art file. For example, if you assign a custom color to a state symbol it will be stored as a property on the state.
capsule Cap { \n statemachine {\n state ColorfulState[[rt::properties(color=\"#b40e0e\")]]; \n };\n};\n
Art elements are mostly edited using their textual notation, but diagrams also provide some editing capabilities. However, all edit commands performed from a diagram are actually mapped to corresponding textual modifications of the Art file. Editing from a diagram is therefore simply an alternative, and sometimes more convenient way, of editing the textual Art file.
"},{"location":"art-lang/#syntax","title":"Syntax","text":"Art uses a syntax that should be familiar to developers with knowledge about languages like C++ and Java.
;
,
are used for separating the elements{}
are used for grouping nested elements[]
are used for specifying cardinality (i.e. multiplicity) of elements.
) is used as scope resolution operator//
and block /* */
comments may be freely used for commentingNames of Art elements must be valid C++ identifiers since they will be used as names of C++ definitions in generated code. Names also must not clash with names used in the TargetRTS. Don't worry - the Art language editor will let you know if you choose a name that won't work.
Just like any language, Art has certain keywords that are reserved and which cannot be used as names. These keywords are listed below:
Art keywords abstract behavior capsule choice class connect entry entrypoint exclude exit exitpoint fixed history in initial junction notify on optional out part plugin port protocol publish redefine service state statemachine subscribe template trigger typename unwired when withArt is a case-sensitive language and names may use any capitalization. However, just like with most languages, there are conventions for how to capitalize names. Those conventions are described below where each Art language construct is described in detail.
"},{"location":"art-lang/#comments","title":"Comments","text":"The same kinds of comments as in C++ can be used, i.e. line and block comments.
// line comment\n\n/* block comment */\n\n/* multi-line\n block comment */\n
"},{"location":"art-lang/#capsule","title":"Capsule","text":"A capsule defines an active class with its own execution context. It may have ports through which it can receive events. A capsule has a state machine that describes how instances of the capsule transitions between different states in the response to received events.
Names of capsules are typically nouns, often describing something that performs some form of activity. For example \"Controller\", \"TrafficLight\" or \"FaultHandler\". By convention names of capsules start with uppercase.
Embedded C++ code can be used for declaring member variables, member functions, nested types etc for the capsule.
Here is an example of a capsule with a simple state machine and a member variable.
capsule Elevator {\n [[rt::decl]]\n `\n unsigned int currentLevel = 0;\n `\n statemachine {\n state Waiting;\n initial -> Waiting;\n };\n};\n
Note
Capsule member variables and member functions may be private or protected, but should usually not be public. To avoid threading issues all communication with a capsule should be done using events, and therefore public members are not recommended. An exception is capsule constructors which need to be accessible from other capsules that create instances of the capsule using a capsule factory. If you anyway let a capsule have public members you need to ensure they are only accessed from the same thread that runs the capsule.
The example above uses an rt::decl
code snippet for declaring a capsule member variable. It will have the default visibility which is private. Here is the list of all code snippets that can be used for a capsule:
Just like a regular class a capsule may have constructors. A capsule constructor is declared using an rt::decl
code snippet and defined using an rt::impl
code snippet. All capsule constructors have two mandatory parameters:
RTController*
) which will execute an instance of the capsule. It corresponds to the thread that runs the capsule instance.RTActorRef*
) into which the capsule instance will be inserted. Every capsule instance, except the top capsule, resides in exactly one part.After these parameters you can add your own parameters, to pass arbitrary initialization data to the capsule instance. Below is an example where a capsule MyCap
has a reference variable m_c
. To initialize this variable a capsule constructor is used.
capsule MyCap { \n [[rt::decl]]\n `\n public:\n MyCap(RTController*, RTActorRef*, MyClass&);\n private:\n MyClass& m_c;\n `\n [[rt::impl]]\n `\n MyCap::MyCap(RTController* rtg_rts, RTActorRef* rtg_ref, MyClass& c) \n :RTActor(rtg_rts, rtg_ref), m_c(c) { }\n `\n};\n
If you don't define any constructor for your capsule, a default capsule constructor will be generated automatically. It only has the two mandatory constructor parameters, and passes them to the TargetRTS which will create the capsule instance. But if your capsule has at least one user-defined constructor, a default capsule constructor will not be generated, and you then need to make sure to provide arguments that match the constructor parameters, when an instance of the capsule should be created. For this you need to use a capsule factory. You can either specify such a capsule factory statically on a part that is typed by the capsule (see Part with Capsule Factory), or you can provide a capsule factory dynamically when calling incarnateCustom()
on a Frame port to incarnate an optional capsule part. Here is C++ code for doing the latter (assuming the optional part is called thePart
):
RTActorId id = frame.incarnateCustom(thePart,\n RTActorFactory([this](RTController * c, RTActorRef * a, int index) {\n return new MyCap(c, a, getMyClass()); // Use capsule constructor\n }));\nif (!id.isValid()) {\n // Failed to incarnate thePart\n}\n
Note the following:
RTActor
constructor in its initializer.Example
You can find a sample application that uses a capsule constructor here.
Read more about capsule factories here.
"},{"location":"art-lang/#capsule-destructor","title":"Capsule Destructor","text":"The destructor of a capsule frees the memory used for representing its states, ports etc. You can provide your own destructor if you want to perform additional clean-up activites when a capsule instance is destroyed (for example, freeing resources allocated in the capsule constructor).
capsule CapsuleWithDestructor {\n [[rt::decl]]\n ` \n public:\n virtual ~CapsuleWithDestructor();\n `\n [[rt::impl]]\n ` \n CapsuleWithDestructor::~CapsuleWithDestructor() {\n // Clean-up code here\n }\n ` \n};\n
Example
You can find a sample application with a capsule that implements its own destructor here.
Clean-up code can also be placed in a function _predestroy()
which overrides RTActor::_predestroy()
. This function gets called just before the capsule instance is destroyed and may often be a better place where to write clean-up code. If you are using a custom capsule factory which doesn't destroy capsule instances by means of the standard delete
operator, then the capsule destructor will not be called, and you can instead use _predestroy()
which always is called when a capsule instance is about to be destroyed.
Here is an example of how to use _predestroy()
:
capsule C { \n [[rt::decl]]\n `\n public: \n virtual void _predestroy() override { \n // Clean-up and free allocated resources here\n SUPER::_predestroy();\n } \n `\n // ...\n};\n
It's important to remember to invoke the inherited function by calling SUPER::_predestroy()
.
Example
You can find a sample application with a capsule that overrides _predestroy()
here.
A protocol defines events that may be sent in to a port (so called in-events) and events that may be sent out from the same port (so called out-events). By grouping events into protocols, and then typing ports with such protocols, we can precisely define which events the capsule may send and receive through that port.
By convention names of protocols start with uppercase, while names of events start with lowercase and use camelCase if the name consists of multiple words.
A protocol event may have a parameter, which enables it to carry data. You declare a parameter for an event by specifying the C++ type of the data to be carried by the event.
Note
An event can have at most one parameter. If you need to send multiple data objects with an event you can declare an event parameter of struct or class type.
The following code snippets can be used for a protocol:
Code snippet C++ mapping Example of use rt::header_preface Inserted at the top of the protocol class header file Adding #includes of header files defining types used as event parameter types rt::header_ending Inserted at the end (or near the end) of the protocol class header file Undefining a local macro that was defined in rt::header_prefaceHere is an example of a protocol that defines some in-events and some out-events:
protocol MachineEvents {\n in start();\n in startDeferred(`unsigned long` /* milliseconds */);\n\n out success();\n out error(`std::string` /* error message */);\n\n in relayEvent(); out relayEvent();\n};\n
The event relayEvent
above is both an in-event and an out-event. Such symmetric events are useful in protocols typing ports that may receive and send the same events (for example a port that just forwards received events to another port). By convention a symmetric event is declared on a single line.
At run-time we often talk about a message rather than an event. A message is an instance of an event, similar to how a capsule instance is an instance of a capsule. In other words, a message is a run-time concept while an event is a design-time concept.
"},{"location":"art-lang/#port","title":"Port","text":"A port defines a named point of communication for a capsule. A port is typed by a protocol which defines the events that may be sent in to (in-events) and out from (out-events) the port. A port may be conjugated in order to swap the meaning of in-events and out-events. That is, a capsule may send out-events on its non-conjugated ports, but in-events on its conjugated ports. A port becomes conjugated if you add a tilde (~) after its name.
Ports are often named to describe the role or purpose of the communication that takes place on them. By convention names of ports start with lowercase and use camelCase if the name consists of multiple words.
Here is an example of a capsule with a few ports. Note that Code RealTime provides several predefined protocols that can be used right away, for example Timing
. Also note that you can declare multiple ports on a single line if the ports are of the same kind (p1
and p2
below are both service ports).
capsule Machine {\n service port control : MachineEvents;\n behavior port timer : Timing; // predefined Timing protocol\n service behavior port control2 : CtrlEvents;\n service port p1~ : MoreEvents, p2~ : OtherEvents; \n\n // ...\n};\n
Service ports constitute the externally visible communication interface for a capsule, and together they define which events can be sent to the capsule, and which events the capsule can send out for other capsules to receive. In a structure diagram the service ports are shown on the border of a capsule or part symbol.
A behavior port is logically connected to the behavior (i.e. state machine) of a capsule. This means that an event that a capsule receives on a behavior port will be handled by the state machine of that capsule. A non-behavior port, however, will simply route an event to another port to which it is connected. Every event that is sent will ultimately reach a behavior port (provided ports are properly connected), and the state machine of the capsule owning that behavior port will handle the event. In a structure diagram, behavior ports are connected to a small ellipse which represents the capsule state machine.
Note that ports can also be shown in a class diagram.
When a capsule wants to send an event to another capsule it calls a function on the port. There is one such function for each out-event (or in-event if the port is conjugated). These functions return an object on which a send()
function can be called. Note that the sending capsule doesn't need to know which capsule that will receive and handle the sent event.
Here is C++ code for sending events on ports with and without data:
pongPort.pong().send(); // Send event \"pong\" without data on the \"pongPort\"\npingPort.ping(5).send(); // Send event \"ping\" with data (an integer) on the \"pingPort\"\n
Example
You can find a sample application that sends events on ports with and without data here.
Note that send()
is not the only function you can call. For example, you can call invoke()
if you want to wait until the receiver has received and replied to the event. This is useful for implementing synchronous communication between capsules.
At run-time an instance of a port can be connected to a port instance on another capsule. Such connections is what make a sent event be routed from the port on which it is sent, through a number of non-behavior ports, until it finally reaches a behavior port. By default a port has single multiplicity (1) meaning that at most one such connection can be established. However, you can specify a non-single multiplicity for a port to allow for more connections to be created at run-time.
In the example below a Server
capsule has a port with multiplicity 100. At run-time an instance of that Server
capsule can be connected to 100 different client ports, each of which can send events to the server.
capsule Server {\n service port clients : ComEvents[100];\n\n // ...\n};\n
In a structure diagram a port is shown as \"stacked\" if it has non-single multiplicity.
You can also use a C++ expression to specify the port multiplicity. This can for example be useful if the multiplicity is defined in C++ as a macro or a constexpr. For example:
capsule Server {\n service port clients : ComEvents[`NBR_CLIENTS`];\n\n // ...\n};\n
"},{"location":"art-lang/#notification-port","title":"Notification Port","text":"Every protocol contains two implicit events rtBound
and rtUnbound
. A port can choose to receive those events whenever a connection for the port is established (rtBound) or dropped (rtUnbound) at run-time. Declare a port as a notification port to receive these events.
capsule Server {\n service notify port clients : ComEvents[100];\n\n // ...\n};\n
Port notifications are useful in dynamic systems when capsules need to wait until other capsules are ready, before they can start to communicate with those capsules. For example, a client may need to wait until a server is ready before it sends a request to that server. In the same way it's often useful to get notified when a connection is dropped, since that means communication on that port should no longer take place.
"},{"location":"art-lang/#unwired-port","title":"Unwired Port","text":"Ports are by default wired, meaning that they should be connected with connectors to specify statically how events will be routed. Having a static connector structure defined has the benefit that it becomes possible to look at a capsule's structure diagram to see how events received by the capsule will be routed at run-time. However, in some dynamic systems it's not possible to describe this statically. Ports may be connected and disconnected dynamically and the run-time connections between port instances may hence vary over time. If you need this flexibility you can declare ports as unwired.
Here is an example of an application where a client capsule can connect to different kinds of server capsules. Sometimes it may be connected to server1
and sometimes to server2
. It is therefore not possible to describe the connections of Top
statically using connectors, and we can instead declare the ports as unwired.
capsule Top {\n part client : Client;\n part server1 : Server;\n part server2 : Server;\n\n // ...\n};\n\ncapsule Client {\n service behavior unwired port p : Protocol;\n\n // ...\n};\n\ncapsule Server {\n service behavior unwired port p~ : Protocol;\n\n // ...\n};\n
Note
Only use unwired ports when required. It's strongly recommended to use wired ports whenever possible to enable the visualization of the connector structure in a structure diagram. When unwired ports are required you should write a comment that describes how they will be connected at run-time, since this often cannot easily be concluded by looking at the C++ code of the capsule.
An unwired port is always a behavior port. In a structure diagram an unwired port is drawn with a hollow ellipse, while a wired behavior port is drawn with a filled ellipse. In the structure diagram below port q
is wired while port p
is unwired.
An unwired port is either a service access point (SAP) or a service provision point (SPP) depending on the role it plays in a dynamic connection with another unwired port. The capsule that owns the SAP port uses it to subscribe to a service that is published by another capsule by means of an SPP port. The capsule with the SAP port is often called \"client\" or \"subscriber\" while the capsule with the SPP port is often called \"server\" or \"publisher\".
Unwired ports get connected by means of registering them under a service name that should be unique in the application. Registration of unwired ports can either happen automatically when the container capsule instance is created, or programmatically at a later point in time. It's also possible to deregister unwired ports in order to disconnect them. You can specify how an unwired port should be registered by means of the following properties:
If you choose to register an unwired port programmatically (using the TargetRTS functions registerSPP()
and registerSAP()
) you decide at registration time whether the port should be an SAP or SPP port. However, if you choose to instead let the port be registered automatically you need to declare the port as either a subscribe
(SAP) or publish
(SPP) port. Here is the same example again, but now with automatic registration of the unwired ports using the service name myService
:
capsule Client {\n subscribe behavior port sap [[rt::properties(\nregistration_name = \"myService\")\n]] : Protocol;\n\n // ...\n};\n\ncapsule Server {\n publish behavior port spp~ [[rt::properties(\nregistration_name = \"myService\")\n]] : Protocol;\n\n // ...\n};\n
Note that the keyword unwired
can be implicit when you declare a port as either a subscribe
or publish
port.
Example
You can find a sample application that uses an unwired port here.
"},{"location":"art-lang/#connector","title":"Connector","text":"Connectors describe how events are routed within a capsule by connecting ports in its composite structure. They make it possible to see in a structure diagram which parts of a capsule that can communicate with each other. Each connector connects exactly two ports with each other. A connected port may either be a port of the capsule itself, or a port of a capsule that types one of its capsule parts. A few constraints decide if it's possible to connect two ports:
1) The ports must be wired. Unwired ports cannot be connected.
2) The ports must be typed by the same protocol.
3) The ports' conjugations must match. If the ports are at the same level in the capsule's structure (e.g. both ports belong to capsules typing capsule parts owned by the same capsule), then the connected ports must have the opposite conjugation. This is because events that are sent out from one of the ports must be able to be received by the other port and vice versa. However, if the ports are at different levels in the capsule's structure (e.g. one of them belongs to a capsule typing a capsule part owned by the capsule and the other belongs to the capsule itself), then the ports must have the same conjugation. This is because in this case events are simply delegated from one capsule to another.
4) If a connector is connected to a port and a part (where the port is defined on the capsule that types the part), then the port must be a service port. Only service ports are visible from the outside of a capsule.
The example below shows the structure diagram of a capsule Top
where we can see two connectors.
capsule Top { \n part ping : Pinger, pong : Ponger; \n connect ping.p1 with pong.p2; \n // ...\n};\n\ncapsule Internal {\n service behavior port i~ : PROTO; \n // ...\n};\n\ncapsule Pinger {\n service behavior port p1 : PROTO; \n // ...\n};\n\ncapsule Ponger { \n service behavior port p2~ : PROTO; \n part inner : Internal;\n connect p2 with inner.i;\n // ...\n};\n
The connector between p1
and p2
goes between two ports on the same level which is why these ports must have opposite conjugation. The connector between p2
and i
goes between two ports at different levels which is why these ports must have the same conjugation. The non-behavior port p2
is a so called relay port (it just relays all events it receives to another port) and the connector between p2
and i
is sometimes called a delegation connector to describe the fact that capsule Ponger
uses it for delegating some of its responsiblities to the capsule Internal
. Note that relay ports can be optimized away so they don't exist at run-time (i.e. at run-time port p1
can be directly connected to i
).
A connector doesn't have a direction, so it doesn't matter in which order it connects the two ports. That is, connecting X with Y is equivalent to connecting Y with X.
"},{"location":"art-lang/#local-binding","title":"Local Binding","text":"A connector can connect two behavior ports on the same capsule (with opposite conjugations). At run-time this will lead to a local binding between these ports. This enables the capsule to send events to itself which sometimes can be useful. Here is an example:
capsule Top { \n behavior port pOut : TheProtocol;\n behavior port pIn~ : TheProtocol;\n connect pOut with pIn; \n // ...\n};\n
One reason for a capsule to send events to itself could be to split a big and long-running task into smaller tasks. By sending an event to itself after completion of each small task, the capsule can remain responsive to other events that may arrive in the meantime. When it receives the event it sent it can proceed with the next part of the big task.
Example
You can find a sample application that uses a local binding here.
"},{"location":"art-lang/#part","title":"Part","text":"A capsule can be decomposed by means of parts (also called \"capsule parts\" to emphasize that they are parts of a capsule). A part is a container that at run-time may hold one or many capsule instances. The part has a multiplicity that specifies the maximum number of capsule instances it can contain at run-time, and it has a type which is another capsule. All capsule instances must either be of that specific capsule type, or of a capsule type that inherits from it.
It's common to name parts according to the capsule that types them. For example, a part typed by a capsule Controller
may be called controller
, ctrl
or perhaps theController
. By convention part names start with lowercase and use camelCase if the name consists of multiple words.
There are three kinds of parts which determine how and when they will be populated with capsule instances.
1) Fixed part
In a fixed part capsule instances are created automatically when the container capsule is created, and destroyed when the container is destroyed. Fixed parts by default have multiplicity 1. Such a part will always contain one and only one instance of the capsule that types the part.
Example
You can find a sample application that has a fixed part with a multiplicity here.
2) Optional part
In an optional part capsule instances don't have a strong lifetime relationship with the container capsule as is the case for fixed parts. The capsule instances can be created programmatically using a Frame port at some point after the container capsule has been created, and they can be destroyed before the container capsule is destroyed. However, at the latest they will be automatically destroyed when the container is destroyed. Optional parts by default have multiplicity 0..1. This means that they may either contain zero or one capsule instance at any point in time. The presence of zero in the multiplicity is what makes the part optional. Here is C++ code for creating a capsule instance in an optional part (also known as incarnating the part) and then immediately destroying it:
RTActorId id = frame.incarnate(thePart);\nif (!id.isValid()) {\n // Failed to incarnate thePart\n}\nframe.destroy(id);\n
It's important to check that incarnation was successful since there are many reasons why it can fail (e.g. too low multiplicity to fit the created capsule instance, not enough memory etc).
If the instantiated capsule has a constructor you need to use a capsule factory for providing the constructor arguments (either provided when doing the incarnation as shown here or specified on the part as described here).
Example
You can find a sample application that creates and destroys capsule instances in optional parts here.
3) Plugin part
A plugin part is similar to an optional part in that it is populated by capsule instances programmatically. However, the capsule instances are not created in the plugin part but instead imported into the plugin part from another part. Typically such a capsule instance is first created into an optional part, and then at some later point in time imported into a plugin part. Later it can be deported (i.e. removed) from the plugin part and perhaps imported into another plugin part. This makes it possible to create very dynamic composite structures where the same capsule instance can play different roles in different parts over time. Moving a capsule instance by deporting it from one plugin part and then importing it in another plugin part is more efficient than destroying the capsule instance in one optional part and then creating another capsule instance in another optional part. Plugin parts are typically used together with unwired ports. In general it's possible to import a capsule instance into more than one plugin part at the same time, but it can only be imported if its ports are not already bound in its current location. Plugin parts by default have multiplicity 0..1.
In the example below the capsule C
contains a few parts of different kinds and multiplicities. Note that you may declare multiple parts on the same line if they are of the same kind (both c
and d
below are optional parts).
capsule C {\n part a : D;\n fixed part b : D[4];\n optional part c : D, d : D[0..5];\n plugin part e : D;\n part f : D[`COUNT`];\n\n // ...\n};\n
Part a
is fixed with multiplicity 1 since neither kind nor multiplicity is specified for it. Part b
is also fixed (using the fixed
keyword for more clarity) and with multiplicity 4. When an instance of capsule C
is created 5 instances of capsule D
will be automatically created. One of these instances will be inserted into part a
and the others into part b
. These instances will remain there until the C
capsule instance is destroyed.
Part c
is optional with multiplicity 0..1. At run-time it can contain at most one instance of capsule D
. Part d
is also optional but can contain up to 5 instances of D
as specified by its multiplicity 0..5.
Part e
is plugin with the default multiplicity 0..1. At run-time at most one instance of capsule D
can be imported into it. That instance must already have been created in another part, for example part c
.
Part f
uses a C++ expression for specifying the multiplicity. This can for example be useful if the multiplicity is defined in C++ as a macro or a constexpr.
Parts can be shown in a structure diagram:
Parts are shown as \"stacked\" if they have non-single multiplicity (multiplicities specified with a C++ expression are assumed to be non-single). Optional parts are shown with a \"diagonal\" background pattern, while plugin parts are shown with a \"double diagonal\" background pattern.
Parts can also be shown in a class diagram:
In the above diagram the filled diamonds show that there is a strong life-time relationship between a C
instance and the instances of D
that are located in the fixed and optional parts a
, b
, c
, d
and f
, while this is not the case for the instance located in the plugin part e
as shown by the hollow diamond.
If the capsule that types a part has a capsule constructor with custom constructor parameters, you can define a capsule factory for the part. Such a capsule factory consists of one or both of the below code snippets that define how an instance of that capsule should be created and destroyed.
rt::create
Defines how to create an instance of the capsule. For example, which constructor arguments to pass, which thread to use for running the created capsule instance, at which index to insert the capsule instance into the part (in case it has multiplicity > 1) etc.rt::destroy
Defines how to destroy an instance of the capsule. By default it's destroyed using the delete
operator.Here is an example where a part defines a capsule factory that specifies a create function. The create function gets the mandatory constructor parameters rtg_rts
and rtg_ref
as arguments, as well as an index
argument that specifies the index where the created capsule instance will be inserted.
part engine : Engine [[rt::create]]\n`\n return new Engine(rtg_rts, rtg_ref, true /* custom constructor arg */);\n`;\n
Note that you may want to create a capsule factory for a part also for other reasons than passing custom constructor parameters. For example, you may want to change the default thread (RTController*
) that should execute the created capsule instance, or you may want to instantiate an inherited capsule rather than the capsule that types the part.
Example
You can find a sample application here where a fixed part uses an rt::create
code snippet for invoking a custom capsule constructor.
You can use a global capsule factory by means of setting the capsuleFactory
TC property. Such a capsule factory will be used when creating or destroying any capsule instance in your application, except those that are located in a part for which you have specified a local capsule factory.
Read more about capsule factories here.
"},{"location":"art-lang/#state-machine","title":"State Machine","text":"State machines are used for specifying the behavior of capsules. It is also possible to provide a state machine for a passive class; see Class with State Machine for more information about that. In this chapter we focus on state machines in capsules.
A state machine consists of states and transitions. During its lifetime a capsule instance transitions between the various states of its state machine, as a consequence of receiving events on its behavior ports. When transitioning between two states one or several code snippets may execute. Such code may for example send events to other capsule instances, something that may cause transitions to execute in their state machines.
A state machine may also have pseudo states, which just like states may be connected with transitions, but that unlike states are not places where the state machine should stay for some time. For example, most pseudo states like junctions and entry/exit points merely act as connection points that make it possible to execute more than one transition when transitioning between two states. The notable exception is the choice in which actually the state machine may get stuck for ever, but that would be an error situation that should not happen in a correctly designed state machine.
"},{"location":"art-lang/#state","title":"State","text":"The states of a state machine are the places where the state machine may stay for some time while waiting for a message to arrive that potentially can cause the state machine to transition to another state. States should have names that describe what is happening while the state machine stays there, or what has happened for the state machine to arrive there. For example, \"WaitForInit\", \"Processing\" or \"Terminated\". By convention state names start with uppercase.
You can declare multiple states on the same line using a comma-separated list of state names. It can be good to write a comment in front of the state name, if you want to elaborate more on its meaning than what is possible in the name itself. Here is an example of a state machine with some states:
capsule TrafficLight { \n statemachine {\n state WaitUntilServerReady, CycleLight;\n state /* pedestrians are crossing the street */ PedestriansCrossing;\n initial -> WaitUntilServerReady;\n WaitUntilServerReady -> CycleLight;\n CycleLight -> PedestriansCrossing;\n };\n};\n
Here is another example where the state machine is shown in a state diagram.
A state comment is not visible in a state diagram, but show up in a tooltip when putting the cursor on a reference to the state. They can thereby make it easier to understand a state machine.
States may be nested to create a hierarchical state machine.
"},{"location":"art-lang/#entry-and-exit-action","title":"Entry and Exit Action","text":"A state may have an entry and/or exit action which is a code snippet that runs whenever the state is entered and/or exited.
state Walking {\n entry\n `\n server.walk().send();\n `;\n exit\n `\n server.stop().send();\n `;\n};\n
Example
You can find a sample application where a state has an entry and exit action here.
"},{"location":"art-lang/#transition","title":"Transition","text":"A transition connects a source state (or pseudo state) to a target state (or pseudo state). When a capsule instance handles a message that was received on one of its behavior ports, one or several transitions may execute.
It's not required to give a name to a transition, but it's possible and often makes the state machine easier to understand. At least triggered transitions (i.e. transitions where the source is a state) should have a name. A transition name can be choosen to describe what has happened when the transition executes, for example \"requestReceived\", \"timeout\" etc. By convention transition names start with lowercase and use camelCase if the name consists of multiple words.
A triggered transition has one or several triggers which define when the transition can be triggered. Each trigger specifies a port and an event. The trigger can only trigger its transition if the received message is an instance of the specified event, and was received on the specified port. In addition it's possible to provide guard conditions that must be fulfilled for the trigger to trigger its transition. Such a guard condition can be specified for the transition, but also for each individual trigger.
Here is an example of a capsule state machine with two triggered transitions requestReceived
and timeout
. It also contains an initial transition that has no name.
capsule MyCap {\n statemachine {\n state Waiting, Processing;\n initial -> Waiting; \n requestReceived: Waiting -> Processing on com1.request, com2.request when\n `\n return canHandleNow();\n `\n `\n log.log(\"Handling request\");\n log.commit();\n handle(msg);\n `;\n timeout: Waiting -> Waiting on timer.timeout[`zCount < 10`];\n };\n};\n
Triggers are specified as PORT.EVENT
after the keyword on
. You may specify multiple triggers separated by comma (,
).
It's only valid to specify triggers for transitions that originate from a state. Transitions that originate from a pseudo-state (e.g. a choice or junction) cannot have triggers, i.e. they must be non-triggered transitions. Note, however, that transitions originating from entry and exit points without incoming transitions represent the container state and hence need a trigger.
"},{"location":"art-lang/#transition-code","title":"Transition Code","text":"A transition can have code snippets:
when
keyword), but also a guard for each individual trigger (specified in square brackets ([]
) after the trigger). The guard for an individual trigger is only evaluated when the received message matches the port and event specified by that trigger, while the guard for the transition itself is always evaluated. A transition can only execute if both these guard conditions are fulfilled.A guard code snippet can either be written as a C++ statement that returns the boolean guard condition (as in the guard for transition requestReceived
in the above example), or it can be written as a boolean expression (as in the trigger guard for the timeout
trigger in the above example). If the guard condition is simple, as is often the case, using a boolean expression is recommended. However, if needed you can use any number of C++ statements in a guard condition where the last statement should return a boolean expression. For example, you can declare local variables to store partial results when computing the boolean expression.
Note
Guard code snippets should execute fast and have no side-effects. They are called frequently to decide which transition to execute when a message has arrived.
Both effects and guards are translated to C++ functions in the generated code. These functions have two arguments that can be used in the effect and guard code snippets:
rtdata
: A const pointer to the event data carried by the received messagertport
: A pointer to the RTProtocol that represents the port on which the message was receivedIn some cases rtdata
will be an untyped pointer (const void*
):
Timing::timeout
or External::event
eventsIn these cases you need to cast rtdata
to the correct type. Such casting should normally be avoided, and for cases 1, 2 and 3 above there are ways to do so. For example, instead of one transition with multiple triggers, you can have several transition with one trigger each. And instead of accessing rtdata
in a non-triggered transition, it's better to access it in the triggered transition that precedes it.
You can set the const_rtdata
property to false on a transition, to make rtdata
a non-const pointer (void*
). One reason could be that you want to move the data into a capsule variable, so you can access it later. Moving data can be more efficient than copying it.
[[rt::properties(const_rtdata=false)]] First -> Second on myport.myevent \n`\n pC = std::move(*rtdata);\n`;\n
Example
You can find a sample application that has transitions with the property const_rtdata
unset here.
Every state machine needs exactly one initial transition. When the state machine starts to run, the first thing that happens is that the initial transition executes and takes the state machine to its first state. Therefore, an initial transition is a non-triggered transition and also cannot have a guard condition. But it can of course have an effect code snippet.
The source of the initial transition is the initial pseudo state which is declared using the initial
keyword. Just like for any transition it's optional to give a name to the initial transition (in fact it's often left unnamed).
For capsule instances that are programmatically created (i.e. located in optional capsule parts) you can provide initialization data at the time of creation in the call to incarnate()
on a Frame port. The initialization data can be accessed in the effect code of the initial transition. Here is an example:
initial -> WaitForServerInit\n`\n RTpchar str = *((RTpchar*) rtdata);\n`;\n
Note
Any type of data object can be passed as initialization data which means that rtdata
is an untyped pointer that has to be casted to the expected type. A more type-safe way of passing initialization data is to define a constructor for a capsule. A capsule constructor can take any number of arguments, while with rtdata
only one data object can be passed (even if you of course can group several data objects into a struct or class to circumvent this limitation). With capsule constructors you can pass initialization data also for capsule instances that are located in fixed parts.
An internal transition doesn't change the active state and therefore doesn't have a target state. An internal transition is always a triggered transition. You define an internal transition inside the state to which it belongs. Here is an example:
state Done {\n unexpected: on myPort.*\n `\n std::cout << \"Unexpected event received! << std::endl; \n `;\n};\n
Note the usage of an asterisk (*
) to specify that any event received on myPort
will trigger the internal transition when the state machine is in the Done
state. Such \"receive-any\" events can of course be used for a trigger of any transition, but can in particular be useful for internal transitions that should handle all messages received on a port that are not handled by other triggered transitions leaving substates of the state. If another event is added to the port's protocol in the future, such a trigger will handle the new event too without a need for being updated.
Example
You can find a sample application that has an internal transition with a \"receive-any\" event trigger here.
Internal transitions are examples of so called self-transitions. To learn about other types of self-transitions see this chapter.
"},{"location":"art-lang/#choice-and-junction","title":"Choice and Junction","text":"Choices and junctions are pseudo states that make it possible to split transition flows in a state machine. That is, one incoming transition may be split into multiple outgoing transitions. Which of the outgoing transitions that will execute is decided by evaluating their guard conditions.
For a junction the guard conditions are evaluated already before leaving the currently active state. Only if there exists a path of transitions where all guards are fulfilled, will the active state be exited and the transitions can execute. Otherwise the state machine stays in its current state and attempts to find another path of transitions to execute. For a choice the guard conditions are evaluated after leaving the current state, when reaching the choice itself. The outgoing transition which has a fulfilled guard will execute next.
Note
It's important that there always is an outgoing transition for a choice with a fulfilled guard condition. Otherwise the state machine will get stuck in the choice without any chance of getting out of it.
The same is true if a junction is used in the initial transition. If such a junction doesn't have an outgoing transition with a fulfilled guard condition then the state machine will stay in the initial state for ever.
Choices and junctions must have names, so they can be referenced as the source or target of transitions. You can choose to use a name that gives a hint about what conditions that are checked in the guards of the outgoing transitions. For example, isEnabled
for a choice that checks a boolean condition and checkValue
when the condition has some other type. If you follow this approach you can then name the outgoing transitions accordingly. For example true
and false
for a choice that checks a boolean condition. By convention choice and junction names start with lowercase and use camelCase if they consist of multiple words. Sometimes it may be difficult to come up with a good name and in that case you can choose something short and \"technical\" like j1
, check1
etc.
Below is an example of a state machine containing a choice and a junction.
statemachine {\n state First, Second, Third;\n t1: initial -> First;\n choice isEnabled;\n junction checkThreshold;\n switchTurned: First -> isEnabled; \n true: isEnabled -> Second when\n `\n return isEnabled(); \n `; \n false: isEnabled -> Second when\n `\n else \n `; \n timeout: First -> checkThreshold; \n low: checkThreshold -> Third when\n `\n return t < LIMIT1; \n `;\n medium: checkThreshold -> Third when\n `\n return t >= LIMIT1 && t < LIMIT2; \n `;\n high: checkThreshold -> Third;\n};\n
Note the use of the C++ keyword else
for defining an else-guard. An else-guard will be fulfilled when no other guard of other outgoing transitions is fulfilled. For choices it's good practise to always have exactly one transition with an else-guard to ensure that at least one guard condition will be fulfilled. Thereby we avoid the risk of the state machine getting stuck in the choice. Else-guards can also be useful for junction transitions, but there they are more optional (except when a junction is used in the initial transition; see the note above).
You can also define an else-transition for a choice or junction by simply omitting the guard condition. This is consistent with triggered transitions where the absense of a guard condition is equivalent to a guard condition that always is fulfilled. See the transition high
in the above example.
Guard conditions should be mutually exclusive so that the order in which they are evaluated doesn't matter.
Junctions can also be used for merging multiple incoming transition flows into a single outgoing transition. This can for example be useful if you want to reuse a transition path in the state machine for several triggered transitions.
statemachine {\n state S1, S2;\n junction j1;\n initial -> S1;\n t1: S1 -> j1 on port1.e1 \n `\n // handle e1\n `;\n t2: S1 -> j1 on port2.e2\n `\n // handle e2\n `;\n t3: S1 -> j1 on port3.e3\n `\n // handle e3\n `;\n common: j1 -> S2 \n `\n // common code here\n `;\n};\n
Of course, in the above simple example the same code reuse could also be obtained by putting the common code in a capsule member function which is called by each of the incoming transitions. But if the common transition is followed by more non-triggered transitions the above approach is more feasible.
When multiple triggered transitions converge into a common transition as in the example above, and the events that trigger those transitions have a data parameter, it's best to access that data in the triggered transitions and not in the common transition. This is especially true if the types of those data parameters are not the same, because in that case the rtdata
parameter of the function generated for the common transition will be untyped (void*
). You can of course still cast it to another type, but that requires that you can know which of the triggered transitions that were triggered. It's therefore better to access the data in the triggered transitions and if necessary store it in a capsule variable which you then can access in the common transition if needed.
Example
You can find a sample application that demonstrates usage of a choice and junction here.
"},{"location":"art-lang/#hierarchical-state-machine","title":"Hierarchical State Machine","text":"A state machine is hierarchical if it contains at least one composite state, i.e. a state with a nested state machine. A transition that is triggered in the enclosing state machine (i.e. the state machine that contains the composite state) should enter a composite state by specifying an entry point of the composite state as the target. In the nested state machine another transition can connect that entry point to a state in the nested state machine. A transition in the nested state machine may specify an exit point of the composite state as the target. In the enclosing state machine another transition can connect that exit point to a state in the enclosing state machine.
Entry and exit points are pseudo states that need to be named. The names can be chosen to give a hint about when the composite state is entered or exited through them, for example systemStarted
or errorDetected
. If you want you can prefix the names with ep
or ex
. It's also common to use short and \"technical\" names like ep1
or ex1
if a more descriptive name doesn't make sense. By convention entry and exit point names start with lowercase and use camelCase if they consist of multiple words.
It's also possible to directly enter a composite state without using an entry point. In this case the behavior will depend on whether the composite state is entered for the first time or not. If it is for the first time, the initial transition of the nested state machine will execute after the transition that targets the composite state has executed. Otherwise the composite state will instead be entered using deep history, i.e. by activating the state in the nested state machine that was most recently active (and recursively if that state again is a composite state).
Note
It's recommended to always enter a composite state using an entry point as the behavior then doesn't depend on if the state was previously entered or not.
Below is an example of a hierarchical state machine with a composite state CompositeState
that contains a nested state machine. Note that you can declare multiple entry or exit points on the same line.
statemachine { \n initial -> CompositeState.ep1;\n state CompositeState {\n state Nested;\n entrypoint ep1, ep2;\n exitpoint ex1;\n initial -> Nested;\n ep1 -> Nested;\n Nested -> ex1;\n ep2 -> history*;\n };\n state Other;\n CompositeState.ex1 -> Other;\n Other -> CompositeState.ep2;\n};\n
Note that a dot (.
) is used as scope resolution operator, to make it possible to reference an entry or exit point from the enclosing state machine. Inside the nested state machine the entry and exit points are directly accessible without use of the scope resolution operator (using it there would be an error).
It is possible to only connect an entry point on the \"outside\". Entering the state via such an entry point will behave in the same way as entering the composite state without using an entry point (see above). It's therefore not recommended. In the same way it's possible to exit a composite state using an exit point that only is connected on the \"inside\". In this case the composite state is not exited and instead the previously active substate again becomes active (recursively, just like for deep history). This is also not recommended, unless the transition is a local transition.
Example
You can find a sample application that contains a composite state with an entry and exit point here.
Just like a junction, an entry or exit point can have multiple outgoing transitions. Guards on those transitions decide which of them to execute, and are evaluated before leaving the current state. Therefore, the same recommendations as for guard conditions of junctions apply for entry and exit points.
"},{"location":"art-lang/#entry-and-exit-point-without-incoming-transition","title":"Entry and Exit Point without Incoming Transition","text":"You can choose to not connect an entry or exit point with an incoming transition. In this case the entry or exit point represents the owning state, and a transition that originates from such an entry or exit point behaves the same as if it would originate from the state itself. Contrary to other transitions that originate from an entry or exit point, such a transition is therefore triggered and should have at least one trigger.
An entry point without incoming transition is useful for handling events in a composite state that should be commonly handled regardless of which substate that is active. The composite state remains active when handling the event, and it will not be exited and entered. The target of such a transition may either be a nested state, the deep history pseudo state, or an exit point (see local transition).
In a similar way an exit point without incoming transition can be used for exiting the composite state in a common way regardless of which substate that is active. The behavior is the same as if the transition would originate from the composite state itself, but by using an exit point you can give a descriptive name to it that tells something about why the state is exited. This can be in particular useful if there are multiple such \"exit transitions\" from the composite state.
In the example below the transition tx
originates from an entry point ep2
without incoming transition. When it triggers the active state will change from Composite::Nested
to Composite::Nested2
without leaving the Composite
state. The transition done
originates from an exit point ex2
without incoming transition. It will exit the active substate of Composite
and then exit Composite
itself, before activating the Done
state.
statemachine {\n state Composite {\n entrypoint ep1, ep2;\n exitpoint ex2; \n state Nested, Nested2;\n ep1 -> Nested;\n tx: ep2 -> Nested2 on port1.timeout;\n };\n initial -> Composite.ep1;\n state Done;\n done: Composite.ex2 -> Done on port1.timeout;\n};\n
Example
You can find a sample application that uses an entry and exit point without incoming transitions here.
"},{"location":"art-lang/#deep-history","title":"Deep History","text":"Every nested state machine has an implicit pseudo state with the name history*
(in state diagrams it's shown as H*
to save space). It can be used as a target for any transition inside the nested state machine. When it is reached, the state machine will restore the previously active substate. If that state again is a composite state, its previously active substate will also be restored. This goes on recursively for all nested state machines (which is why it's called a deep history).
In the example above we can see that the transition from ep2
targets the deep history pseudo state. This means that if the Nested
substate is active and then the transition to ex1
gets triggered, the state Other
becomes active. If then the transition to ep2
gets triggered the CompositeState
will be entered using deep history so that the Nested
substate will again become active.
Example
You can find a sample application that uses the deep history pseudo state here.
"},{"location":"art-lang/#local-transition","title":"Local Transition","text":"A transition in a nested state machine that connects an entry point and exit point on the same state, and these entry/exit points only are connected on the \"inside\", is a local transition. A local transition is a self-transition that behaves something in between an internal transition and a regular (a.k.a. external) self-transition. An internal transition defined on a composite state handles a message without exiting neither that composite state, nor any of its substates. However, a local transition will exit the substates, run the effect code, and then enter the substates again. But the composite state itself will not be exited and entered. An external self-transition on the other hand will exit both the composite state and all active substates recursively, run the effect code, and then enter these states again.
Both for local and external self-transitions exiting of states happens bottom-up which means that the deepest nested substate will first be exited, then its parent state, and so on. Entering happens in the opposite order, i.e. in a top-down fashion.
Let's look at an example to understand the difference between these three kinds of self-transitions:
statemachine { \n initial -> SelfTransitionExample;\n state SelfTransitionExample {\n state Nested1 {\n state Nested2;\n };\n internal: on port1.e1\n `\n // Internal transition\n `;\n entrypoint e1;\n exitpoint e2;\n local: e1 -> e2 \n `\n // Local transition\n `;\n };\n external: SelfTransitionExample -> SelfTransitionExample on port2.e2\n `\n // External transition\n `;\n};\n
Assume the currently active state configuration is {SelfTransitionExample
, Nested1
, Nested2
} when one of the self-transitions get triggered:
internal
)No state is exited and the active state configuration remains unchanged.
local
)1) Nested2
is exited.
2) Nested1
is exited.
3) local
executes.
4) Nested1
is entered.
5) Nested2
is entered.
external
)1) Nested2
is exited.
2) Nested1
is exited.
3) SelfTransitionExample
is exited.
4) external
executes.
5) SelfTransitionExample
is entered.
6) Nested1
is entered.
7) Nested2
is entered.
Example
You can find a sample application that has a local transition here.
"},{"location":"art-lang/#class-with-state-machine","title":"Class with State Machine","text":"Art allows you to create passive classes with state machines. This can be an alternative to using a capsule in case you only need a passive stateful data object, and don't need the ability to send events to it, or to let it execute in its own context. A class with a state machine is more lightweight than a capsule at runtime.
Transitions in a class state machine are triggered by calling trigger operations on the class. A trigger operation is similar to a regular member function in C++, but does not have a code behavior of its own. Instead, when you call a trigger operation on an object of a class with a state machine it may trigger a transition in the class' state machine. That transition may have an effect code snippet that will execute.
A trigger operation can have parameters which allows you to pass data when calling them. Those parameters can be accessed in the transition that is triggered by it.
Below is an example of a class with a state machine with two trigger operations initialize
and finalize
. Note that you can define multiple trigger operations on the same line.
class DataObject {\n /* Trigger Operations */\n trigger initialize(`int` data), finalize();\n /* State Machine */\n statemachine {\n state Initial, Initialized, Finalized;\n initial -> Initial;\n init: Initial -> Initialized on initialize(`int`)\n `\n // Initialized\n int i = data;\n `;\n Initialized -> Finalized on finalize()\n `\n // Finalized\n `;\n };\n};\n
Just like for C++ member functions, trigger operations support overloading. That is, you can have many trigger operations with the same name as long as their full signatures are unique. The signature of a trigger operation consists of its name and the types of all its parameters. When you reference a trigger operation with parameters as a transition trigger, you need to include the types of the parameters (see the trigger for the init
transition above).
The same transition can be triggered by multiple trigger operations (just like a transition in a capsule state machine can be triggered by multiple events). However, in that case those trigger operations should agree on the names and types of their parameters so that the transition effect code can access them in a way that works regardless of which of the trigger operations that will trigger the transition.
Names of classes with state machines by convention start with uppercase, while names of trigger operations and their parameters by convention start with lowercase and use camelCase if the name consists of multiple words.
A common design pattern is to let a class-with-statemachine instance be managed by a single capsule instance. This means that the capsule instance is responsible both for creating, using and finally destroying the class-with-statemachine instance. If you follow this pattern it is thread-safe to for example call public member functions defined on the capsule from a transition in the class state machine (or, better, to call non-public member functions by letting the class be a friend of the capsule). This can for example be used as a means for the class state machine to send events through the ports of the capsule (i.e. it can call a capsule member function that sends the event). However, to avoid exposing the full capsule functionality to the class state machine it's recommended to define an interface (i.e. abstract C++ class) which the capsule can implement. This interface can contain only those member functions which the class needs to call on the capsule.
A class state machine can use the same constructs as a capsule state machine with a few exceptions:
The initial transition cannot access initialization data as can a capsule's initial transition. Instead you can define one or several constructors for the class with parameters needed for passing initialization data when the class-with-statemachine instance is created. See Constructor for more information.
The state machine can be hierarchical but the deep history pseudo state is not supported. Instead the shallow history pseudo state can be used.
Even if it's possible for a class with a state machine to inherit from another class with a state machine, this doesn't mean that the state machines will be inherited as is the case for capsule inheritance. Read more about this in Inheritance.
A class with state machine can have the same code snippets as a capsule.
Example
You can find a sample application that uses a class with a state machine here.
"},{"location":"art-lang/#constructor","title":"Constructor","text":"By default the initial transition of a class state machine executes at the time of constructing the class-with-statemachine instance. This happens because the generated default constructor will call an operation rtg_init1()
which contains the code from the initial transition. If you want to wait with \"starting\" the state machine until a later point in time you need to define your own parameterless constructor which doesn't call this function.
You can define any constructors you need on a class with a state machine. They are regular C++ constructors and allow to pass initialization data when creating a class-with-statemachine instance. Remember to call the rtg_init1()
function in all such constructors, if you want the state machine to start at the time of creating the class-with-statemachine instance.
Here is an example of a class with a state machine that has a user-defined constructor:
class PC {\n [[rt::decl]]\n `\n private:\n double m_data;\n\n public:\n PC(double data);\n ` \n\n [[rt::impl]]\n `\n PC::PC(double data) : m_data(data) {\n rtg_init1();\n }\n `\n\n statemachine {\n state First;\n initial -> First\n `\n // State machine started\n `;\n };\n};\n
"},{"location":"art-lang/#shallow-history","title":"Shallow History","text":"Every nested state machine has an implicit pseudo state with the name history
(in state diagrams it's shown as H
to save space). It can be used as a target for any transition inside the nested state machine. When it is reached, the state machine will restore the previously active substate. However, if that state again is a composite state it's previously active substate will not be restored. This is in contrast to the deep history for capsule state machines, and is why for a class state machine this pseudo state is referred to as a shallow history.
Here is an example:
class MyClass {\n\n statemachine {\n state First {\n entrypoint ep1;\n ep1 -> history;\n };\n initial -> First.ep1;\n };\n};\n
"},{"location":"art-lang/#inheritance","title":"Inheritance","text":"By using inheritance you can reuse and customize generic (base) Art elements into more specific (derived) Art elements. An Art element can inherit either from one or several other Art elements, and/or it can inherit from one or several C++ classes. The derived Art element can redefine elements of the base element. The redefining element (located in the derived element) can change one or several properties of the redefined element (located in the base element). This is very similar to how inheritance works in C++, with the difference that in C++ a redefining element has more restrictions on what properties that can be changed in the redefined element. For example, a redefining member function (known as an overridden member function in C++ terminology) must keep the same signature as the redefined member function (known as a virtual base member function in C++ terminology), and can only (in fact, must) change its implementation.
In some cases Art inheritance not only allows to redefine inherited elements, but also to completely exclude them. An excluded element is not present in the derived element, so exclusion can be seen as a special form of redefinition where the whole element is removed in the derived element. In C++ it's not possible to exclude any inherited members.
"},{"location":"art-lang/#capsule-inheritance","title":"Capsule Inheritance","text":"A capsule can inherit from another capsule. Only one base capsule is allowed; multiple inheritance is not supported for capsules. In addition a capsule can inherit from any number of C++ classes (or structs).
The derived capsule is type compatible with the base capsule in the sense that if you have a capsule part typed by the base capsule, you can at runtime incarnate it with instances of the derived capsule.
Capsule inheritance has multiple dimensions. One dimension is the usual C++ inheritance between classes (remember that a capsule is an active class). In this dimension it is for example possible to redefine (a.k.a override) a virtual member function defined in the base capsule or in another base C++ class. But there is also a second dimension where the state machine of the derived capsule will implicitly inherit from the state machine of the base capsule. This makes it possible to redefine transitions and states. For example, a redefining transition in a derived capsule can change the effect code, the guard condition or the target state or pseudo state. And a redefining state in a derived capsule can change the entry or exit action, as well as any substate or subtransition in case the state is composite and has a nested state machine. It's also possible to completely exclude a state or a transition, either in the capsule's top state machine, or in a nested state machine.
Below is an example of a capsule D
that inherits from another capsule B
. In addition the capsule D
inherits from two C++ classes IDataManager
and IController
.
capsule B { \n [[rt::decl]]\n ` \n protected:\n virtual void doSmth();\n `\n [[rt::impl]]\n ` \n void B::doSmth() {\n // ...\n }\n `\n statemachine {\n state BS, BS2;\n _Initial: initial -> BS;\n };\n};\n\ncapsule D : B, `IDataManager`, `IController` { \n [[rt::decl]]\n `\n // IDataManager impl\n protected:\n void manageData() override;\n\n // IController impl \n void control() override;\n\n void doSmth() override;\n `\n\n [[rt::impl]]\n `\n // impl of manageData() and control()\n\n void D::doSmth() {\n // ...\n SUPER::doSmth(); // Call inherited function\n }\n `\n\n statemachine {\n state DS;\n state exclude BS2;\n redefine _Initial: initial -> DS;\n };\n};\n
In the example we can see that D
overrides functions from the base C++ classes that are assumed to be virtual (or pure virtual). For brevity the implementations of these functions have been omitted but would be placed in the rt::impl
code snippet. D
also overrides a virtual function doSmth()
from the base capsule B
. The implementation of that function (also placed in the rt::impl
code snippet) calls the inherited function by using a macro SUPER
. This macro expands to the name of the base capsule class. Using this macro, instead of the base capsule class name, makes it easier to copy/paste code from one capsule to another.
Example
You can find a sample application where a capsule inherits from both another capsule and from C++ classes here.
We can also see an example of a state machine redefinition. The initial transition _Initial
of B
's state machine is redefined in D
's state machine so that it targets state DS
instead of state BS
. In the state diagram of D
the state BS
and the initial pseudo state are drawn with gray color and dashed outline, to show that they are inherited. The transition _Initial
is also drawn with dashed outline, but with a different line style (\"dash-dot-dot\"), and with a green label to show that it's redefining the inherited initial transition. The state BS2
is excluded in D
's state machine. In state diagrams excluded elements are shown with a \"crossed\" background.
Note that to be able to redefine the initial transition of B
it is necessary to give it a name (so that it can be referenced as redefined from D
). This is yet another reason why it's good practise to give names to transitions, even if it's not mandated. But, of course, if you want to prevent anyone from creating a derived capsule with a state machine that redefines a certain transition, you can accomplish that by not giving a name to that transition. In effect, an unnamed transition is final, i.e. cannot be overridden or excluded.
The rule that a capsule state machine must have exactly one initial transition also applies to a derived capsule. Therefore, when you introduce inheritance between two existing capsules, you typically first get an error saying that the derived capsule has two initial transitions (one inherited, and one locally defined). You then need to decide if you want to either remove the initial transition in the derived capsule, or (like in the above example) instead redefine the initial transition.
Example
You can find sample applications where capsule state machines are inherited here:
Capsule inheritance also has a third dimension, which relates to its structure. Parts and ports defined in the base capsule are inherited by the derived capsule. Just like for states and transitions, it's possible to redefine or exclude a part or a port. A redefining port can change the type (i.e. protocol), multiplicity and the notification property of the redefined port. A redefining part can change the type, multiplicity and kind (fixed, optional or plugin) of the redefined part.
Below is an example of a capsule DPPI
that inherits from another capsule BPPI
. The port port1
and the part part1
is redefined, while the port port2
and part part2
are excluded.
capsule BPPI { \n service port port1 : PR1; \n behavior port port2 : PR1; \n part part1 : Cap1;\n part part2 : Cap1;\n statemachine {\n state State;\n initial -> State;\n };\n};\n\ncapsule DPPI : BPPI {\n service notify port redefine port1 : PR2[10];\n optional part redefine part1 : Cap2[0..20];\n part exclude part2;\n behavior port exclude port2;\n statemachine {\n state State2; \n };\n};\n
Redefined and excluded elements are also shown in class diagrams. Below is the class diagram for the capsules in the above example.
Example
You can find a sample application where parts are inherited here.
"},{"location":"art-lang/#calling-code-from-an-inherited-transition","title":"Calling Code from an Inherited Transition","text":"Guard and effect code for a transition that redefines an inherited transition can call the guard and effect code snippet from that inherited transition. For this purpose two C++ macros are available:
rtdata
and rtport
arguments.rtdata
and rtport
.That is, CALLSUPER
is equivalent to SUPERMETHOD(rtdata, rtport)
.
Example
You can find a sample application where these macros are used here.
Note that these macros are just a convenience and you can accomplish the same thing if you place the code of the transition code snippet in a virtual capsule member function, which then can be overridden in the sub capsule.
"},{"location":"art-lang/#abstract-capsule","title":"Abstract Capsule","text":"Some capsules are not intended to be instantiated, and just provide a base implementation which other capsules can reuse and specialize by means of inheritance. Such capsules should be declared as abstract. By doing so, you can leave the state machine of the abstract capsule incomplete, with only a partial implementation. Validation rules that check the correctness of capsule state machines will not report any problems for state machines of abstract capsules.
Below is an example of an abstract capsule Base
which provides a partial implementation of a state machine. The capsule C
inherits from Base
and extends the inherited state machine to make it complete.
abstract capsule Base { \n statemachine {\n state State;\n initial -> State;\n choice x; // Would normally report error ART_0006, but not for abstract capsule \n };\n};\n\ncapsule C : Base {\n statemachine {\n state A, B;\n x -> A when `expr()`;\n x -> B when `else`;\n };\n};\n
The Base
capsule state machine has a choice x
without any outgoing transitions. Normally a problem would be reported for that (ART_0006
), but because Base
is an abstract capsule, no error is reported. The capsule C
, which is non-abstract, can inherit Base
but must then define the missing outgoing transitions.
Note
A capsule that contains one or many pure virtual functions (either locally defined or inherited) is effectively also abstract in the sense that it cannot be instantiated. However, to use a partial state machine in a capsule you need to declare it with the abstract
keyword. Of course, you can still declare pure virtual functions for an abstract capsule if needed.
Example
You can find a sample application with an abstract capsule here. An example with an abstract capsule that has a pure virtual function can be found here.
"},{"location":"art-lang/#class-inheritance","title":"Class Inheritance","text":"A class with state machine can inherit from other classes with state machines, or from C++ classes (or structs). Multiple inheritance is supported.
Contrary to capsule inheritance, class inheritance does not imply inheritance between the state machines in the derived and base classes. This means it's not possible to redefine or exclude states and transitions in an inherited class state machine. Nor is it possible to redefine trigger operations. In fact, the derived class will have two state machines (its own, plus the one inherited from the base class) and these two state machines will execute independently of each other. That is, class inheritance is more a way of aggregating state machines rather than reusing and redefining them. Because of this, it's rather unusual to let two classes with state machines inherit each other. It's more useful to let a class with state machine inherit from other C++ classes.
Below is an example of a class with state machine that inherits from two C++ classes DataContainer<CData>
and IDisposable
.
class DataClass : `DataContainer<CData>`, `IDisposable` {\n [[rt::decl]]\n `\n void dispose() override; // From IDisposable\n `\n [[rt::impl]]\n `\n void DataClass:dispose() {\n // impl\n }\n `\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"art-lang/#abstract-class","title":"Abstract Class","text":"Just like for abstract capsules, you can declare a class with the abstract
keyword. However, since class state machines are not inherited, there is no formal meaning in doing so. It can still sometimes be useful to declare a class as abstract, to tell users of the class that it should not be directly instantiated. For example, if a class has a pure virtual function it is effectively abstract, and you can then use the abstract
keyword to further emphasize this.
abstract class AClass {\n [[rt::decl]]\n `\n virtual void implementMe() = 0;\n `\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"art-lang/#protocol-inheritance","title":"Protocol Inheritance","text":"A protocol may inherit events from another protocol. Only one base protocol is allowed; multiple inheritance is not supported for protocols. Inherited events can be redefined, but not excluded. A redefining event in a derived protocol can change the type of the event parameter as defined in the base protocol.
In the example below, the protocol ExtendedMachineEvents
adds one more in-event stop
to the inherited MachineEvents
protocol. It also redefines the startDeferred
event to change its parameter type.
protocol MachineEvents {\n in start();\n in startDeferred(`unsigned long` /* milliseconds */);\n\n out success();\n out error(`std::string` /* error message */);\n};\n\nprotocol ExtendedMachineEvents : MachineEvents {\n in stop();\n in redefine startDeferred(`unsigned long long`);\n};\n
Example
You can find sample applications using protocol inheritance here:
A template is a type that is parameterized by means of template parameters to make it more generic. When a template is used (a.k.a. instantiated), actual template parameters must be provided that match the formal template parameters defined in the template. Currently only classes can have template parameters. Just like in C++, two kinds of template parameters are supported:
Replaced with a type when the template is instantiated.
Replaced with a non-type, for example a constant value, when the template is instantiated.
Template parameters may have defaults that will be used if a matching actual template parameter is not provided when instantiating the template.
Below is an example of a class with template parameters, some of which have defaults specified. The keywords typename
and class
can both be used for defining a type template parameter. A non-type template parameter is defined by specifying its type as a C++ code snippet.
template <typename T = `int`, class U, `int` p1 = `5`>\nclass TemplateClass : `Base<T,U,p1>` {\n [[rt::decl]]\n `\n void func(T arg1) {\n // impl\n }\n `\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
Template parameters can only be used from C++ code snippets, and above you see some examples of how they can be used. It's not possible to instantiate a template in Art itself. For example, even if class Base
above was defined as an Art class, a C++ code snippet has to be used since it has template parameters.
Example
You can find a sample application using templates here.
"},{"location":"art-lang/#property","title":"Property","text":"Properties are name-value pairs that provide a generic mechanism for augmenting Art elements with extra data. Such data can be utilized by tools that operate on a parsed Art file, such as the code generator and semantic checker. Most Art elements can have properties and the syntax for specifying properties is the same regardless of the kind of element. However, different kinds of Art elements can have different properties.
For Art elements that have a name, properties are specified right after the name. For elements without name, properties are specified before the element itself. In both cases the syntax looks like this:
[[rt::properties(\n<property name>=<property value>,\n<property name>=<property value>,\n...\n<property name>=<property value>\n)]]\n
All properties have a default value, so you only need to specify a property if you want to set it to something else. The default values have been chosen so that you in most cases don't need to specify any properties at all.
A property has a type, and its value must conform to that type. The following property types are supported:
Boolean properties have a value that is either true
or false
. If you want to set a boolean property to true
you can use a shorthand syntax where you just specify the property name. For example:
capsule CapProp \n[[rt::properties(\ngenerate_file_impl=false,\ngenerate_file_header\n)]]\n{\n // ...\n};\n
Writing generate_file_header
is equivalent to writing generate_file_header=true
. However, this particular property has the default value true
and hence doesn't need to be set at all.
Integer properties have a numeric value (>= 0). Here is an example:
protocol XProtocol [[rt::properties(\n version=1\n)]]{\n // ...\n};\n
String properties have a string value, enclosed in double quotes. Here is an example:
class MC [[rt::properties(\n rule_config=\"E0022\"\n)]]{\n // ...\n};\n
A property of enumeration type has a value that references a literal of the enumeration. There are different enumerations used for different properties. The best way to learn about what enumeration literals that are available for a certain property is to use the Content Assist feature in the Art file editor. Place the cursor after the equal sign, and press Ctrl+Space. Here is an example of defining a property of enumeration type:
class MC [[rt::properties(\n kind=struct\n)]]{\n // ...\n};\n
Note that in some cases the name of an enumeration literal starts with underscore (_
) to prevent it from clashing with the set of Art keywords.
Below is a table that lists all properties that can be used on different kinds of Art elements. Each property is described in a section of its own below the table.
Art Elements Property Type Default Capsule generate_file_header Boolean true Capsule generate_file_impl Boolean true Capsule, Protocol, Port, Initial transition, Triggered transition Trigger rule_config String \"\" Protocol version Integer 0 Port registration Enumeration (automatic, automatic_locked, application) automatic Port registration_name String \"\" Initial transition, Triggered transition const_rtdata Boolean true Transition, State, Choice, Junction, Entry Point, Exit Point, Port, Part, Capsule, Class color String \"\""},{"location":"art-lang/#generate_file_header","title":"generate_file_header","text":"By default a capsule is translated to one header file (.h
) and one implementation file (.cpp
). Set this property to false
to prevent generation of the header file, for example if you prefer to write it manually.
By default a capsule is translated to one header file (.h
) and one implementation file (.cpp
). Set this property to false
to prevent generation of the implementation file, for example if you prefer to write it manually.
This property is used for configuring validation rules for an Art element. Read more about this here.
"},{"location":"art-lang/#version","title":"version","text":"Specifies the version of an Art element. You can use this to keep track of updates to types used in APIs (increase the version when the element changes).
"},{"location":"art-lang/#registration","title":"registration","text":"This property specifies how to register an unwired port at runtime. The default is automatic
which means the port will be registered automatically when the container capsule instance is initialized. The value automatic_locked
has the same meaning but the registration will be \"locked\" so that any future attempt to deregister it, or register it under a different name, will fail. Set the property to application
to programmatically register the port using the functions registerSPP()
and registerSAP()
respectively.
This property specifies the name to use when registering an unwired port at runtime. By default the port name is used, but it can be overridden using this property.
"},{"location":"art-lang/#const_rtdata","title":"const_rtdata","text":"This property can be set on transitions where you need to modify the data it receives when it's triggered. If the property is set to false
the rtdata
parameter in the transition function will be non-const. It can then be modified, which for example can avoid copying received message data and instead move it using its move constructor or move assignment operator.
[[rt::properties(const_rtdata=false)]] CurrentState -> NextState\n`\n someAttr = std::move(*rtdata); // Avoid copying the message data object\n`;\n\nMyTransition: [[rt::properties(const_rtdata=false)]] OtherState -> NextState\n`\n pC = std::move(*((MyClass*) rtdata)); // Avoid copying the message data object\n`;\n
Note that the const_rtdata
property appears in the Art syntax right after the transition name. If the transition has no name, it appears in the beginning of the transition declaration.
Specifies which color to use for an Art element in a diagram. Colors should be specified as RGB values using 6 hexadecimal digits. For example, \"#ff00ff\". The Art text editor will help you set an appropriate color by means of a color picker.
Note that you can also set the color directly from the diagram. Select a symbol or line and then set the color property using the Properties view (under \"Appearance\").
"},{"location":"art-lang/cpp-extensions/","title":"C++ Extensions","text":"An Art file may contain code snippets at various places, where the C++ code within these snippets takes the form of expressions (e.g., guard conditions), statements (e.g., transition effects), or declarations (e.g., types, functions, variables, etc.). While most code snippets are copied directly to the generated C++ files during the translation process from an Art file to C+, certain snippets containing declarations (specifically, those marked as rt::decl
and rt::impl
) undergo parsing and analysis by the code generator. The code generator identifies and processes certain C+ extensions, such as attributes applied to the declarations in these snippets, translating them into additional C++ code. This capability enables the code generator to enhance the C++ code you write by incorporating additional boilerplate code. This chapter provides details on the applicable C++ extensions and the corresponding generated code for each.
A type descriptor is meta data about a C++ type. The TargetRTS uses the type descriptor to know how to initialize, copy, move, destroy, encode and decode an instance of the type. Type descriptors for all primitive C++ types are included in the TargetRTS, but for other types you need to ensure type descriptors are available if you plan to use them in a way where the TargetRTS needs it.
Note
Most types in your application may not need a type descriptor, and for some types it will be enough to provide a partial implementation of the type descriptor. Only implement what is necessary for how the type actually will be used. For example, if you send an instance of a type in an event between two capsules, and the instance is copied rather than moved, then the type descriptor needs a copy function but doesn't need a move function. And if you don't encode instances to strings (or decode them from strings) then the type descriptor doesn't need encode and decode functions. Always think about how a type will be used before deciding if it needs a type descriptor or not.
"},{"location":"art-lang/cpp-extensions/#c-implementation","title":"C++ Implementation","text":"The C++ implementation of a type descriptor consists of four parts. In the code shown below we assume the type descriptor describes a type called \"MyType\".
1) A type descriptor object
This is a variable typed by RTObject_class with a name that has the prefix \"RTType_\". It will be declared in the header file:
extern const RTObject_class RTType_MyType;\n
and implemented in the implementation file:
const RTObject_class RTType_MyType =\n{\n // Initialization of RTObject_class corresponding to properties of MyType\n};\n
Member variables of RTObject_class store all information about the type, such as its name and byte size. Some of the member variables store pointers to type descriptor functions which the TargetRTS will call when it needs to do something with an instance of the type, for example copy or encode it.
2) Type descriptor functions
These are functions with a certain prototype, each of which performs a specific action on an instance of the type. The type descriptor object stores pointers to these functions. For a partial type descriptor, some of these pointers will be nullptr
and then the corresponding type descriptor function does not exist. Below is the list of type descriptor functions that can be part of a type descriptor:
void rtg_MyType_init(const RTObject_class* type, MyType* target)
Initializes an instance of the type copy void rtg_MyType_copy(const RTObject_class* type, MyType* target, const MyType* source)
Copies one instance of the type (source) to another (target) move void rtg_MyType_move(const RTObject_class* type, MyType* target, MyType* source)
Moves one instance of the type (source) to another (target) destroy void rtg_MyType_destroy(const RTObject_class* type, MyType* target)
Destroys an instance of the type encode void rtg_MyType_encode(const RTObject_class* type, const MyType* source, RTEncoding* coding)
Encodes an instance of the type decode void rtg_MyType_decode(const RTObject_class* type, MyType* target, RTDecoding* coding)
Decodes an instance of the type The encode function usually encodes the instance into a string representation, and the decode function usually parses the same string representation and creates an instance of the type from it. However, the functions use interface classes RTEncoding
and RTDecoding
from the TargetRTS which can be implemented in many different ways. Note also that you can globally disable the support for encoding and/or decoding by unsetting the macros OBJECT_ENCODE
and OBJECT_DECODE
respectively. Learn more about encoding and decoding in this chapter.
3) A type installer object
Decoding functions typically need to look up a type descriptor from the name of the type. For example, if it finds the type name \"MyType\" in the string that it parses, it needs to find the type descriptor object for \"MyType\" so it can allocate memory for an instance of \"MyType\" and then initialize it. To facilitate this lookup the TargetRTS keeps a type descriptor registry. The purpose of the type installer object is to add the type descriptor to this registry. The C++ code looks like this:
#if OBJECT_DECODE\nRTTypeInstaller rtg_MyType_installer( RTType_MyType );\n#endif\n
4) A \"typed value\" struct
This is a struct which encapsulates an untyped instance of the type and its type descriptor. Its name has the prefix \"RTTypedValue_\". The struct has constructors that enable construction from a reference to an instance of the type. The typed value struct is the glue between generated code and TargetRTS code. TargetRTS functions get an untyped instance together with the type descriptor, and the type descriptor provides all information it needs to know about the type.
The typed value struct will be declared and implemented in the header file:
struct RTTypedValue_MyType\n{\n const void * data; // Untyped pointer to an instance of MyType\n const RTObject_class * type; // Type descriptor for MyType\n const bool rValueRef = false; // Was the typed value struct created from an rvalue or lvalue reference to MyType?\n inline RTTypedValue_MyType( const MyType & rtg_value )\n : data( &rtg_value )\n , type( &RTType_MyType )\n {\n }\n inline RTTypedValue_MyType( const MyType && rtg_value )\n : data( &rtg_value )\n , type( &RTType_Colors )\n , rValueRef( true )\n {\n }\n inline RTTypedValue_MyType( const MyType & rtg_value, const RTObject_class * rtg_type )\n : data( &rtg_value )\n , type( rtg_type )\n {\n }\n inline RTTypedValue_MyType( const MyType && rtg_value, const RTObject_class * rtg_type )\n : data( &rtg_value )\n , type( rtg_type )\n , rValueRef( true )\n {\n }\n inline ~RTTypedValue_MyType( void )\n {\n }\n};\n
"},{"location":"art-lang/cpp-extensions/#automatically-generated","title":"Automatically Generated","text":"The code generator will automatically generate a type descriptor for a type if you mark it with the rt::auto_descriptor attribute. The generated type descriptor functions will get a default implementation that depends on the kind of type. If the default implementation is not appropriate for your type you can simply provide your own implementation of one or many type descriptor functions in an rt::impl
code snippet.
Note
If you write your own type descriptor function it's important that its prototype exactly matches what is listed in this table. It's recommended to use Content Assist within the rt::impl
code snippet to avoid typos and reduce typing effort.
Here are some examples of using the rt::auto_descriptor
attribute on different kinds of types:
enum [[rt::auto_descriptor]] Status {\n Error, OK\n};\n\nenum class [[rt::auto_descriptor]] Colors { \n Red, Green, Yellow \n};\n\nstruct [[rt::auto_descriptor]] Point {\n int x;\n int y;\n};\n\n#include <string>\ntypedef std::string [[rt::auto_descriptor]] MyString;\n\n#include <vector>\nusing MyVector [[rt::auto_descriptor]] = std::vector<int>; \n
An automatically generated type descriptor behaves like this:
init
function for an enum will initialize it to the value of its first literal. For other types it will initialize the instance by invoking the parameterless constructor.copy
function for an enum just assigns the source
parameter to the target
parameter. For other types it will invoke the copy constructor.move
function is not available for an enum. For other types it will invoke the move constructor.destroy
function for an enum does nothing. For other types it will invoke the destructor.encode
function for an enum encodes it into an integer representation (0 for the first literal, 1 for the second, etc). For a typedef or type alias there is no default encode implementation. A structured type (struct or class) is encoded by calling put_struct
on the RTEncoding
. The TargetRTS provides encoding implementations that produce either JSON or a compact ASCII format.encode
implementation, the decode
implementation will read the encoded representation and create a corresponding instance of the type. For a typedef or type alias there is no default decode implementation.Here is an example of how to override the default implementation of a type descriptor (for an enum), and to provide a missing type descriptor function (for a typedef):
[[rt::decl]]\n`\n enum class [[rt::auto_descriptor]] Colors { \n Red, Green, Yellow \n }; \n\n #include <string>\n typedef std::string [[rt::auto_descriptor]] MyString;\n`\n\n[[rt::impl]]\n`\n static void rtg_Colors_init( const RTObject_class * type, Colors * target )\n {\n *target = Colors::Green; // Make Green the default color instead of Red\n }\n\n #if OBJECT_ENCODE\n static int rtg_MyString_encode(const RTObject_class* type, const MyString* source, RTEncoding* coding )\n {\n return coding->put_string(source->c_str()); // Encode as string literal\n }\n #endif\n`\n
Note that the default implementation of a type descriptor for a typedef or type alias makes the assumption that the typedefed or aliased type is a structured type which has both a parameterless constructor, a copy constructor and a move constructor. If this assumption is not correct, you need to write your own type descriptor functions, or even implement the whole type descriptor manually.
Example
You can find sample applications that use automatically generated type descriptors here:
If a type needs a type descriptor but the default implementation is not appropriate, and it's also not enough to simply override one or a few of the type descriptor functions with custom implementations, you can choose to implement the type descriptor manually. To do so you need to mark the type with the rt::manual_descriptor attribute. The code generator will then skip generation of the following parts of the type descriptor:
As an example assume we have a type alias for the Colors enum from the previous example. We only plan to use this type for standard enum encoding/decoding, but we want the Yellow color to be the default. Since the default type descriptor implementation for a type alias assumes the aliased type to be a structured type, we choose to do a manual implementation of the type descriptor instead of overriding several type descriptor functions (most of which we anyway won't need).
[[rt::decl]]\n`\n using MyColors [rt::manual_descriptor] = Colors;\n`\n\n[[rt::impl]]\n`\nstatic void rtg_MyColors_init( const RTObject_class * type, MyColors * target )\n{\n *target = Colors::Yellow; // Default color\n}\n\n// Manual type descriptor implementation\nconst RTObject_class RTType_MyColors =\n{\n nullptr\n , nullptr\n , \"MyColors\"\n , 0 /* RTVersionId */\n , sizeof( MyColors )\n , reinterpret_cast< RTInitFunction > ( rtg_MyColors_init )\n , nullptr\n#if RT_VERSION_NUMBER >= 7105\n , nullptr\n#endif\n#if OBJECT_DECODE\n , RTenum_decode\n#endif\n#if OBJECT_ENCODE\n , RTenum_encode\n#endif\n , RTnop_destroy\n , 0\n , nullptr\n};\n`\n
Use the Content Assist template \"New Type Descriptor Object\" to avoid typing all code manually.
Example
You can find a sample application that uses a manually implemented type descriptor here.
"},{"location":"art-lang/cpp-extensions/#versioning","title":"Versioning","text":"Type descriptors support versioning by means of the field RTObject_class::_version
. By default the version field is set to 0 which denotes the first version of the type described by the type descriptor. For types that are part of an API for which you need to maintain backwards compatibility, you can raise the version in case you need to modify the type in a way that is not backwards compatible. Use of versioning requires a manually implemented type descriptor, and the version information is for example used when you perform encoding/decoding using the RTVAsciiEncoding
and RTVAsciiDecoding
classes.
A type descriptor for a structured type (class or struct) contains information about the member variables (a.k.a fields) of the type. This information is stored in a field descriptor object typed by the TargetRTS class RTFieldDescriptor
.
If the structured type only contains public member variables, the code generator can generate the field descriptor as a global object in the implementation file. However, if at least one member variable is either private or protected, you need to declare the type descriptor inside the type, as a public member variable. The reason is that the byte offset of each member variable is stored in the field descriptor, and computing this offset requires access to the member variable from the field descriptor.
Below is an example of a struct and a class. The struct only contains public member variables and hence it's not necessary to declare the field descriptor manually. However, since the class contains a private member variable, we need to declare a field descriptor for it.
[[rt::decl]]\n`\n struct [[rt::auto_descriptor]] Point {\n int x;\n int y;\n };\n\n class [[rt::auto_descriptor]] DataPoint : public Point {\n private:\n long data;\n\n public: \n static const RTFieldDescriptor rtg_DataPoint_fields[];\n };\n`\n
A field descriptor member variable must have the name rtg_<type>_fields
, where <type>
is the name of the structured type.
Use Content Assist to create a class or struct that contains a field descriptor.
Example
You can find a sample application where a field descriptor is declared here.
"},{"location":"art-lang/cpp-extensions/#inheritance","title":"Inheritance","text":"If a class or struct inherits from another class or struct, as in the example above, then the type descriptor of the derived type will have a reference to the type descriptor of the base type. In this case both types need to have a type descriptor. A type descriptor can at most reference one base type descriptor (a.k.a. a super descriptor) which means that only single inheritance is supported for automatically generated type descriptors. If you use multiple inheritance you have to write a manual type descriptor for the derived type.
Example
You can find a sample application that uses an automatically implemented type descriptor for a class that inherits from another class here.
"},{"location":"building/","title":"Building","text":"Art and C++ files can be built into applications or libraries. The build process consists of three steps:
Generate source files In this step Art files are translated to C++ source files.
Generate a make file A make file for building generated C++ code (and possibly also other non-generated C++ source files) is generated.
Run make to generate binaries A make tool is invoked for building an executable or library from the generated make file.
All these steps require information which is stored in a transformation configuration (TC for short). It is a text file which contains various properties needed for translating Art elements to C++ code, for generating the make file, and finally for launching the make tool. You can have more than one TC in your workspace, but at most one TC in each workspace folder can be active. Set a TC as active by right-clicking on it and perform the command Set as Active. An active TC is marked with a checkmark.
Once there is an active TC in a workspace folder, the first and second steps (generation of C++ source files and make file) will happen automatically for all Art files contained in that workspace folder. The files that get generated are placed in its own workspace folder as specified by the targetFolder
property of the TC. The C++ code in this target workspace folder is then incrementally and automatically updated as soon as any of these Art files are changed. Also the make file (which by default is placed in a subfolder called default
in the target workspace folder) gets updated when needed. Below is an example of a simple target workspace folder.
To perform the third step (running make to generate binaries) you can simply go to the target folder in the Terminal and invoke the command make
. Alternatively you can use the TC context menu command Build (see below).
The context menu of a TC provides a few useful commands that automate some of the steps mentioned above:
Build This command first generates C++ code and a make file for the TC, and then runs the make tool on the generated make file. Note, however, that this command does not set the TC as active. If you plan to change code snippets in generated code you must set the TC as active yourself.
Run First builds the TC, and then attempts to launch the executable that is produced. The executable is launched in a non-debug mode by specifying the launch argument -URTS_DEBUG=quit
. If you instead want to launch the executable for debugging it you can go to the Terminal and manually launch it from there without any extra arguments. Note that if your TC creates a library rather than an executable, then this command will still build the TC, but will then give an error message since there is no executable to run.
Note
For a more flexible way of launching a built executable, consider using a launch configuration.
make clean
to clean using the make file.If you build a TC and at least one error exists in the TC itself, in prerequisites TCs or in any of the Art files that will be built, then a dialog will ask if you want to cancel the build (recommended) or proceed anyway (only do this if you are confident the errors are safe to ignore).
Use the setting code-rt.build.cancelOnError
to suppress this dialog.
In some cases of rapid prototyping and testing you may want to quickly build and run a capsule without first having to create a TC or a launch configuration. Then you can click the \"Run\" link that appears just before any capsule in the Art text editor.
This command will create a temporary TC file and use a temporary target folder. All other TC properties will have their default values.
"},{"location":"building/#build-messages","title":"Build Messages","text":"When you use the Build or Run commands on a TC, messages will be printed in two places depending on what kind of message it is:
You can navigate from an element in an Art file to the corresponding element in the C++ file that gets generated from that Art file. Use the context menu that appears when you right-click on an element in an Art file and invoke the command Open Generated Code. If C++ code has not yet been generated for the Art file, for example because no active TC has been set, navigation will fail with an error message.
When navigating to generated C++ code an attempt is made to put the cursor as close as possible to the relevant C++ element. However, when there is no C++ element that directly corresponds to the selected Art element, the cursor may instead be placed on a container C++ element. If there are more than one C++ element generated from a single Art element, you will be prompted for where to navigate. For example:
For C++ code snippets you can as an alternative perform the navigation using a tooltip that appears when you hover over the code snippet:
If the cursor is within the C++ code snippet when navigating, the cursor will be set at the same place in the generated C++ code. This is convenient if you start to edit a code snippet in an Art file but later realize that you want to edit it in the generated C++ code instead.
You can also navigate in the other direction, i.e. from generated C++ code to the source Art file. This is described below in Making Changes in Generated C++.
"},{"location":"building/#making-changes-in-generated-c","title":"Making Changes in Generated C++","text":"C++ code snippets that are embedded in the Art file will be enclosed by special comments in the generated C++ file. You can edit such code snippets in a generated C++ file. When you save the file your changes will be automatically propagated back to the Art file. Here is an example of what a code snippet may look like in the generated C++ code:
//{{{USR file:///c:/code-realtime/workspaces/demoWorkspace/HelloWorld.art#::HelloWorld::<TopStateMachine>::<TriggeredTransition_5>::<Effect>\n std::cout << \"Hello World!\" << std::endl;\n context()->abort();\n//}}}USR\n
The comment contains information about the source Art file and the Art element in that file that contains the code snippet.
Warning
Only make edits on the lines within the special code snippet comments. If you edit outside the comment those edits will be lost the next time the file gets regenerated. And if you change the comment itself, the propagation of changes back to the Art file will no longer work correctly.
One very common scenario where it's useful to change a code snippet in a generated file is when there is a compilation error reported in the code snippet. Navigating from that compilation error will take you to the code snippet in the generated file, and it's convenient to directly fix the problem there.
Another scenario is when you write new code in such a code snippet and want to take advantage of the editing support for C++ that is provided by your IDE, and/or need to see the full C++ context of the edited code snippet. You can navigate from the code snippet in the Art file to the code snippet in the generated file as described above.
Sometimes you may need to navigate in the other direction, i.e. from a code snippet in the generated C++ code to the source Art file. For example, when editing the effect code of a transition it can be useful to look at that transition in its state machine (either in the Art file or in a state diagram). You can Ctrl-click the hyperlink of the USR-comment to perform this navigation as shown in the picture below.
You can make edits in multiple code snippets in a generated file. When the file is saved all edited code snippets will be automatically propagated back to the Art file.
Warning
Code snippets in Art files can only be updated when there is an active TC set. Changes made in generated code snippets will be lost the next time they are generated, unless you have set the TC as active. To prevent this, always make sure the TC is set as active before you make any changes in generated files.
Pay attention to the status bar in the bottom left corner when you save a generated file. If you know at least one code snippet was modified, but still get the message shown below:
then you can know the changes failed to propagate to the Art file. If the update was successful you should instead get a message that tells how many code snippets that were updated. For example:
"},{"location":"building/#building-from-the-command-line","title":"Building from the Command Line","text":"You can build a TC from the command line by using the Art Compiler.
"},{"location":"building/art-compiler/","title":"Art Compiler","text":"The Art Compiler is a stand-alone tool which can be used for building a TC from the command-line. Using this tool makes it possible to integrate the translation of Art files into C++, and compilation of that C++ code, into your automated build process.
"},{"location":"building/art-compiler/#location-and-launching","title":"Location and Launching","text":"The Art Compiler is located in the bin
folder of the Code RealTime installation. It's a JAR file with the name artcompiler.jar
.
Tip
The folder where the Art Compiler is located can be seen from the message that is printed in the Art Server output channel when the Code RealTime extension is activated. It's located in the same folder as the Art Language Server JAR file, called artserver.jar
.
To launch the Art Compiler you need a Java Virtual Machine (JVM). You should use the same JVM as is used for running the Art Language Server (see Setup Java). Launch the Art Compiler with the java
command like this:
java <JVM options> -jar <extension-path>/bin/artcompiler.jar <Art Compiler options>\n
Often you don't need to use any JVM option, but if the application is huge you may need to increase the memory of the JVM. Refer to the documentation of your JVM for a list of available JVM options. You may want to use the same JVM options as are used when launching the Art Language Server (see the setting code-rt.languageServer.jvmArgs
), but it's not required to do so.
To test that the Art Compiler can be successfully launched you can try to invoke it without any arguments. You should see an output similar to the below:
C:\\openjdk-17\\bin\\java -jar c:\\Users\\MATTIAS.MOHLIN\\testarea\\install\\VSCode\\data\\extensions\\secure-dev-ops.code-realtime-ce-1.0.0\\bin\\artcompiler.jar\n10:24:53 : INFO : Art Compiler 1.0.0-20231212_1212\n10:24:53 : INFO : Copyright (C) HCL Technologies Ltd. 2022, 2023.\n10:24:54 : INFO : Arguments:\nUsage: java -jar artcompiler.jar <options>\nOptions:\n LIST OF OPTIONS\n\nAll options with argument can be used in format <option> <argument> or <option>=<argument>.\n
"},{"location":"building/art-compiler/#art-compiler-options","title":"Art Compiler Options","text":"The Art Compiler accepts options in the form of command-line arguments to artcompiler.jar
that start with single or double dash (-
or --
). Many options can take an argument which then needs to be of the correct type (Boolean, Path etc). You can specify the argument for an option either like this
<option> <argument>\n
or like this
<option>=<argument>\n
Below is a table that lists all options that are available for the Art Compiler. Each option is described in a section of its own below the table.
Option Argument Type buildConfig String buildVariants Path cwd Path generate N/A help N/A out Path ruleConfig String tc Path version N/A ws Path"},{"location":"building/art-compiler/#buildconfig","title":"buildConfig","text":"A build configuration is useful when you want to build a TC that uses build variants. It provides values for build variant settings and hence specifies a certain variant of the application to be built. Read more about build configurations here.
"},{"location":"building/art-compiler/#buildvariants","title":"buildVariants","text":"Specifies a Build Variants script to use for the build. Read more about build variants here.
"},{"location":"building/art-compiler/#cwd","title":"cwd","text":"Set the current working directory. By default this is the location from which you launch the Art Compiler. If you use a relative path in options that take a path as argument, such as --out or --tc, the path will be resolved against the current working directory.
"},{"location":"building/art-compiler/#generate","title":"generate","text":"By default the Art Compiler will generate C++ files and a make file, and then build the C++ code by invoking make on the make file. If you set this option then only the files will be generated, but make will not be invoked. Usually the running of make is what takes most time when building a TC, so if you for example only is interested in getting the generated files you can save time by setting this option.
"},{"location":"building/art-compiler/#help","title":"help","text":"Use this option to print information about the version and all available options. This is the same information as is printed if launching the Art Compiler without any options. If this option is passed, all other options are ignored.
"},{"location":"building/art-compiler/#out","title":"out","text":"Set the output folder which controls where generated files will be placed. By default it is set to the folder that contains the folder containing the built TC. It hence by default corresponds to the workspace folder used when building from the UI. If you want to place generated files in a different location when building from the command-line you can set this option to another folder. Relative paths specified as targetFolder in TCs will be resolved against the specified --out
folder.
Specifies which validation rules that should be enabled, and what severity the problems they find should have. Rules are configured using the same syntax as is used for the rule_config
property in an Art file. For example:
--ruleConfig \"W0009,X7001\"\n
Read more about how to configure validation rules here.
"},{"location":"building/art-compiler/#tc","title":"tc","text":"Specifies the TC to build. This option is mandatory, unless you only pass the help or version options.
"},{"location":"building/art-compiler/#version","title":"version","text":"Use this option to print the version of the Art Compiler. This version is the same as is used for the Code RealTime extension and can also be seen in the file CHANGELOG.md
in the Code RealTime installation folder. If this option is passed, all other options are ignored.
Specifies a workspace file (.code-workspace
) which will be used for resolving paths that are relative to the workspace. It's optional to use this option and it only needs to be set if any of the built TCs contain workspace-relative paths.
--ws C:/art-comp-test/validation.code-workspace\n
"},{"location":"building/art-compiler/#art-compiler-steps-and-messages","title":"Art Compiler Steps and Messages","text":"The Art Compiler performs its work using several sequential steps. During each step messages can be printed with a severity that is either INFO, WARNING or ERROR. Messages are printed to stdout
with a time stamp. If at least one error is reported when performing one of the steps, the Art Compiler stops and doesn't proceed with the next step.
The following steps are performed:
RTPredefined.art
from the Code RealTime installationNote that the last step is skipped if the generate option is set.
"},{"location":"building/art-compiler/#process-return-value","title":"Process Return Value","text":"The Art Compiler exits with a zero return value if no errors occur when building the TC. The value will be non-zero in case an error occured and in that case there will also be a printout explaining why the build failed. You can for example use the Art Compiler process return value if you launch it from a script that needs to know if the build was successful or not.
"},{"location":"building/build-cpp-files/","title":"C++ Source Files","text":"You can include regular, non-generated, C++ files in a build. This can be useful whenever you need to include some C++ code in your application that doesn't naturally belong to any particular Art element. For example, you can write utility functions or types in regular C++ files and then use these functions and types from any Art element in the application.
All files with the file extension .cpp
that are present in a workspace folder, or in a subfolder of the workspace folder, are assumed to be C++ source files and will be compiled together with generated C++ files. They will be compiled with the same compiler flags as are used for generated C++ files, and resulting object files will be placed in the target folder (in the same subfolder hierarchy as in the workspace folder).
All files with the file extension .h
that are present in a workspace folder, or in a subfolder of the workspace folder, are assumed to be C++ header files. The folders where they are located will be added automatically to the inclusionPaths property so they will be found by the preprocessor.
As an example, let's assume we have a workspace folder with the following structure:
The file utils.h
contains declarations of some utility functions, and utils.cpp
contains their implementations. To use one of these utility functions from main.art
you just need to include the header file. For example:
capsule Main {\n [[rt::impl_preface]]\n `\n #include <iostream>\n #include \"utils.h\" // Automatically added to inclusion paths\n `\n\n statemachine {\n state S1;\n initial -> S1\n `\n std::string str(\"Hello \");\n concat_string(str, \"World!\"); // Utility function defined in utils.cpp\n std::cout << str << std::endl;\n `;\n };\n};\n
It's recommended to place C++ source files in subfolders as in the example above. If you place them directly in the workspace folder you need to ensure they don't have the same name as any of the Art elements that are built. This is to ensure there are no name clashes between the object files that are produced when building.
"},{"location":"building/build-cpp-files/#excluding-source-files","title":"Excluding Source Files","text":"By default all .cpp
and .h
files that are found in a workspace folder, or in a folder that is directly or indirectly contained in the workspace folder, will be included in the build. If you want to exclude some of these files from the build you can use the sources
TC property, just as for Art files. All .cpp
and .h
files that match at least one pattern in the sources
property, and doesn't match an anti-pattern (starting with the character !
) will be included into the build.
Note
If you set the sources
property to explicitly include some specific .cpp
and .h
files you also need to specify which .art
files to include. That is, make sure the specified patterns match all files that should be included in the build (both .cpp
, .h
and .art
files). If you only specify anti-patterns (i.e. files to exclude from the build) you don't need to do this, since by default all files are included except the ones that are excluded.
Here are some examples:
tc.sources = [\"**/*.cpp\", \"**/*.h\", \"*.art\"]; // Include all .cpp and .h files from the workspace folder and all its subfolders, and all .art files from the workspace folder. This is the default behavior if the \"sources\" property is not set.\ntc.sources = [\"src/utils.cpp\", \"include/utils.h\", \"*.art\"]; // Include a specific .cpp and .h file into the build\ntc.sources = [\"!src/utils.cpp\", \"!include/utils.h\"]; // Include all .cpp and .h files except two specific ones (all .art files will be built)\n
If you have set-up the TC to place generated C++ files in a subfolder of the workspace folder, you don't have to exclude them by means of the sources
TC property. Generated files will always be included in the build exactly once, no matter where they are located.
The C++ extension that you use with Code RealTime makes use of JSON files for knowing how to analyze C++ source code. Such JSON files are automatically generated by the C++ code generator based on information provided in the TC:
c_cpp_properties.json
is usedcompile_commands.json
is usedWhen you add C++ source files to a workspace folder that you want to build together with the Art files it contains, you should also create such a JSON file to enable the C++ extension to also analyze these files. Without doing so, features such as content assist, navigation and hover tooltips will not work when you edit those files. Rather than creating these files manually it's often easiest to copy the ones that are generated by the C++ code generator from the target folder, and then modify them as required. The most important thing to get right is the inclusion paths to make sure the C++ extension is able to find all include files required by a certain C++ source file.
"},{"location":"building/build-variants/","title":"Build Variants","text":"There is often a need to build several variants of the same application. For example:
A variant of an application may often only use slightly different build settings (for example, setting a compiler flag to include debug symbols), but in some cases the application logic may also be somewhat different (for example, use of some code that is specific to a certain target platform). Code RealTime provides a powerful mechanism, based on scripting, that allows you to build several variants of an application with minimal effort.
"},{"location":"building/build-variants/#dynamic-transformation-configurations","title":"Dynamic Transformation Configurations","text":"A TC is defined using JavaScript which is interpreted when it is built. This opens up for dynamic TC properties where the value of a property is computed at build-time. For simple cases it may be enough to replace static values for TC properties with JavaScript expressions to be able to build several variants of an application. As an example, assume that you want to either build a release or a debug version of an application. The debug version is obtained by compiling the code with the $(DEBUG_TAG)
flag. The TC can then for example look like this:
let tc = TCF.define(TCF.CPP_TRANSFORM);\ntc.topCapsule = 'Top';\nlet system = Java.type('java.lang.System');\nlet isDebug = system.getenv('DEBUG_BUILD');\ntc.compileArguments = isDebug ? '$(DEBUG_TAG)' : '';\n
TCs are evaluated using Nashorn which is a JavaScript engine running on the Java Virtual Machine. This is why we can access a Java class such as java.lang.System
to read the value of an environment variable. With this TC, and the use of an environment variable DEBUG_BUILD
, we can now build either a release or a debug version of our application depending on if the environment variable is set or not.
However, defining variants of an application like this can become messy for more complex examples. Setting up several environment variables in a consistent fashion will require effort for everyone that needs to build the application, and perhaps you even need to write a script to manage it. But the biggest problem is that the JavaScript that defines the build variants is embedded into the TC file itself. This makes it impossible to reuse a build variant implementation for multiple TCs.
"},{"location":"building/build-variants/#build-variants-script","title":"Build Variants Script","text":"A Build Variants script allows to define build variants outside the TC itself. It defines a number of high-level settings, we call them build variant settings, each of which is implemented by a separate JavaScript file. There are two kinds of build variant settings that can be defined:
A Build Variants script must have a global function called initBuildVariants
which defines the build variant settings. Here is an example where one boolean and one enumerated build variant setting are defined:
// Boolean setting\nlet isDebug = {\n name: 'Debug',\n script: 'debug.js',\n defaultValue: false,\n description: 'If set, a debug version of the application will be built'\n};\n\n// Enumerated setting\nlet optimization = {\n name: 'Optimization',\n alternatives: [\n { name: 'HIGH', script: 'opt.js', args: ['HIGH'], description: 'Apply all optimizations', defaultValue: true },\n { name: 'MEDIUM', script: 'opt.js', args: ['MEDIUM'], description: 'Apply some optimizations'},\n { name: 'OFF', script: 'opt.js', args: ['OFF'], description: 'Turn off all optimizations' }\n ]\n};\n\n// This function defines which build variant settings that are applicable for a certain TC\nfunction initBuildVariants(tc) {\n BVF.add(isDebug);\n BVF.add(optimization);\n}\n
Note the following:
name
property specifies a user-friendly name of the build variant setting.description
property to document what the build variant setting means and how it works.defaultValue
property set. This alternative will be used in case no value is provided for that build variant setting at build-time. For a boolean setting, defaultValue
should be set to either true
or false
depending on if you want the build setting to be turned on or off by default.script
property specifies the JavaScript file that implements the build variant setting. The path is relative to the location of the Build Variants script (usually they are all placed in the same folder). For a boolean setting the script is only invoked if the setting is set to true
. For an enumerated setting the script is always invoked, and the value of the enumerated setting is passed as an argument to the script using the args
property. It's therefore possible (and common) to implement all alternatives of an enumerated setting with the same script.The initBuildVariants
function gets the built TC as an argument. You can use it for defining different build variant settings for different kinds of TCs. Here is an example where a build variant setting only is defined for an executable TC:
function initBuildVariants(tc) {\n if (tc.topCapsule) {\n // Executable TC\n BVF.add(linkOptimization);\n }\n else {\n // Library TC\n }\n}\n
"},{"location":"building/build-variants/#build-variant-setting-script","title":"Build Variant Setting Script","text":"A script that implements a build variant setting is invoked twice when a TC is built. The first time a function preProcess
gets called, and the second time a function postProcess
gets called. You can define either one or both of these functions depending on your needs.
If a preProcess
function exists it will be called before the built TC is evaluated. Therefore you cannot access the TC in this function. The only input to the function is the script arguments, as defined by the args
property for an enumerated setting. The main reason for implementing a preProcess
function is to compute some data based on a build variant setting. Such data can be stored globally and later be accessed when postProcess
gets called or in a TC file when setting the value of a TC property. Here is an example:
function preProcess( targetPlatform ) {\n MSG.formatInfo(\"Building for target platform %s\", targetPlatform);\n TCF.globals().targetPlatform = targetPlatform;\n if (targetPlatform == 'Win64_MSVS') {\n TCF.globals().targetCompiler = 'MSVS';\n MSG.formatInfo(\"Building with Microsoft Visual Studio Compiler\"); \n } else {\n TCF.globals().targetCompiler = 'GCC'; \n MSG.formatInfo(\"Building with GNU Compiler\");\n }\n}\n
Here we use the MSG
object for printing messages to the build log and we use the TCF
object for storing globally some data that we have computed based on the build variant setting. Remember that the TCF
object also is available in a TC file, which means that TC properties may access the stored global data.
If a postProcess
function exists it will be called after the built TC has been evaluated. The function gets the built TC as an argument, as well as all its prerequisite TCs. For an enumerated setting it also gets the script arguments as defined by the args
property. The function can directly modify properties of both the built TC and all its prerequisites. The property values that the TCs have when the function returns are the ones that will be used in the build. Hence this function gives you full freedom to customize all TC properties so that they have values suitable for the build variant setting. Here is an example that uses the global data computed in the preProcess
function above:
function postProcess(topTC, allTCs, targetPlatform) { \n for (i = 0; i < allTCs.length; ++i) {\n if (TCF.globals().targetCompiler == 'MSVS') {\n allTCs[i].compileCommand = 'cl';\n }\n else if (TCF.globals().targetCompiler == 'GCC') {\n allTCs[i].compileCommand = 'gcc';\n } \n }\n if (targetPlatform == 'MacOS') {\n MSG.formatWarning(\"MacOS builds are not fully supported yet\"); \n }\n}\n
Also in this function we can use the TCF
and MSG
objects to access global data and to print messages to the build log. But most importantly, we can directly write the properties of the topTC
(the TC that is built) and/or allTCs
(the TC that is built followed by all its prerequisite TCs).
By modifying the compileArguments TC property the build variant setting script can set preprocessor macros in order to customize the code that gets compiled. Hence we can both customize how the application is built, and also what it will do at run-time. This makes Build Variants a very powerful feature for building variants of an application, controlled by a few well-defined high-level build variant settings.
Example
You can find a sample application that uses build variants here.
"},{"location":"building/build-variants/#build-configuration","title":"Build Configuration","text":"When building a TC that uses build variants you need to provide values for all build variant settings, except those for which you want to use their default values. These values are referred to as a build configuration. You can only specify a build configuration when building with the Art Compiler. When building from within the IDE, all build variant settings will get their default values.
Specify the build configuration by means of the --buildConfig option for the Art Compiler. A boolean build variant setting is set by simply mentioning the name of the setting in the build configuration. To set an enumerated build variant setting use the syntax setting=value
. Separate different build variant settings by semicolons. For the sample build variants script above, with one boolean and one enumerated build variant setting, a build configuration can look like this:
--buildConfig=\"Debug;Optimization=MEDIUM\"
Build variant scripts are implemented with JavaScript and run on a Java Virtual Machine (JVM) by means of an engine called Nashorn. It supports all of ECMAScript 5.1 and many things from ECMAScript 6. Since it runs on the JVM you can access Java classes and methods. See the Nashorn documentation to learn about these possibilities.
In addition to standard JavaScript and Java functionality, a build variant script can also use an API provided by Code RealTime. This API consists of a few JavaScript objects and functions. Note that there are three different contexts in which JavaScript executes in Code RealTime and not all parts of the API are available or meaningful in all contexts.
Evaluation of a TC: TCs are evaluated when they are built, but also in order to perform validation of TC properties, for example while editing the TC. JavaScript in a TC file has access to the TCF object. Typically on the first line in a TC it's used like this: let tc = TCF.define(TCF.CPP_TRANSFORM);
. Since TCs are evaluated frequently all JavaScript it contains should only compute what it necessary for setting the values of TC properties. It should not have any side-effects, and should not print any messages.
Build Variants script: A build variants script is evaluated when building a TC with the Art Compiler. This evaluation happens early with the purpose of deciding which build variant settings that are applicable for the build. You can use the BVF object in a build variants script.
Build Variant Setting script: A build variant setting script is evaluated when building a TC with the Art Compiler. It's evaluated twice as explained above. You can use the MSG and TCF objects in a build variant setting script.
This object provides a \"Build Variant Framework\" with functions that are useful when implementing a Build Variants script. The object is only available in that kind of script.
"},{"location":"building/build-variants/#bvfadd","title":"BVF.add","text":"add(...buildVariantSettings)
Adds one or several build variant settings to be available for the current build. Each build variant setting is represented by a JavaScript object that either describes a boolean or enumerated setting as explained above.
"},{"location":"building/build-variants/#bvfaddcommonutils","title":"BVF.addCommonUtils","text":"addCommonUtils(...commonUtils)
If you implement utility functions that you want to use from several scripts you can make them globally available by means of this function. For example:
let myUtils = {\n name: 'My utils', // Any name\n script: 'myUtils.js' // Script that contains global utility functions\n}\nfunction initBuildVariants(tc) {\n BVF.addCommonUtils(myUtils);\n // ...\n}\n
All functions defined in \"myUtils.js\" will now be available to be used by build variant setting scripts.
"},{"location":"building/build-variants/#bvfformatinfo","title":"BVF.formatInfo","text":"formatInfo(msg,...args)
Prints an information message to the build log. This function works the same as MSG.formatInfo.
"},{"location":"building/build-variants/#bvfformatwarning","title":"BVF.formatWarning","text":"formatWarning(msg,...args)
Prints a warning message to the build log. This function works the same as MSG.formatWarning.
"},{"location":"building/build-variants/#bvfformaterror","title":"BVF.formatError","text":"formatError(msg,...args)
Prints an error message to the build log. This function works the same as MSG.formatError. Note that the Art Compiler will stop the build if an error is reported.
"},{"location":"building/build-variants/#msg-object","title":"MSG Object","text":"This object provides functions for writing messages to the build log. Each function takes a message and optionally also additional arguments. The message may contain placeholders, such as %s, that will be replaced with the arguments. You must make sure the number of arguments provided match the number of placeholders in the message, and that the type of each argument matches the type of placeholder (e.g. %s for string).
The MSG object is only available in a build variant setting script.
"},{"location":"building/build-variants/#msgformatinfo","title":"MSG.formatInfo","text":"formatInfo(msg,...args)
Prints an information message to the build log. Example:
MSG.formatInfo(\"Building for target platform %s\", targetPlatform);\n
"},{"location":"building/build-variants/#msgformatwarning","title":"MSG.formatWarning","text":"formatWarning(msg,...args)
Prints a warning message to the build log.
"},{"location":"building/build-variants/#msgformaterror","title":"MSG.formatError","text":"formatError(msg,...args)
Prints an error message to the build log. Note that the Art Compiler will stop the build if an error is reported.
"},{"location":"building/build-variants/#tcf-object","title":"TCF Object","text":"This object provides a \"Transformation Configuration Framework\". It is available in a build variant setting script and also in a TC file.
"},{"location":"building/build-variants/#buildvariantsfolder","title":"buildVariantsFolder","text":"buildVariantsFolder() -> String
Returns the full path to the folder where the build variants script is located.
"},{"location":"building/build-variants/#buildvariantsscript","title":"buildVariantsScript","text":"buildVariantsScript() -> String
Returns the full path to the build variants script.
"},{"location":"building/build-variants/#define","title":"define","text":"define(descriptorId) -> {TCObject}
Creates a new TC object. This function is typically called in the beginning of a TC file to get the TC object whose properties are then set.
"},{"location":"building/build-variants/#gettoptc","title":"getTopTC","text":"getTopTC() -> {TCObject}
Returns the top TC, i.e. the TC that is built. You can use this from a prerequisite TC to access properties set on the top TC. For example, it allows a library TC to set some of its properties to have the same values as are used for the executable TC. This can ensure that a library is built with the same settings that are used for the executable that links with the library. Here is an example of how a library TC can be defined to ensure that it will use the same target configuration as the executable that links with it:
let tc = TCF.define(TCF.CPP_TRANSFORM);\nlet topTC = TCF.getTopTC().eval; // eval returns an evaluated TC object, where all properties have ready-to-read values (even for properties with default values)\ntc.targetConfiguration = topTC.targetConfiguration;\n
"},{"location":"building/build-variants/#globals","title":"globals","text":"globals() -> {object}
Returns an object that can store global data needed across evaluations of different JavaScript files. For an example, see above.
"},{"location":"building/build-variants/#orderedgraph","title":"orderedGraph","text":"orderedGraph(topTC) -> [{TCObject}]
Traverses all prerequisites of a TC (topTC
) and returns an array that contains them in a depth-first order. The last element of the array is the top TC itself. The function also ensures that all prerequisite TCs are loaded.
var prereqs = TCF.orderedGraph(tc);\nfor (i = 0; i < prereqs.length; ++i) {\n var arguments = prereqs[i].compileArguments;\n // ...\n}\n
"},{"location":"building/launch-configurations/","title":"Launch Configurations","text":"As described above you can launch a built executable using the Run command in the TC context menu. This is a quick and easy way to run an executable, which is sufficient for simple applications. However, the simplicity comes with several limitations:
-URTS_DEBUG=quit
which means it will run in non-debug mode.A more flexible way to launch an executable is to use a launch configuration. This is a JSON file that contains several attributes that control how to launch the executable. Using a launch configuration also gives additional benefits:
To create a launch configuration open the \"Run and Debug\" view from the activity bar and then click the create a launch.json file hyperlink.
You can choose to store the launch configuration either in a workspace folder or in the workspace file. In most cases it makes sense to store it in the same workspace folder that contains the TC that will be used for building the application to launch. However, if you want to share the same launch configuration for multiple applications you can choose to store it in the workspace file instead.
When you choose to store the launch configuration in a workspace folder it will be placed in the .vscode
folder and get the name launch.json
:
The created launch configuration looks like this:
{\n \"type\": \"art\",\n \"request\": \"launch\",\n \"name\": \"runTC\",\n \"tc\": \"${workspaceFolder}/${command:AskForTC}\"\n}\n
You should change the name
attribute to give the launch configuration a more meaningful name.
You can have multiple launch configurations in the same launch.json
file. Create new ones either by copy/paste of another one, or by pressing the Add Configuration... button. You can also create new launch configurations using the drop down menu in the \"Run and Debug\" view:
The name of a launch configuration appears in the launch configuration drop down menu:
Launch the launch configuration by pressing the green arrow button that appears to the left of this drop down menu.
You can also perform the launch from the Run
menu, using the command Run without Debugging (Ctrl+F5). In that case the launch configuration that is selected in the \"Run and Debug\" view drop down menu will be used.
Below is a table that lists all attributes that can be used in a launch configuration. Each attribute is described in a section of its own below the table.
Attribute Description Mandatory type The type of launch configuration. Always \"art\". Yes request What the launch configuration will do. Always \"launch\". Yes name The name of the launch configuration Yes tc The TC file to use for building and launching the application. Yes args Command line arguments to pass to the launched application. No environment Environment variables to set for the launched application. No cwd Current working directory for the launched application No"},{"location":"building/launch-configurations/#type","title":"type","text":"This attribute specifies the type of launch configuration. It is mandatory and is always the string \"art\".
"},{"location":"building/launch-configurations/#request","title":"request","text":"This attribute specifies what the launch configuration will do. It is mandatory and is always the string \"launch\".
"},{"location":"building/launch-configurations/#name","title":"name","text":"Specifies the name of the launch configuration. You should give a meaningful and unique name to each launch configuration that describes what it does. The chosen name appears in the drop down menu in the \"Run and Debug\" view. When you first create a launch configuration it gets the name \"runTC\". Make sure to change this default name.
"},{"location":"building/launch-configurations/#tc","title":"tc","text":"This attribute specifies which TC file to use for building and launching. The specified TC must build an executable. You must use an absolute path to the TC file, but it can contain the ${workspaceFolder}
variable which expands to the location of the workspace folder. By default the tc
attribute is set to ${workspaceFolder}/${command:AskForTC}
which means you will be prompted for choosing which TC to use when the launch configuration is launched.
For a list of other variables that can be used in this attribute see this page.
"},{"location":"building/launch-configurations/#args","title":"args","text":"Specifies the command-line arguments for the launched executable. This is a list of strings, and by default it is set to [\"-URTS_DEBUG=quit\"]
which means that the executable will run in non-debug mode. You may add custom command-line arguments for your application as necessary. For example:
{\n \"type\": \"art\",\n \"request\": \"launch\",\n \"name\": \"Let my exe listen to a port\",\n \"tc\": \"${workspaceFolder}/app.tcjs\",\n \"args\": [\"-URTS_DEBUG=quit\", \"--port=12345\"]\n}\n
"},{"location":"building/launch-configurations/#environment","title":"environment","text":"Specifies environment variables to be set for the launched executable. This is a list of objects where each object has a property that specifies the name of an environment variable. The environment variable will be set to the value of that property. In the example below the environment variable LD_LIBRARY_PATH
will be set to /libs/mylibs
to tell a Linux system where to load shared libraries needed by the application.
{\n \"type\": \"art\",\n \"request\": \"launch\",\n \"name\": \"Launch and load shared libraries\",\n \"tc\": \"${workspaceFolder}/app.tcjs\",\n \"environment\": [{\"LD_LIBRARY_PATH\" : \"/libs/mylibs\"}]\n}\n
"},{"location":"building/launch-configurations/#cwd","title":"cwd","text":"By default the launched application runs in the same folder as where the executable is located. By setting this attribute you can change the current working directory to something else. The value of this attribute must be an absolute path, but certain variables can be used. See this page for more information.
"},{"location":"building/transformation-configurations/","title":"Transformation Configurations","text":"A transformation configuration (or TC for short) contains all properties needed for transforming Art files into C++ code and for building the generated code into an application or a library. It is a text file in JavaScript format with the file extension .tcjs. Using JavaScript for defining build properties has many advantages. For example, it allows for dynamic properties where the value is not a static value but computed dynamically by JavaScript code when the TC is built.
Code RealTime provides a dedicated language server for TCs to make them just as easy to work with as Art files. A form-based editor is also provided as an alternative.
"},{"location":"building/transformation-configurations/#creating-transformation-configurations","title":"Creating Transformation Configurations","text":"To create a new TC select a file in the workspace folder that contains the Art files you want to transform to C++. Then invoke the command File - New File - Transformation Configuration. In the popup that appears specify the name of the TC or keep the suggested default name.
A .tcjs file will be created with the minimal contents. Specify the mandatory topCapsule property (if you are building an executable) and any other properties needed.
"},{"location":"building/transformation-configurations/#setting-a-transformation-configuration-as-active","title":"Setting a Transformation Configuration as Active","text":"You can have more than one TC in your workspace, and also multiple TCs in the same workspace folder, but at most one TC in each workspace folder can be active. Code RealTime uses the active TC in several ways:
--tc
option for the Art Compiler.Note
If you don't set a TC as active none of the above will work (or will work incorrectly). It's therefore strongly recommended to create a TC and set it as active as early as possible when you start to work in a new Art workspace folder.
Set a TC as active by right-clicking on it and perform the command Set as Active. An active TC is marked with a checkmark.
If the active TC has prerequisites, those too will be set as active. This ensures that the results you get when working with Art files in the IDE will be the same as when you will build the TC using the Art Compiler.
You can deactivate an individual TC by removing the file art_build_settings.json
in the .vscode
folder. To deactivate all TCs in the workspace use the Deactivate All command in the Art Build view.
You can edit a TC directly as a JavaScript file in the text editor. Features such as content assist, navigation and hover tooltips work very similar to how they work for an Art file:
tc.
to get the list of all available TC properties that can be set. You can also use content assist in many places to get suggestions for valid TC property values, for example the list of available top capsules.As an alternative to editing a TC as a JavaScript file Code RealTime also provides a form-based editor which may be easier to use, especially until you are familiar with all TC properties that exist and what they mean.
To open the form-based TC editor, right-click on a TC file and invoke the context menu command Edit Properties (UI).
Each available TC property has its own widget for viewing and editing the value. The type of widget depends on the type of TC property. For example, an enumerated property like \"C++ Code Standard\" uses a drop down menu.
Click the info button to view documentation about a certain TC property. Click the button again to hide the documentation.
Certain TC properties have default values. Such values are not stored in the TC file, but the TC editor still shows them so you can know what value will actually be used unless you set a custom value for such a property.
You can tell which TC properties that have a custom (i.e. non-default) value set by looking at the color of the property name. Properties with custom values set have names shown in blue which are hyperlinks that will navigate to the value in the TC file. Such properties also have a \"Delete\" button which can be used for deleting the property value (i.e. to restore the property to use its default value).
You can freely choose if you want to edit TC files as text files or using the form-based TC editor, and you can even use both at the same time. The form-based TC editor is automatically updated as soon as you edit the TC file, and the TC file is automatically updated when a widget with a modified value loses focus.
"},{"location":"building/transformation-configurations/#transformation-configuration-prerequisites","title":"Transformation Configuration Prerequisites","text":"A TC can either build a library or an executable. This is controlled by the topCapsule property. If this property is set the TC will build an executable, otherwise it will build a library. To ensure that a library gets built before an executable that links with it, you can set the prerequisites property of the executable TC to reference the library TC. Doing so will also cause the executable to link with the library automatically (i.e. you then don't need to manually set-up necessary preprocessor include paths or linker paths using other TC properties).
If you change the prerequisites of a TC you should again set it as active so that the prerequisite TCs also become active.
"},{"location":"building/transformation-configurations/#accessing-the-top-tc-from-a-prerequisite-tc","title":"Accessing the Top TC from a Prerequisite TC","text":"A library TC that is built as a prerequisite of an executable TC should typically use the same values for many TC properties as are used by the executable TC. For example, both TCs should be compiled with the same compiler and use the same TargetRTS configuration, otherwise build inconsistencies could occur. To avoid the need of duplicating such common properties both in the executable TC and in all its prerequisite library TCs, it's possible to access properties of the executable TC in the library TC. Here is an example:
tc.compileArguments = TCF.getTopTC().eval.compileArguments;\n
The function TCF.getTopTC()
will at build-time return a reference to the TC that is at the \"top\" of the prerequisite hierarchy, i.e. the TC on which the build command is performed. It's typically an executable TC, but could also be a library TC (in case you build a library that depends on other libraries).
Since we construct a TC property value based on the value of another TC property (here coming from the top TC) we should use the eval
property to ensure all default values get expanded into real values.
Code RealTime provides a view called Art Build which makes several workflows related to TCs easier. The view shows all TCs that are present in the workspace so you don't have to find them in the Explorer view under each workspace folder. For each TC its prerequisites are shown below in a tree structure. This allows to quickly see which TCs a certain TC depends on through its prerequisites without having to open the TC editor.
The smaller gray text to the right of the TC name tells in which workspace folder the TC is located. This helps since it's common to have TCs with the same name in a workspace.
You can edit a TC by double-clicking on it. This will open the TC in a text editor.
When a TC is selected in the Art Build view you can use the toolbar buttons for building, cleaning and running it.
Tip
It's common to build the same TC many times when developing an Art application. By keeping that TC selected in the Art Build view you can quickly build it by just pressing its Build toolbar button. Building the TC from the Explorer view requires you to first find it which can be cumbersome since the Explorer view selection changes frequently.
There are also a few useful commands in the Art Build view toolbar:
Refresh In most cases the Art Build view refreshes automatically when TCs are modified. However, if needed you can force a refresh by pressing this button.
Clean All Cleans all TCs by removing all target folders in the workspace. Everything contained in the target folder will be deleted (generated code, makefiles, built binaries, etc). A message will be printed in the Art Server channel in case all target folders could be successfully removed, or if not, which ones that could not be deleted.
Collapse All Collapses all TCs to not show any prerequisites.
Deactivate All Makes all TCs non-active. This makes it easier to switch from building one set of active TCs to another.
Below is a table that lists all properties that can be used in a TC. Note that many TC properties have default values and you only need to specify a value for a TC property if its different from the default value. Each property is described in a section of its own below the table.
Property Type Default Value capsuleFactory String N/A commonPreface String N/A compileArguments String N/A compileCommand String \"$(CC)
\" copyrightText String N/A cppCodeStandard Enum string \"C++ 17\" eval TC object N/A executableName String \"$(TOP_CAPSULE)$(EXEC_EXT)
\" inclusionPaths List of strings [] libraryName String \"$(LIB_PFX)$(TCONFIG_NAME)$(LIB_EXT)
\" linkArguments String N/A linkCommand String \"$(LD)
\" makeArguments String N/A makeCommand String \"$defaultMakeCommand
\" prerequisites List of strings [] sources List of strings [\"*.art\"] targetConfiguration String Depends on current operating system targetConfigurationName String \"default\" targetFolder String Name of TC with \"_target\" appended targetRTSLocation String \"${code_rt_home}
/TargetRTS\" threads List of Thread objects List of two Thread objects (MainThread and TimerThread) topCapsule String N/A unitName String \"UnitName\" userLibraries List of strings [] userObjectFiles List of strings []"},{"location":"building/transformation-configurations/#capsulefactory","title":"capsuleFactory","text":"This property can be used for specifying a global capsule factory that can control how all capsule instances in the application are created and destroyed. One scenario where this is useful is when implementing dependency injection for capsule creation. See Capsule Factory and Dependency Injection for more information.
The value of this property should be a C++ expression of type RTActorFactoryInterface*
. If the expression contains the variable $(CAPSULE_CLASS)
it will be replaced with the name of the C++ class for the capsule. This can be useful for implementing a generic capsule factory which takes the capsule class as a template parameter.
tc.capsuleFactory = '&CapsuleFactory::factory';\n
Note that you can override the use of the global capsule factory by providing a local capsule factory for a specific part.
"},{"location":"building/transformation-configurations/#commonpreface","title":"commonPreface","text":"This property allows you to write some code that will be inserted verbatimly into the header unit file (by default called UnitName.h
). Since the header unit file is included by all files that are generated from the TC, you can use the common preface to define or include definitions that should be available everywhere in generated code.
tc.commonPreface = `\n#include <iostream>\n`;\n
"},{"location":"building/transformation-configurations/#compilearguments","title":"compileArguments","text":"Specifies the arguments for the C++ compiler used for compiling generated C++ code. Note that some compiler arguments may already be specified in the TargetRTS configuration that is used, and the value of this property will be appended to those standard compiler arguments.
tc.compileArguments = '$(DEBUG_TAG)'; // Compile code for debugging\n
"},{"location":"building/transformation-configurations/#compilecommand","title":"compileCommand","text":"Specifies which C++ compiler to use for compiling generated C++ code. The default value for this property is $(CC)
which is a variable that gets its value from the TargetRTS configuration that is used.
This property may be used to insert a common comment block in the beginning of each generated file, typically a copyright text.
tc.copyrightText = \n`\nCopyright \u00a9 2024 \nAll rights reserved!\n`;\n
You can use a multi-line text with empty lines in the beginning and end as shown above, to make the TC more readable. Such empty lines will be ignored by the code generator.
"},{"location":"building/transformation-configurations/#cppcodestandard","title":"cppCodeStandard","text":"Defines the C++ language standard to which generated code will conform. The default value for this property is C++ 17
. Other valid values are C++ 98
, C++ 11
, C++ 14
and C++ 20
. Note that the latest version of the TargetRTS requires at least C++ 11, so if you use an older code standard you have to set TargetRTSLocation to an older version of the TargetRTS that doesn't contain any C++ 11 constructs. If you need to compile generated code with a very old compiler that doesn't even support C++ 98 you can set this preference to Older than C++ 98
.
This is a special property that returns a copy of the TC where all properties with implicit default values have been expanded into real values. It's useful when you want to define a TC property value based on the value of another TC property which has a default value.
As an example consider this TC file which builds an application where the copyright statement is placed in the unit header file:
let tc = TCF.define(TCF.CPP_TRANSFORM);\ntc.commonPreface = `\n/** Copyright by me */\n`;\ntc.copyrightText = \"See copyright in \" + tc.eval.unitName + \".h\";\n
This TC doesn't set the value of the unitName property, but it has a default value (\"UnitName\") which can be obtained by means of the eval
property. If you would directly access tc.unitName
in the above example, the string \"undefined\" would be returned when the unitName property has not been explicitly set.
The eval
property is often used together with the TCF.getTopTC()
function as described here.
Specifies the name of the executable that is built by the TC. This property is only applicable for TCs that build executables.
The default value of this property is \"$(TOP_CAPSULE)$(EXEC_EXT)
\" where $(TOP_CAPSULE)
is the name of the topCapsule
and $(EXEC_EXT)
is a variable for the file extension which gets its value from the TargetRTS configuration that is used.
Specifies additional include paths for the C++ preprocessor in addition to \"standard\" ones such as the location of TargetRTS include files. If your application links with a user library or user object file you need to add the location of the header file(s) that belong to the library or object file.
tc.inclusionPaths = [\"/libs/myLib1/includes\", \"/libs/myLib2/includes\"];\n
Note that you don't need to add inclusion paths for target folders of prerequisite TCs. They are added automatically by the make file generator.
"},{"location":"building/transformation-configurations/#libraryname","title":"libraryName","text":"Specifies the name of the library that is built by the TC. This property is only applicable for TCs that build libraries.
The default value of this property is \"$(LIB_PFX)$(TCONFIG_NAME)$(LIB_EXT)
\" where $(LIB_PFX)
is a prefix and $(LIB_EXT)
is a file extension which get their values from the TargetRTS configuration that is used. $(TCONFIG_NAME)
expands to the name of the TC file (only the base name, not including the file extension).
Specifies the arguments for the C++ linker used for linking object files and libraries into an executable. This property is only applicable for TCs that build executables.
tc.linkArguments = '/DEBUG'; // Build executable for debugging with Visual Studio\n
"},{"location":"building/transformation-configurations/#linkcommand","title":"linkCommand","text":"Specifies which C++ linker to use for linking object files and libraries into an executable. The default value for this property is $(LD)
which is a variable that gets its value from the TargetRTS configuration that is used. This property is only applicable for TCs that build executables.
Specifies the arguments for the make command to be used.
tc.makeArguments = '-s'; // Silent make (do not print build commands to the terminal)\n
"},{"location":"building/transformation-configurations/#makecommand","title":"makeCommand","text":"Specifies which make command to use for processing the generated make file. By default the make command is $defaultMakeCommand
which gets its value from which TargetRTS configuration that is used.
This property is a list of references to other TCs that need to be built before the current TC. It's typically used to express that a library TC is a prerequisite of an executable TC, which means the library TC needs to be built before the executable TC. Below is an example where an executable TC has a library TC as a prerequisite:
tc.prerequisites = [\"../MyLibrary/lib.tcjs\"]; \n
Prerequisite TCs can either be specified using absolute or relative paths. Relative paths are resolved against the location of the TC that has the property set.
You can also use the predefined variable ${workspaceFolder}
in prerequisite paths. This is often useful as it makes it possible to reference a prerequisite TC based on another workspace folder, regardless of where in the file system it's located. For example:
tc.prerequisites = [\"${workspaceFolder:MyLibrary}/lib.tcjs\"]; \n
Note that use of workspace-relative paths requires setting the -ws option for the Art Compiler.
For more information about this property see Transformation Configuration Prerequisites.
"},{"location":"building/transformation-configurations/#sources","title":"sources","text":"By default all Art files that are located in the same folder as the TC will be transformed to C++. Sometimes you may want to exclude some Art files, for example because they are built with another TC, or they contain something you want to temporarily exclude from your application without having to delete the files or comment out their contents. In these cases you can set the sources
property to specify exactly which Art files that should be built by the TC. The value of the property is a list of strings that specify glob-style patterns and anti-patterns. An Art file will be transformed if it matches at least one pattern and doesn't match any anti-pattern. Anti-patterns start with the !
character. In both patterns and anti-patterns you can use the wildcards *
(matches any sequence of characters) and ?
(matches a single character). Below are a few examples:
tc.sources = [\"*.art\"]; // Transform all Art files in the folder that contains the TC. This is the default behavior if the \"sources\" property is not set.\ntc.sources = [\"cap1.art\", \"cap2.art\"]; // Only transform two specific Art files\ntc.sources = [\"*.art\", \"!code_rt_gen.art\"]; // Transform all Art files except one\ntc.sources = [\"!code_rt_gen.art\"]; // Same as above (i.e. the pattern \"*.art\" is optional)\ntc.sources = [\"source??.art\", \"!*_gen.art\"]; // Transform all Art files with names starting with \"source\" and followed by two arbitrary characters. Art files with a name that ends with \"_gen\" are excluded.\n
Example
You can find a sample application that has a TC with the \"sources\" property set here.
The sources
property can also be used to specify which regular (i.e. non-generated) C++ files that should be included in the build. See this chapter for more information.
Specifies which TargetRTS configuration to use. The TargetRTS location specified in the targetRTSLocation property defines valid values for this property. If this property is not specified, and the default TargetRTS location from the Code RealTime installation is used, then it will get a default value according to the operating system that is used. For Windows a MinGw-based configuration will be used, while for Linux a GCC-based configuration will be used.
tc.targetConfiguration = \"WinT.x64-VisualC++-17.0\";\n
"},{"location":"building/transformation-configurations/#targetconfigurationname","title":"targetConfigurationName","text":"This property maps to a subfolder of the target folder where all generated files that are not source code will be placed. This includes for example makefiles and the files that are produced by these makefiles (typically binaries). The default value of this property is default
.
Specifies where files generated from the TC will be placed. This property is usually just set to a name, and then a target folder with that name will be created next to the folder that contains the TC. When the build runs from the UI, that target folder is then added as a workspace folder, which will cause you to be prompted about if you trust the authors of the files in that folder. It's safe to answer yes since all files in that folder are automatically generated by Code RealTime.
You can specify an absolute path to a target folder if you prefer generated files to be placed in a certain location that is accessible to everyone in the team, for example a shared network drive. If you instead use a relative path it will get resolved relative to a desired output folder. For a UI build the output folder is set using the setting code-rt.build.outputFolder
. When building with the Art Compiler the output folder is instead set using the --out
option. If you haven't set the desired output folder, relative paths will instead be resolved against the folder that contains the workspace folder that contains the TC where the targetFolder
property is set.
Use forward slashes as path separator in this property.
If this property is not specified it defaults to the name of the TC, with _target
appended. For example, if the TC is called app.tcjs
, the target folder will default to app_default
.
Specifies the location of the TargetRTS to use. If no value is set for this property the TargetRTS from the Code RealTime installation will be used. If you want to use another TargetRTS specify the full path to the TargetRTS
folder (including that folder itself). Use forward slashes as path separator. For example:
tc.targetRTSLocation = \"C:/git/rsarte-target-rts/rsa_rt/C++/TargetRTS\";\n
"},{"location":"building/transformation-configurations/#threads","title":"threads","text":"Specifies the threads used by the application. If no value is set for this property the application will have two default threads:
MainThread Runs all capsule instances in the application. It's implemented in the TargetRTS by the RTPeerController class.
TimerThread Runs all timers in the application. It's implemented in the TargetRTS by the RTTimerController class.
Both these threads will have a stack size of 20kB and run at a normal priority.
If your application needs a different thread configuration, or threads with different properties, you need to set the threads
property. Note that you cannot just specify threads in addition to the default ones mentioned above, but must always specify all threads. Here is an example where the default threads are present, plus one additional user-defined thread:
tc.threads = [\n{\n name: 'MainThread',\n implClass: 'RTPeerController',\n stackSize: '20000',\n priority: 'DEFAULT_MAIN_PRIORITY'\n},\n{\n name: 'TimerThread',\n implClass: 'RTTimerController',\n stackSize: '20000',\n priority: 'DEFAULT_TIMER_PRIORITY'\n},\n{\n name: 'MyThread',\n implClass: 'RTPeerController',\n stackSize: '20000',\n priority: 'DEFAULT_MAIN_PRIORITY',\n logical: [\n 'MyLogicalThread'\n ]\n}\n];\n
Note that for user-defined threads, like MyThread
above, you need to specify one or many logical threads that are mapped to it. These are references to threads that your application use instead of refering directly to a physical thread. This indirection makes it possible to change how capsule instances of your application are run by threads by only modifying the threads
property in the TC, without the need to change any application code.
Only executable TCs can define physical threads. A library TC can, however, define logical threads. An executable TC that has such a library TC as its prerequisite must map those logical threads to physical threads. Here is an example of a library TC that defines a logical thread.
tc.threads = [ `LibraryThread` ];\n
Note that in this case the threads
property contains a list of strings rather than a list of objects as is the case for an executable TC.
Read more about threads here.
"},{"location":"building/transformation-configurations/#topcapsule","title":"topCapsule","text":"Specifies the capsule that should be automatically incarnated when the executable starts to run. Hence this property is only applicable for TCs that build executables, and for those TCs it's a mandatory property. The top capsule is the entry point of the realtime application.
If you don't specify a value for this property, the TC will build a library instead of an executable.
"},{"location":"building/transformation-configurations/#unitname","title":"unitName","text":"Specifies the base name of the so called unit header and implementation files that are generated from the TC. By default the value of this property is UnitName
which means that these unit files will be called UnitName.cpp
and UnitName.h
. The unit files contain certain information that applies to the whole unit of code that is generated from a TC. The header unit file is included by all files that are generated from the TC.
This property is a list of user libraries that should be linked with the application. The property is only applicable for TCs that build executables.
tc.userLibraries = [\"../../libs/libMyLib.a\"];\n
Each library should be specified with a full or relative path so the linker can find it. If no path is provided you may need to provide a link argument to specify the location(s) where the linker should look for the user libraries.
"},{"location":"building/transformation-configurations/#userobjectfiles","title":"userObjectFiles","text":"This property is a list of user object files that should be linked with the application. The property is only applicable for TCs that build executables.
tc.userObjectFiles = [\"../../objs/extra.obj\"];\n
Each object file should be specified with a full or relative path so the linker can find it. If no path is provided you may need to provide a link argument to specify the location(s) where the linker should look for the object files.
"},{"location":"releases/","title":"Releases","text":"All releases of Code RealTime are available on the Visual Studio Marketplace and the Open VSX Registry.
Release notes can be found here.
"},{"location":"releases/CHANGELOG/","title":"1.0.4 (2024-06-26 10:50)","text":"MacT.AArch64-Clang-15.x
.RTJsonResult::keys_begin()
and RTJsonResult::keys_end()
which make it possible to iterate over the keys of a parsed JSON string. This makes the JSON parser useful also for scenarios where the expected keys are not statically known._Actor
when referencing a capsule from C++ code, for example when defining a capsule member function. This makes the code more readable and less typing is required. Note, however, that this change is not backwards compatible; existing references must be updated to remove the _Actor
suffix. Note also that the type descriptor object, which previously had the same name as the capsule, now has the prefix RTType_
. This prefix was already used for non-capsule types. If you have existing references to the type descriptor object of a capsule, for example in calls to RTFrame::incarnate()
, you therefore need to add the RTType_
prefix in those places.abstract
for marking capsules and classes as being abstract. This is an alternative to adding a pure virtual function to the capsule/class, and is better for scenarios where no pure virtual functions are needed, but the capsule/class should still be abstract (i.e. not possible to instantiate). Another benefit is that for an abstract capsule certain validation rules that check the correctness of state machines will not report a problem even if the capsule state machine is not correct. This makes it possible to create an abstract base capsule with an incomplete state machine which derived capsules can inherit from, reuse and specialize.rt::decl
code snippets of a capsule, looking for pure virtual functions. If at least one such function is found, without a matching overridden function, the capsule is implicitly abstract, and treated by the code generator as if it was declared with the abstract
keyword.rt::decl
code snippets of a capsule, looking for a user-defined destructor. If a user-defined destructor is present, the code generator doesn't generate the capsule destructor it otherwise generates. For an example, see this sample.RTActor
base class (such as _predestroy()
).notify
, publish
and subscribe
. Another improvement is that checkboxes for properties that are not applicable for a port with a predefined protocol (such as Timing
or Frame
) will now be hidden.Timing
or Frame
) is declared with properties that are not applicable for such a port (for example notify
or unwired
).$(CAPSULE_CLASS)
in the value for the capsuleFactory
TC property. It will be replaced with the name of the C++ class that is generated for the capsule. This can be useful for implementing a generic capsule factory which takes the capsule class as a template parameter.TCF.getTopTC()
function and the special eval
property. This is useful in order to allow the same library to be built as a prerequisite from multiple executables. For more information, see the documentation.executableName
and libraryName
.inclusionPath
property. The sources
TC property can be used to precisely control which C++ source files that should be included into the build. For more information, see the documentation.SUPERMETHOD
and CALLSUPER
for redefined transitions. They make it possible to call the effect or guard code of an inherited transition from a redefined transition, without having to place such code in a capsule member function. For an example, see this sample.rt::decl
code snippets of a capsule, looking for user-defined constructors. If at least one user-defined constructor is present, it doesn't generate the default capsule constructor it otherwise generates. Other parts of generated code were updated accordingly to no longer assume that a capsule has a default constructor. For an example, see this sample.sources
and prerequisites
TC properties by means of hyperlinks in the TC editor. When specified paths can be resolved to valid Art or TC files, they will become underlined, and can be opened by Ctrl+click.${workspaceFolder}
can now be used when specifying the prerequisites of a TC. This is often a better alternative than using a relative path since it allows the prerequisite TC to be located anywhere in the file system and still be found when resolving the prerequisite. As an example you can write ${workspaceFolder:MyLibrary}/lib.tcjs
where MyLibrary
is the name of the workspace folder where the prerequisite TC is located.-ws
which can be used for specifying the location of the workspace (in the form of a .code-workspace
file). It's optional to use this option, but it must be specified if built TCs contain workspace-relative paths.capsuleFactory
. This allows to globally control how capsule instances in an application are created and destroyed. One scenario where this is useful is when using the RTInjector
class from the TargetRTS to implement dependency injection for capsule creation. For an example, see this sample. Read more about these topics in new documentation chapters Capsule Factory and Dependency Injection.art_diagram_settings.json
.sources
property, is modified in the form-based TC editor, the TC file is automatically updated accordingly. Now the formatting of such TC properties is improved by placing each list element on a line of its own, and to add a trailing comma after the last element. This makes it easier to merge the TC file, without getting merge conflicts, in case more people are editing it in parallel.art_diagram_settings.json
) now uses an improved JSON format. A benefit with this new format is that it's now possible to move a workspace folder in the file system without invalidating the diagram settings. Another benefit is that each capsule part in a structure diagram can now be independently expanded, even for the case when there are multiple capsule parts typed by the same capsule.threads
property in a TC. For example, an attempt to map the same logical thread to different physical threads are reported as a problem. Previously, such problems were only detected when compiling or linking the application, or even at run-time.history*
). For an example, see this sample.const_rtdata
property. By setting this property to false
on a transition, the rtdata
parameter of the transition function will be non-const. This, for example, allows it to be moved instead of copied, which can be more efficient. For an example, see this sample.Delete
key or using the Delete command in the popup menu that appears when pressing Ctrl+Space.rt::create
and rt::destroy
code snippets. For an example, see this sample.long long
.rt::decl
code snippet you can tag it with a C++ attribute to tell the code generator how it should generate the type descriptor. Currently two attributes are supported: rt::auto_descriptor
(for fully automatic type descriptor generation) and rt::manual_descriptor
(when you prefer to write the type descriptor manually). Read more about this feature in the documentation.--buildConfig
and --buildVariants
. For an example, see this sample.rtdata
parameter in transition functions generated for non-triggered transitions. Previously this only worked for triggered transitions, and rtdata
in a non-triggered transition would become untyped (const void *). Now the code generator analyzes the whole transition chain to use the more specific type also for functions generated for non-triggered transitions. For an example, see this sample.rtistic.build.cancelOnError
can be used to control this behavior.rtistic.build.outputFolder
is now available. It can be used for setting a common output folder for generated code, and TCs where the targetFolder
specifies a relative path will now be resolved against that output folder. This new setting hence plays the same role for a UI build as the --out option does when building with the Art Compiler from the command-line.c_cpp_properties.json
file. This prevents references to definitions in prerequisite libraries to appear as broken in the UI when looking at generated code, and it also enables features such as Content Assist, hover tooltips etc for those definitions.*.art
) and doesn't match any anti-pattern (e.g. !exclude.art
). This property is not yet supported by the form-based TC editor, but can be set in the .tcjs file in order to exclude one or several Art files in the workspace folder where the TC is located.Delete
key. More than one such element can be deleted at the same time by using Ctrl+click for selecting multiple symbols or lines before pressing the Delete
key.topCapsule
property set an executable will be built. Otherwise a library will be built.rtistic.diagram.showDiagnostics
) controls if they should be shown or not.=
character. If there is not a fixed list of valid values, the expected value type (e.g. string) will be shown.Art Server
is now available and can be seen in the Output view. It's used by the Art language server for printing diagnostic messages (usually internal errors), and can be useful for troubleshooting problems.Art Build
is now available and can be seen in the Output view. It's used when building a TC. For example, messages are printed when a build starts and finishes.color
property. Currently this is supported for initial and triggered transitions.makeCommand
and makeArguments
.targetProject
and targetServicesLibrary
were renamed to targetFolder
and targetRTSLocation
respectively.The Target RunTime System (or TargetRTS for short) is a C++ library that is used by the code that is generated by Code RealTime. When building the realtime application from the generated code, it links with a TargetRTS library that has been prebuilt for the platform (hardware, operating system etc) on which the application will run.
The TargetRTS provides C++ implementations for the concepts of the Art language. The APIs of these implementations are used by the generated code, but also by the embedded C++ code that you write inside the Art files. This documentation serves the following purposes:
A realtime application runs in a target environment, which is comprised of all things that \"surround\" the application at run-time. Examples include the operating system (if any) and the target hardware. When building the TargetRTS, it's built for a particular target environment, and the resulting TargetRTS libraries become specific for that environment. The Code RealTime installation includes several TargetRTS libraries that have been prebuilt for common target environments that can be used right away. They have been created for popular combinations of C++ compilers for operating systems such as Windows, Linux, macOS and VxWorks. If none of those prebuilt TargetRTS libraries are sufficient, you can build your own.
Each way of building the TargetRTS for a specific target environment is described by means of a target configuration. It provides fixed values for parameters of the target environment, such as
These values can be put together to form a string which uniquely describes the target configuration in a compact way. Here is an example of such a string:
The first part of the name is the target which identifies the operating system name, version (if significant) and threading model. In the example above the operating system name is Windows. The version is not specified which means that the target configuration can work for more than one version of Windows. The letter 'T' shows that the application will be multi-threaded (for a single-threaded application the letter 'S' is used instead).
The second part of the name, which follows after the dot, is the libset which identifies the processor architecture and the compiler. In the example the processor architecture is x64 and the compiler is Microsoft Visual C++ version 17.0.
When you build your realtime application you need to specify the target configuration to use by means of the TC property targetConfiguration
. Valid values for this property is determined by the folder specified in the TC property targetRTSLocation
. If this property is not set, it defaults to the TargetRTS
folder inside the Code RealTime installation.
TargetRTS files are organized into subfolders under the TargetRTS
folder in the Code RealTime installation. If you set the targetRTSLocation
TC property it must point to a folder which follows the same structure.
In addition to the subfolders listed below, you may see folders with names build-<target_configuration>
. Those folders are output folders produced when building the TargetRTS. See this chapter for more information.
This folder contains various utilities in the form of Perl scripts and make file fragments. Not all of these are used in the latest version of Code RealTime but are provided for historic and backwards compatibility reasons.
"},{"location":"target-rts/#config","title":"config","text":"This folder contains a subfolder for each available target configuration. The contents of this folder defines valid values for the TC property targetConfiguration
.
The following files are present in each target configuration subfolder:
config.mk
This file is often empty, but can contain overrides of variables defined in the target or libset of the target configuration. See target and libset for the list of available variables that can be overridden.setup.pl
This file can set or override Perl variables used by the Perl script Build.pl
that is used when building the TargetRTS. Some of the variables have defaults set in Build.pl
which you only need to override if required. Others have no values set in Build.pl
and must be set in setup.pl
.This folder contains those TargetRTS header files that is used by generated code and are independent of a specific target. Target specific header files are present in the target folder.
This folder contains a file RTConfig.h
which defines a large set of C++ macros that can be used for configuring the TargetRTS. This file is included by all source files of the TargetRTS. For more information see Configure or Change the TargetRTS.
This folder contains the libraries that result from building the TargetRTS. There is one subfolder for each target configuration that has been built. Each subfolder contains two libraries (with prefixes and file extensions according to target-specific conventions):
ObjecTimeTypes
Contains the parts of the TargetRTS related to predefined types, encoding and decoding for those types, type descriptors etc. Also contains commonly used utilities such as synchronization primitives, code for TCP communication etc.ObjecTime
Contains everything else, for example implementations of the Art language concepts.In addition they also contain the main
object file which contains the implementation of the main function of the application (only used when building an executable).
This folder contains files related to the libset part of a target configuration, i.e. the processor architecture and the compiler. There is one subfolder for each libset. Each such subfolder have a file libset.mk
which defines make file variables that have a libset specific value. To avoid repeating variables that are often the same in different libsets, there is a file default.mk
which defines default values for many of these variables.
For example, the DEBUG_TAG
variable has the default value -g
, but is overridden to /Zi
for Microsoft Visual C++ libsets.
A libset subfolder may also contain a file RTLibSet.h
which can set libset specific C++ macros. It gets included from the main configuration file RTConfig.h
(see include).
This folder contains the TargetRTS source code (i.e. implementation files). It's only present in the Code RealTime Commercial Edition. The folder also contains some files needed when building the TargetRTS from its sources, such as Build.pl
.
The file MANIFEST.cpp
defines which source files that should be built. All TargetRTS source files are listed there under groups that decide which library each one should be placed in (see lib). Preprocessor conditions can be used for including or excluding some of the files depending on the target configuration that is built. For example, if a file uses a C++ 11 construct a condition must be specified that will exclude it when building a target configuration that uses a C++ 98 compiler.
The include
subfolder contains header files that are only used internally within the TargetRTS. Header files that are used by generated code are instead found in include.
The target
subfolder contains TargetRTS source code that is specific for a certain target. When you need to build the TargetRTS for a new target (e.g. a new operating system) you need to provide implementations for certain primitives which the TargetRTS uses from the operating system (e.g. to read the system clock, or create a new thread). The target/sample
subfolder contains template files that can help when doing this. For more information, see Creating a New Target Configuration.
This folder contains files related to the target part of a target configuration, i.e. the operating system, version (if significant) and threading model. There is one subfolder for each target. Each such subfolder have (at least) the following files:
target.mk
This file gets included in make files used both when compiling the TargetRTS and a realtime application that uses the TargetRTS. It may define or redefine any make file variable as required, but the following ones are typically target-specific: TARGETCCFLAGS
(compile options), TARGETLDFLAGS
(link options), TARGETLIBS
(link libraries). The values for these variables can use variables from libset.mk
(see libset).RTTarget.h
Defines target specific preprocessor macros, for example USE_THREADS
which is set to 1 for multi-threaded targets, and 0 for single-threaded targets. It gets included from the main configuration file RTConfig.h
(see include).This folder contains various utility scripts, some of which are used when building the TargetRTS.
"},{"location":"target-rts/build/","title":"Building, Debugging and Customizing","text":"Note
The information in this chapter is only applicable for the Commercial Edition of Code RealTime since it includes the source code for the TargetRTS. With the Community Edition comes only precompiled versions of the TargetRTS for a limited number of commonly used target configurations.
"},{"location":"target-rts/build/#build","title":"Build","text":"To build the TargetRTS you need to have Perl installed. On Linux and macOS Perl is usually already available, while on Windows you have to download and install it yourself. One way to get Perl on Windows is to use GitBash. See the Perl web page for other options.
Hint
As a user of Code RealTime Commercial Edition you also have access to Model RealTime which includes a version of Perl, called rtperl
. It can be found inside the plugin com.ibm.xtools.umldt.rt.core.tools
in the Model RealTime installation. If you want to use this version of Perl, locate the version under tools
that matches your operating system and add its folder to your PATH variable.
Follow these steps to build the TargetRTS from its sources:
extension/TargetRTS
into a folder. src
subfolder of the unzipped folder and invoke a command similar to the below:perl Build.pl WinT.x64-MinGw-12.2.0 make all\n
The Perl script Build.pl
drives the build process of the TargetRTS, but uses a make tool for the bulk of the work. The first argument to the script is the name of the target configuration to use. This is the same name as is specified with the TC property targetConfiguration
. The second argument to the script is the make tool to use. It corresponds to the TC property makeCommand
. The final argument is a make target defined in the file main.mk
. To build everything use the target all
.
The object files produced during the build will be placed in an output folder inside the TargetRTS
folder. The name of this folder is build-<target_configuration>
where <target_configuration>
is the name of the target configuration used. When all object files have been built, library files will be created from them and placed in a lib/<target_configuration>
sub folder. Any existing libraries in that folder, such as the precompiled versions of the TargetRTS, will be overwritten.
The Build.pl
script accepts an optional flag -flat
which, if used, should be the first argument. This flag causes the script to concatenate all source files that belong to the same class into a single file (placed in the output folder), and then build those concatenated file. This significantly reduces the number of source files to compile and therefore often speeds up the build. But beware that when debugging the TargetRTS, you will debug these concatenated files, rather than the original source code. Do not change the concatenated files as those changes will be lost the next time you build the TargetRTS with the -flat
flag.
To be able to debug the TargetRTS, you need to build it with debug symbols included. Follow these steps (either in an existing target configuration, or in a new one you have created):
libset.mk
in a text editor. This file is located in a subfolder under the libset
folder depending on what target configuration you are using. For example TargetRTS/libset/x64-MinGw-12.2.0/libset.mk
.LIBSETCCEXTRA
to include the flag $(DEBUG_TAG)
. This variable expands to the debug compilation flag of the compiler. You might also want to remove any specified optimization flags since they can make debugging harder.Build.pl
Perl script as mentioned above.You also need to build the generated code with debug symbols included. To do this set the compileArguments
property in the TC to include the $(DEBUG_TAG)
tag (and remove any optimization flags, if present).
Note
The Visual Studio compiler also requires a link argument /DEBUG
to be set to include debug symbols in an executable. Use the linkArguments
TC property to set it.
If you updated an existing target configuration and TC that were already built without debug symbols previously, make sure to do a clean build. The easiest way to do a clean build of the TargetRTS is to simply remove the entire output folder where the object files are placed (the output folder is named build-<target_configuration>
). To do a clean build of your application, perform the Clean command in the TC context menu, followed by Build.
There are many opportunities to customize the TargetRTS, and several good reasons to do so. Two common reasons are:
Examples when you need to create a new target configuration include:
Rather than creating a new target configuration from scratch, it's easier to start by copying an existing one. Pick a target configuration that resembles the one you like to create, and copy its subfolder under config
.
For example, if you want to create a new target configuration for using the latest version of the MinGw compiler on Windows, a good starting point is to copy config/WinT.x64-MinGw-12.2.0
since most things will be the same as in that target configuration. Also copy the libset folder libset/x64-MinGw-12.2.0
. Rename the copied folders for the new MinGw version and update any settings as required. For this scenario you don't need to create a new target since the existing target/WinT
can be used also for this new target configuration.
If your new target configuration is for a different operating system you also need to create a target for it. Create a subfolder under target
according to the naming conventions (see target configurations). The name of this subfolder must be used as the first part of your target configuration name (the text before the dot). Also create a new subfolder under src/target
where you can place source code that is target specific. The files in src/target/sample
can be used as a template for what code you need to write to integrate with the new operating system. Finally set the variable $target_base
in the file setup.pl
in your copied target configuration subfolder to the name of your new subfolder under src/target
. After this you can build your new target configuration.
Most configuration of the TargetRTS is done at the source code level using preprocessor macros. Each macro implements a specific configuration setting and can get its value from two files:
target/<target>/RTTarget.h
Settings specific for the target (e.g. operating system specific settings).libset/<libset>/RTLibSet.h
Settings specific for the libset (e.g. compiler specific settings).A setting can have a default value in include/RTConfig.h
which a setting in the above files can override. If needed, you can add your own macros for new configuration settings you need, but in most cases it's enough to change the value of existing settings. Below is a list of the most commonly used configuration settings. Those settings that control if a certain feature should be included in the TargetRTS have the value 1 when the feature is included, and 0 when it's not included.
Controls if the TargetRTS should use threads. Set to 0 for building a single-threaded version of the TargetRTS or 1 for building a multi-threaded version of it.
Default value: none (must be set for a target configuration, usually in target/<target>/RTTarget.h
)
Controls if the TargetRTS should keep track of statistics, such as the number of messages sent, the number of created capsules instances, etc. Collected statistics is saved per thread in the controller object, and can be printed by calling RTController::printStats()
. See RTCounts for what data that gets collected when this feature is enabled.
Default value: 0 (do not collect statistics)
"},{"location":"target-rts/build/#defer_in_actor","title":"DEFER_IN_ACTOR","text":"When a message is deferred it gets stored in a queue from where it later can be recalled. There can either be one such defer queue per capsule instance or only one defer queue per thread (i.e. stored in the controller object). Separate queues for each capsule instance will use more memory but can on the other hand result in better performance.
Default value: 0 (use one defer queue per thread). If your application doesn't use message deferral you should keep this default value.
"},{"location":"target-rts/build/#integer_postfix","title":"INTEGER_POSTFIX","text":"This is a deprecated setting that controls if the RTInteger class should support the increment (++) and decrement (--) operators. The setting is deprecated since use of RTDataObject subclasses for representing primitive types is deprecated. Use a primitive C++ type instead, such as int
.
Default value: 1 (set to 0 only if you use RTInteger and a very old C++ compiler)
"},{"location":"target-rts/build/#log_message","title":"LOG_MESSAGE","text":"By default a capsule has a function logMsg()
which gets called when a received message gets dispatched to a capsule instance, just before the message is handled by the capsule's state machine. This is a virtual function that you can override in your capsule to perform any general action needed when a message is dispatched (logging is a common, but not the only example). The default implementation is used by the debugger to log the dispatched message.
Default value: 1 (set to 0 if you don't need this feature and want to slightly improve the performance)
"},{"location":"target-rts/build/#object_decode-and-object_encode","title":"OBJECT_DECODE and OBJECT_ENCODE","text":"The TargetRTS contains features that can convert an object to a string representation (encoding) and create an object from a string representation (decoding). It can for example be used when persisting objects from memory to a file or database, when building distributed applications where data needs to be sent between processes or machines, or when using APIs of web services (often JSON).
Default value: 1 (set to 0 if you don't need this feature)
Note that encoding/decoding is controlled by two separate settings since it's possible that you need one but not the other.
"},{"location":"target-rts/build/#otrtsdebug","title":"OTRTSDEBUG","text":"This setting controls the level of debugging support that the TargetRTS will provide. There are 3 possible values:
Default value: DEBUG_VERBOSE
Note that regardless how you set this setting you can of course always build the TargetRTS with debug symbols to debug it with a C++ debugger.
"},{"location":"target-rts/build/#rtreal_included","title":"RTREAL_INCLUDED","text":"Controls if the RTReal class should be included or not. If your target environment doesn't support floating point data types, or your application doesn't use them, you can disable this feature.
Default value: 1
"},{"location":"target-rts/build/#purify","title":"PURIFY","text":"If you use tools for tracking run-time problems such as memory leaks or access violations, for example Purify, you can enable this feature. It will remove some optimizations in the TargetRTS which otherwise can hide some memory allocation and deallocation events from such tools.
Default value: 0
"},{"location":"target-rts/build/#rts_inlines","title":"RTS_INLINES","text":"Controls if the TargetRTS uses any inline functions. If enabled, inline functions of classes will be compiled with the class header file. If disabled, they will instead be compiled from a special file inline.cc
which most classes have.
Default value: 1 (set to 0 if you use an old compiler without proper support for inline functions)
"},{"location":"target-rts/build/#rts_compatible","title":"RTS_COMPATIBLE","text":"This setting is used for controlling backwards compatibility in the TargetRTS. It corresponds to the version number RT_VERSION_NUMBER
which is defined in the file RTVersion.h
.
Default value: 520. This is a very old version of the TargetRTS, but it means that by default certain deprecated code gets included to make the TargetRTS compatible for old applications that use it. If you set it to RT_VERSION_NUMBER
, then such deprecated code will not be included which will slightly reduce the size of the executable.
Controls if the TargetRTS can use TCP/IP. This is required for certain features such as debugging.
Default value: 1 (set to 0 if your target environment doesn't provide TCP/IP support)
"},{"location":"target-rts/build/#inline_methods","title":"INLINE_METHODS","text":"The member functions in a generated capsule class that contain user-defined code, such as transition or guard code, will be declared with this macro. You can set it to inline
if you want those function to be declared as inline functions. This may (or may not, depending on compiler) improve the performance, but may also lead to a larger executable.
Default value: none
"},{"location":"target-rts/build/#rtframe_checking","title":"RTFRAME_CHECKING","text":"This setting is used when you call functions on a Frame port, for example to destroy a capsule instance in a part. The recommendation is to declare Frame ports as non-service ports, so they only can be used internally by the owning capsule itself. However, if you somehow make a Frame port accessible for other capsules, they must at least be run by the same thread as the capsule that owns the Frame port.
The following values are possible for this setting:
Perform a run-time check that a Frame port is only used by the capsule that owns it.
Perform a run-time check that a Frame port is only used by code that runs in the same thread as the capsule that owns it.
Do not perform a run-time check. It improves the application performance slightly, and can be safely set if all Frame ports are only used by the capsule that owns them.
"},{"location":"target-rts/build/#rtframe_thread_safe","title":"RTFRAME_THREAD_SAFE","text":"This setting is used when you call functions on a Frame port, for example to create or destroy a capsule instance in a part. By default these functions are thread-safe, which is required when a created or destroyed capsule instance runs in a different thread than the code that calls the functions. However, in certain cases it's possible to optimize the performance by instead using function implementations that are not thread-safe. For example, if you know that you only use Frame ports to operate on capsule instances that run in the same thread as the capsules that owns the ports, then you can disable this setting to improve the application performance.
Default value: 1 (set to 0 if you know it's safe to not use thread-safe implementations of Frame functions)
"},{"location":"target-rts/build/#rtmessage_payload_size","title":"RTMESSAGE_PAYLOAD_SIZE","text":"This setting controls the size of the data area in each message. This data area is used for message data that is small enough, such as integers, booleans and short strings. If the data that is sent with a message is bigger than the specified RTMESSAGE_PAYLOAD_SIZE, then dynamically allocated memory is used for storing the message data outside of the message itself. See Message Data Area for more information about the message data area.
Default value: 100 (byte size of the message data area)
Increasing the value will make each message bigger, which makes the application consume more memory, but on the other hand it may become faster since fewer messages will require dynamic memory to be allocated for storing the message data. On the other hand, if your application mostly send very small data objects, you may benefit from decreasing the RTMESSAGE_PAYLOAD_SIZE. You need to fine-tune the value of this setting to find the optimal trade-off between speed and memory consumption for your application.
"},{"location":"target-rts/build/#observable","title":"OBSERVABLE","text":"This setting controls if the application will be \"observable\" at run-time. Target observability includes different kinds of features such as debugging, tracing etc. Disabling this setting will improve application performance and decrease memory consumption, but you will then not be able to use any of the target observability features.
Default value: 1 (set to 0 to disable all target observability features)
"},{"location":"target-rts/capsule-factory/","title":"Capsule Factory","text":"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 in a capsule part. These rules are:
new
operator and destroyed using the delete
operator.When you incarnate a capsule into an optional part using the incarnate() functions of a Frame port, you can customize rules 3, 4 and 5 above by using different overloads of incarnate()
. However, if you want to customize rules 1 and/or 2, or incarnate a fixed part, you need to provide your own capsule factory. This can be done in a few different ways.
You can provide a local capsule factory for a part by implementing the rt::create
and/or rt::destroy
code snippets. For an example see Part with Capsule Factory.
You can also provide a local capsule factory in a more dynamic way when you incarnate an optional part by calling incarnateCustom()
instead of incarnate()
. For an example see Capsule Constructor. However, note that in this case it's only possible to provide the create
implementation of the capsule factory, and capsule instances created that way will always be deleted using the delete
operator.
Scenarios where a local capsule factory could be useful include:
Example
You can find a sample application that uses a local capsule factory here.
"},{"location":"target-rts/capsule-factory/#global-capsule-factory","title":"Global Capsule Factory","text":"A global capsule factory will be used for creating and destroying capsule instances in all capsule parts in the application, except those for which a local capsule factory has been provided. Implement a global capsule factory by means of a class that inherits RTActorFactoryInterface
. You need to implement the create()
and destroy()
functions. Then define an object of this class and set the capsuleFactory
TC property to the address of that object.
Here is an example of how a global capsule factory can be implemented in an Art file called CapsuleFactory.art
:
[[rt::decl]]\n`\nclass CapsuleFactory : public RTActorFactoryInterface {\npublic: \n RTActor *create(RTController *rts, RTActorRef *ref, int index) override {\n\n // Create capsule instance here\n }\n\n void destroy(RTActor* actor) override {\n // Delete capsule instance here\n }\n\n static CapsuleFactory factory;\n};\n`\n\n[[rt::impl]]\n`\nCapsuleFactory CapsuleFactory::factory;\n`\n
In the TC, specify the capsule factory object and make sure the capsule factory header file gets included everywhere:
tc.capsuleFactory = \"&CapsuleFactory::factory\";\ntc.commonPreface = `\n#include \"CapsuleFactory.art.h\"\n`;\n
If the expression specified in the capsuleFactory
property contains the variable $(CAPSULE_CLASS)
it will be replaced with the name of the C++ class that is generated for the capsule. This can be useful for implementing a generic capsule factory which takes the capsule class as a template parameter.
Scenarios where a global capsule factory could be useful include:
Example
You can find a sample application that uses a global capsule factory here.
"},{"location":"target-rts/dependency-injection/","title":"Dependency Injection","text":"An object in an application has several \"dependencies\", i.e. various things that affect how it behaves at run-time. For a capsule part that gets incarnated with capsule instances at run-time, examples of such dependencies include which capsule to create an instance of, what data to pass to the capsule constructor, and which thread that should run the created capsule instance. But the behavior of a capsule instance also depends a lot on which other capsule instances it communicates with at run-time, so those are also examples of dependencies.
Dependency injection is a technique where run-time dependencies of objects are managed by a central injector object, instead of being hardcoded across the application. The injector is configured so it provides the desired dependencies for objects when they are needed at run-time. One benefit with using dependency injection is that objects in your application become more loosly coupled and it becomes much easier to configure and customize the behavior of your application.
There are many dependency injection frameworks for C++ which you can use for the passive C++ classes of your application. To use dependency injection for capsules, the TargetRTS provides a class RTInjector
. You can use this class for registering create-functions for capsule parts at application start-up. When the TargetRTS needs to incarnate a capsule part, it will check if a create-function is registered for it. If so, that create-function will be called for creating the capsule instance. Otherwise, the capsule instance will be created by the TargetRTS itself, as usual.
To use dependency injection in your realtime application you need to implement a global capsule factory and specify it in your TC. The create()
function of the global capsule factory delegates all calls to the RTInjector
singleton object.
You also must configure the injector by registering create-functions for all capsule parts where you want to customize how a capsule instance should be created. You need to do this early, typically at application start-up. At least it must be done before the TargetRTS attempts to create a capsule instance in a capsule part which you want to customize with dependency injection. A good place can be to do it in the constructor of the top capsule, in the main function of your application, or in the constructor of a static object (such as the global capsule factory object itself).
A create-function is registered by calling registerCreateFunction()
on RTInjector
. The second argument is the create-function, and the first argument is a string that specifies the path to the capsule part in the composite structure of the application. Such paths always start with a /
denoting the top capsule, and then follows names of capsules parts separated by /
. You can use a :
to specify the index of a capsule instance in a part with multiplicity. For example, if the application has this composite structure
then the path string /logSystem:0/logger
refers to the capsule part logger
that is contained in the LogSystem
capsule instance which is the first (index 0) capsule instance in the capsule part logSystem
of the top capsule Top
.
A call to registerCreateFunction()
to customize the incarnation of capsule instances in the logger
capsule part could then look like this:
RTInjector::getInstance().registerCreateFunction(\"/logSystem:0/logger\",\n [this](RTController * c, RTActorRef * a, int index) { \n return new TimestampLogger(c, a);\n }\n);\n
Example
You can find a sample application that uses dependency injection here.
In most cases your application will only register create-functions once at start-up. However, RTInjector
allows to do it at any time, and you can also remove or replace an already registered create-function. This makes it possible to implement very dynamic dependency injection scenarios. For example, you can change which capsule that gets instantiated depending on how much memory is currently available.
Dependency injection can for example be useful when implementing capsule unit testing in order to \"mock out\" capsules which the capsule-under-test depends on. In this case you could for example let the registration of create-functions be controlled by a configuration file that is part of the test case.
Another possibility is to combine dependency injection with Build Variants to build multiple variants of an application by means of high-level build settings (so called \"build variants\"). The build variant script can set compilation macros that control how injected create-functions behave.
"},{"location":"target-rts/encoding-decoding/","title":"Encoding and Decoding","text":"Encoding is the process of serializing data from memory into a string representation. Decoding is the opposite, i.e. deserializing a string representation of data into memory. There are many situations when it's useful to encode and/or decode:
The TargetRTS provides support for encoding and decoding data by means of the encode
and decode
functions of a type descriptor. This means that if you want to use encoding and/or decoding for a data type, you just need to make sure that it has a type descriptor with one or both of these functions implemented. The TargetRTS has a default implementation for encoding and decoding many types, including all predefined C++ types, structured types and enums. You can choose between three string formats:
If your data type is too complex for the default encode/decode implementation in the TargetRTS, or if you want to use some other format than the above two, you can write your own encode/decode functions. If you let your implementation inherit from the RTEncoding
and RTDecoding
interface classes, then the TargetRTS can seamlessly manage encoding and decoding also for your custom implementation.
If you don't need support for encoding and/or decoding in your application you should unset the macros OBJECT_ENCODE
and/or OBJECT_DECODE
both when building the TargetRTS and your application. This will reduce the application footprint.
Assume you have a data object stored in a variable data
of type T
, and the type descriptor of T
has support for encoding and decoding. The sample below will encode the object to ASCII and print it to stdout.
T data = new T(); // Object to encode\n\nchar buf[1000];\nRTMemoryOutBuffer buffer( buf, 1000 );\nRTAsciiEncoding coding( &buffer );\nRTType_T._encode_func(&RTType_T, &data, &coding);\nbuffer.write(\"\", 1); // IMPORTANT: Terminate the buffer string before printing it!\nstd::cout << \"ASCII encoding: \" << buf << std::endl << std::flush; \n
Here we call the static encode function of the type descriptor (_encode_func
) and provide the type descriptor object (RTType_T
), the object to encode (data
) and the ASCII coding object (coding
). If you instead prefer to use the JSON encoding, just change the type of the coding object from RTAsciiEncoding
to RTJsonEncoding
(see the example below).
To avoid the risk of overflowing a fixed-sized buffer, and get a slightly more compact code, you can use the RTDynamicStringOutBuffer
utility class. You can encode by calling put()
on the coding object:
RTDynamicStringOutBuffer buffer;\nRTJsonEncoding coding(&buffer);\ncoding.put(&data, &RTType_T);\nstd::cout << \"JSON encoding: \" << buffer.getString() << std::endl << std::flush; \n
RTEncoding::put()
produces a string that is prefixed with the type name. For JSON encoding it may look like this:
{T}{\"a\" : 5,\"b\" : true}\n
The {T}
prefix is needed if you later want to decode this string back to an object, but it has to be stripped off to get a string with valid JSON syntax. If you only are interested in getting the JSON encoding, without the type prefix, you can instead call RTEncoding::put_struct()
(assuming T
is a structured type).
Let's continue the above example and decode the string stored in buffer
back to an object of type T
:
RTMemoryInBuffer inBuffer(buffer.getString(), RTMemoryUtil::strlen(buffer.getString()));\nRTJsonDecoding decoding(&inBuffer);\nvoid* decodedObj;\nconst RTObject_class* type;\ndecoding.get(&decodedObj, &type);\nT* decodedData = reinterpret_cast<T*>(decodedObj);\n
Here we call RTJsonDecoding::get()
to perform the decoding. This function expects a string that is prefixed with the type name, and it will look-up the type descriptor object based on it (type
in the example). The decoded object is assigned to decodedObj
which is untyped (void*
). You can cast this pointer to the expected type (T
).
If your JSON string is not prefixed with the type name, you can instead call RTJsonDecoding::get_struct
which takes the type descriptor object as an argument.
decoding.get_struct(&decodedObj, &RTType_T);\nT* decodedData = reinterpret_cast<T*>(decodedObj);\n
Note that decoding will allocate and initialize a new object in memory. It's your responsibility to delete this object when you no longer need it. If you prefer to work with the decoded object as an untyped pointer, you can delete the object by calling the destroy function on the type descriptor like this:
type->destroy(decodedObj, RTObject_class::DestroyAndDeallocate);\n
"},{"location":"target-rts/encoding-decoding/#encoding-a-message","title":"Encoding a Message","text":"The JSON encoder has a special function put_msg
which can be used for encoding a received RTMessage
to JSON. It can for example be useful as a way to trace received messages in a standard format which other tools can read and use. Here is an example of how it can be used in a code snippet within a capsule (e.g. a transition):
RTDynamicStringOutBuffer buf;\nRTJsonEncoding coding(&buf);\ncoding.put_msg(msg);\ncout << \"Received msg: \" << buf.getString() << endl << flush;\n
The encoding includes the name of the message's event, its argument data type (if any) and the data object itself (if any). Here is an example of what it may look like:
{\n \"event\" : \"event_with_class\",\n \"type\" : \"MyClass\",\n \"data\" : {\"a\" : 8, \"b\" : false}\n}\n
"},{"location":"target-rts/encoding-decoding/#default-encodingdecoding-rules","title":"Default Encoding/Decoding Rules","text":"The default encoding/decoding in the TargetRTS follows these rules:
If you wish to change any of these rules, you can customize encoding/decoding.
"},{"location":"target-rts/encoding-decoding/#custom-encodingdecoding","title":"Custom Encoding/Decoding","text":"If you want to customize how a certain type gets encoded/decoded, you can write a custom encode and/or decode function for its type descriptor. Note that you can also define a typedef or type alias of an existing type, if you only want to change the encoding/decoding for some objects typed by that type (i.e. then change the type of those objects to your typedef or type alias instead). See this chapter for more information and examples.
If you want to encode/decode using a different format, such as another textual format or even a binary format, you need to write your own encoder and/or decoder. If possible you should let your implementation inherit from the RTEncoding
and RTDecoding
classes. That allows the TargetRTS to work seamlessly with your implementation from encode/decode functions of a type descriptor.
You can also let your encoder and/or decoder class inherit from the classes that implement ASCII and JSON encoding/decoding by overriding some of their virtual functions. This can be useful if you just want to slightly customize the ASCII or JSON encoding/decoding. For example, assume you want to change the JSON encoding to encode boolean data as strings. Then you can define your custom encoder class like below:
#include <RTJsonEncoding.h>\n\nclass CustomJsonEncoding : public RTJsonEncoding {\n public:\n CustomJsonEncoding(RTOBuffer * buffer)\n : RTJsonEncoding(buffer) {}\n\n virtual int put_bool(bool value) {\n if (output->write(\"\\\"\", 1 ) != 1)\n return 0;\n int res = RTJsonEncoding::put_bool(value);\n if (output->write(\"\\\"\", 1 ) != 1)\n return 0;\n return res;\n }\n};\n
"},{"location":"target-rts/encoding-decoding/#json-parser","title":"JSON Parser","text":"The JSON Decoder has to parse a JSON string before it can create an object representation of it in memory. However, it only needs to support parsing a subset of JSON, namely the subset of JSON which can be produced by the JSON Encoder. Because of this it doesn't need to use a general-purpose JSON parser.
There are, however, scenarios where you may need to parse JSON, not for the purpose of decoding it, but for some other reason. For example, you may get JSON as the result of making an API call, and then need to parse the JSON to more easily extract the relevant information from it. To support this scenario the TargetRTS includes a general-purpose JSON parser implemented in RTJsonParser
.
You parse a JSON string by calling RTJsonParser::parseJsonString()
. The parser result is represented by an object of RTJsonResult
. On this object you can call functions to
get_type()
)operator[const std::string&]
)operator[size_t]
)Values are also represented by RTJsonResult
and you can check their type (either null
, JSON object, array, string, number or boolean). For values with types that correspond to C++ primitive types you can call \"get_\" functions (e.g. get_bool()
to get a C++ bool from a JSON boolean value). Do not forget to first check the type of the value, because if you try to convert to the wrong kind of value, the result may be unexpected. Often it's more convenient to use one of the operator==
functions to directly compare a JSON value with the corresponding C++ value.
Here is an example of how to parse a JSON string and check the result:
RTJsonParser parser;\nRTJsonResult result;\nbool ok = parser.parseJsonString(result, \"{\\\"field1\\\" : \\\"string\\\", \\\"arr\\\" : [1,true,3.14]}\");\n\nstd::cout << result[\"field1\"].get_string() << std::endl; // \"string\"\nif (result[\"arr\"].ok()) { // Check if the \"arr\" key is present\n std::cout << result[\"arr\"].get_size() << std::endl; // 3\n if (result[\"arr\"][2] == 3.14) {} // will be true\n if (result[\"arr\"][1].get_type() == RTJsonResult::RTJSON_BOOL) {} // will be true\n}\n
Example
You can find a sample application that uses the JSON parser here.
"},{"location":"target-rts/integrate-with-external-code/","title":"Integration with External Code","text":"In most cases a realtime application contains at least some code that is not generated by Code RealTime. The application can use libraries, both 3rd party libraries and libraries you have created yourself. It can also contain other C++ code which is built together with generated code, either from within Code RealTime or from another IDE or build system. In this chapter we'll look at some techniques and best practises for integrating the code generated by Code RealTime with other code, which we here refer to as external code.
"},{"location":"target-rts/integrate-with-external-code/#external-port","title":"External Port","text":"External code can send events to a capsule in a thread-safe way by means of an external port defined on the capsule. Such a port is typed by the predefined External
protocol.
External ports can for example be useful in scenarios when external code has to wait for some event to occur or some data to become available, and then notify a capsule about it. Such external code has to run in its own thread (an external thread), to avoid blocking the application while it's waiting. In some cases it's enough to just notify the capsule about what has happened, while in other cases it's necessary to also transfer some data from the external code to the capsule. Here are examples for three typical scenarios:
float
as event data.All these types of scenarios are supported by means of external ports.
Example
You can find a sample application that uses an external port here.
"},{"location":"target-rts/integrate-with-external-code/#general-usage","title":"General Usage","text":"The capsule that owns an external port has full control over when it's ready to accept an event on the external port. This is important to prevent an external thread from \"starving\" the thread that runs the capsule. The following rules apply for how an external port must be used:
enable()
on the port. It's important that this call happens from the same thread that runs the capsule. If the port is already enabled, nothing happens and it stays enabled.raise()
on the port, and it's important that this call happens from the external thread (i.e. the capsule itself should not call it). Once an event has been raised on the external port, it automatically becomes disabled and has to be enabled by the capsule again before another event can be raised on it.disable()
on the port. If the port is already disabled, nothing happens and the port stays disabled.The state machine below shows these rules graphically:
The external code can use the return value of raise()
to know if the event was successfully raised on the port or not. It should never assume that raising the event will succeed, because it cannot know if the capsule has enabled the external port or not. If raising the event fails, the external code can choose to try again a little later, or skip it altogether. How to best handle this situation depends on the application and how important it is for the capsule to be notified about the event.
Here is code which can be called from the external thread to attempt to raise an event on an external port extPort
:
if (extPort.raise() == 0){\n // failure (possibly try again a little later)\n}\nelse {\n // success\n}\n
Note
To allow the external code to call raise()
on the external port it's necessary to somehow provide the external code with a way to access it. A good way to do this is to let the capsule implement an interface class which provides a function for raising the event on the external port. The external code can then be given a reference to the capsule through that interface class. This prevents the need to expose the external port itself to the external code, and it effectively ensures that the external code cannot do anything with the capsule except calling the provided function. See this sample for an example of how to let a capsule implement an interface class.
The capsule with the external port handles the raised event like any other event it receives (it has the name event
). Here is an example of a PushButton capsule that uses an external port for getting notified when a button is pushed:
capsule PushButton {\n\n behavior port external : External; \n\n statemachine {\n state WaitForPush {\n entry\n `\n // Enable the external port so we can receive *one* event on it\n external.enable();\n `;\n };\n initial -> WaitForPush;\n\n onButtonPush: WaitForPush -> WaitForPush on external.event\n `\n // The button was pushed\n `;\n };\n};\n
"},{"location":"target-rts/integrate-with-external-code/#passing-data","title":"Passing Data","text":"The external code can choose to pass a data argument with the event that it raises on an external port. The same rules apply for such data as for all other event data, except that it will always be copied (i.e. it's not possible to move it). Just like when data is associated with a timeout event, it's necessary to provide both the data and its type descriptor when raising an event with data. Here is an example where the raised event carries a string as data:
char* str = \"external data\";\nextPort.raise(&str, &RTType_RTpchar);\n
As with all other events, the capsule receives the passed data through the rtdata
argument in the transition that is triggered on the event called event
of the External
protocol. For example, to receive the string data used above:
RTpchar d = *((RTpchar*) rtdata);\nstd::cout << \"Received data on external port: \" << d << std::endl;\n
Sometimes data may become available in the external thread at a higher pace than what the capsule can (or want to) handle. In that case it\u2019s not convenient to pass the data to the capsule thread in the call of raise()
. Instead, the external code can call dataPushBack()
to push the data into a data area on the external port itself. The external code can push any number of data objects on the external port. The capsule can access the data, when it's ready to do so, by calling either dataPopFront()
or dataPopBack
depending on if it wants to handle the data in a \"first-in-first-out\" (FIFO) or \"last-in-first-out\" (LIFO) manner. The external code can call raise()
to notify the capsule that data is available for it to fetch. It can either do this as soon as it has pushed any data on the external port, or wait until a certain number of data objects have been pushed.
Here is an example of external code for pushing a data object consisting of a string and an integer on the external port:
std::pair<std::string,int>* data = new std::pair<std::string,int>(\"external data\", 15);\nextPort.dataPushBack(data);\n
And here is how the capsule can choose to fetch the data in a FIFO manner:
unsigned int remaining;\ndo {\n std::pair<std::string,int>* data;\n remaining = extPort.dataPopFront((void**) &data);\n if (data == 0)\n break;\n // Handle received external data here...\n delete data;\n}\nwhile (remaining > 0);\n
Note that the external code is responsible for allocating the data pushed on the external port, while the capsule is responsible for deleting the data once it has fetched it. The capsule can choose whether it wants to fetch all available data at once (as in the above example), or only some of it. However, it's important to design the application so that external port data doesn't just keep growing as that eventually would cause the application to run out of memory.
It's of course possible to implement another scheme for passing data from external code to a capsule, and the data area of an external port should just be seen as a convenience. Any data structure can be shared between the external code and the capsule, but it's important that it is thread-safe. You can for example use a mutex (see RTMutex
in the TargetRTS) for protecting data that is shared by the external thread and the capsule's thread.
The TargetRTS contains a main()
function implementation which is used when you build an executable. However, its implementation simply calls RTMain::entryPoint()
which is provided by the generated code in the unit file (by default called UnitName.cpp
). If you want to integrate the generated code with external code that already contains a main()
function you can either
The generated implementation of RTMain::entryPoint()
performs certain set-up activities such as defining the RTSystemDescriptor
which among other things contains a reference to the type descriptor of the top capsule. It then calls RTMain::execute()
which will start-up the TargetRTS and create and run an instance of the top capsule. This means that when you build an executable, the top capsule instance will always be run by the main thread. But if you instead provide the main()
function in the external code, you can create a different thread and call RTMain::entryPoint()
from that thread, and then the top capsule instance will be executed by that thread.
One example where you typically cannot let the main thread run the top capsule instance, is when the application has a user interface. This is because the main thread then already is busy running the event loop that manages the user interface. In this case you can create a separate thread responsible for running the realtime part of your application (i.e. the top capsule instance).
Example
You can find a sample application where a user interface is integrated with a realtime application here.
"},{"location":"target-rts/message-communication/","title":"Message Communication","text":"Applications developed with Code RealTime consist of objects with state machines that communicate by means of messages. This chapter describes the details of how this message-based communication is implemented in the TargetRTS.
"},{"location":"target-rts/message-communication/#controllers-and-message-queues","title":"Controllers and Message Queues","text":"As explained in Threads, an application consists of controllers each of which is run by a physical thread and is managing a group of capsule instances. The main responsibility of a controller is to facilitate the exchange of messages from one capsule instance to another. There are two kinds of message exchange:
From the application\u2019s perspective, there is no difference between sending a message within a thread and sending a message across threads; the code to send and receive the message is still the same. There is, however, a difference in performance, and message sending across threads is approximately 10-20 times slower than message sending within a thread. You should therefore assign capsule instances to controllers in a way so that those that communicate frequently with each other should be run by the same controller.
Each physical thread in the TC of an application specifies an implementation class that inherits from RTController. The default implementation is provided by the RTPeerController class, and it implements a simple event loop that in each iteration delivers the most prioritized message to the capsule instance that should handle it. Let's explore what happens internally in the TargetRTS when a capsule sends an event on a port:
myPort.myEvent().send();\n
The message is now delivered to the controller that runs the receiver capsule instance. This is done by calling RTController::receive()
. The controller has two message queues where it stores received messages, the internal queue and the incoming queue. The received message is put in one of these:
Note that both the internal and incoming message queue is actually an array of queues, one for each message priority level. The received message is inserted in the end of the queue that matches the priority of the message as specified by the sender. This ensures that messages are handled in priority order, and, within each level of priority, in a FIFO (\"first-in-first-out\") manner.
If the message was placed in the incoming queue, it gets transferred to the internal queue in the beginning of the RTController::dispatch()
function which is called once in each iteration of the controller's event loop. This happens in the function RTController::acceptIncoming()
.
RTController::dispatch()
function checks the contents of the incoming queue, starting with the queue at the highest priority level (Synchronous
), proceeding with queues at lower priority levels, until the queue at the lowest priority level (Background
). As soon as it encounters a non-empty queue it dispatches the first message of that queue. Dispatching a message is done by calling RTMessage::deliver()
, which eventually leads to a call of the RTActor::rtsBehavior()
function which implements the capsule state machine in the generated code.
Note
The control is not returned to the TargetRTS until the transition which is triggered by the received message has run to completion. This includes the triggered transition itself, and also any number of non-triggered transitions that may follow it. It also includes entry and exit actions for states. It may also involve the call of one or several guard functions that are required for determining which transition that should be triggered. Hence, the dispatching of a single message may lead to execution of several code snippets. Before they have all executed it's not possible for the controller to dispatch another message, and it's therefore important that the code that runs operates as efficiently as possible. A long-running operation should be performed by a capsule instance that runs in a different controller, to avoid blocking the execution of other capsule instances.
Finally, when the message has been dispatched, it's freed (see Message Memory Management).
From a code snippet of a capsule, such as a transition effect or guard code, you can get the message that was most recently dispatched to the capsule by accessing RTActor::msg
. You should treat this message object, and all data it contains, as read-only. Since it will be freed when control returns to the TargetRTS after the message has been dispatched, it's not safe to store any pointers to the message or the data it contains and access these at a later time. All data from the message object that you need to keep should be copied. However, if the data is big it's possible to instead move it by setting the property const_rtdata on a transition.
The picture below illustrates a controller and how messages arriving from capsule instances are placed in the incoming or the internal queue, depending on if those capsule instances run in the same or a different controller. It also shows how these queues actually are arrays of queues organized according to message priority.
"},{"location":"target-rts/message-communication/#message-priority","title":"Message Priority","text":"The sender of a message can choose between the following priority levels:
In addition to these five priority levels, there are two system-level priorities which are higher than all the above; System and Synchronous. These are used internally by the TargetRTS and cannot be used when sending user-defined messages.
As explained above, each priority level has its own message queue in the controller, and the controller looks for messages to dispatch starting from the queue with the highest priority. As soon as a message is found, it gets dispatched, and no more messages are dispatched in that iteration of the event loop. This means that if a large number of high priority messages are continously sent, they will prevent (or at least delay) dispatching of low priority events in the same controller. It's therefore best to stick to the default message priority for most messages, and only use higher and lower priority messages when really needed.
"},{"location":"target-rts/message-communication/#message-representation","title":"Message Representation","text":"A message is an instance of a protocol event and is represented by an object of the RTMessage class. It stores the following information:
signal
). This is a numerical id that uniquely identifies the event for which the message was created within its protocol. void*
) but can safely be casted to a pointer typed by the parameter of the protocol event. In most cases such casts happen automatically in generated code, so that you can access correctly typed data in a transition function. However, if you get the data from the message object by calling RTMessage::getData()
you need to cast it yourself. See Message Data Area for more information about how the message data is stored.RTMessage::getType()
.RTMessage::getPriority()
.RTMessage::sap()
.RTMessage::receiver()
.As mentioned above you should treat a message object, and all data it contains, as read-only and owned by the TargetRTS. The data that is passed with the message object is either a copy of the data provided by the sender or was moved from it. This avoids the risk that both the sender and receiver, which may run in different threads, access the same data object simulatenously.
Important
When you develop an application don't make assumptions about how many times the data object will be copied. In many cases it will only be copied once, but there are situations when multiple copies will need to be created. It is therefore important that any event parameter has a type descriptor where the copy function (e.g. a copy constructor) is able to copy the data object multiple times. Note that copying of the message object may happen even after it has been dispatched. The receiver must therefore not change it in a way that will prevent it from later being copied correctly by the TargetRTS.
"},{"location":"target-rts/message-communication/#message-data-area","title":"Message Data Area","text":"Messages that carry data can either store that data inside the RTMessage object, in the _data_area
member variable, or on the system heap. A TargetRTS configuration macro RTMESSAGE_PAYLOAD_SIZE
defines the byte limit which decides where a data object will be stored. Data that is smaller than this limit is stored inside the RTMessage object, while bigger data is stored on the system heap. The picture below shows two message objects, one where the data object is small enough to fit in the _data_area
and another where it's too big to fit there and therefore is instead allocated on the system heap.
As a user you don't need to think about where the message data is placed, because it's still accessed in the same way. However, to obtain optimal performance for the application it's necessary to fine-tune RTMESSAGE_PAYLOAD_SIZE
to find the best trade-off between application memory usage and speed.
When a capsule instance wants to send a message, its controller first needs to get a new RTMessage object. The controller gets it by calling RTController::newMsg()
. The obtained message object will then be given to the controller which manages the receiver capsule instance (which in case of intra-thread communication will be the same controller). That controller inserts the message object into an event queue according to the message priority. When the message has been dispatched to the receiver, the message object is no longer needed and the controller then frees it by calling RTController::freeMsg()
.
Dynamically allocating and deallocating memory for each individual message object would impact negatively on application performance. The newMsg()
and freeMsg()
functions therefore use a free list which is a pool of message objects that are \"free\" to use (meaning that they are currently not involved in any communication). newMsg()
obtains the first message of the free list, and freeMsg()
returns that message to the beginning of the free list.
There is a single free list in the application and it's implemented by the RTResourceMgr class. At application start-up, when the first message of the application needs to be sent, a block of messages (see RTMessageBlock) is allocated from the heap and added to the free list. Subsequent requests to get a message object can therefore be processed quickly, without the need to allocate memory.
If the application needs to send a large number of messages at a faster pace than they can be dispatched, it can happen that the free list becomes empty. In that case another block of messages get allocated.
The size and behavior of the free list is controlled by a few constants in the TargetRTS implementation:
RTController::freeMsg()
. If the size of the free list exceeds maxFreeListSize a number of messages are freed to reduce the size of the free list to minFreeListSize. Hence, the size of the free list is always kept in the range defined by these two constants.Note that freeing a message doesn't deallocate its memory. Instead it is reset by calling RTMessage::clear()
so it becomes ready to be used again. This means that the memory occupied by the free list will grow until a certain limit when it's big enough to always contain a free message when the application needs one. That limit is different for different applications, and if you want to avoid dynamic allocation of additional message blocks after application start-up, you may need to adjust the RTMessageBlock::Size
constant.
Hint
You can compile the TargetRTS with the RTS_COUNT
flag set to collect run-time statistics for your application. Among other things it counts the peek number of messages allocated by each controller. This information can help you configure the free list to have a size that is appropriate for your application.
Example
You can find a sample application that shows how to collect and print statistics here
"},{"location":"target-rts/threads/","title":"Threads","text":"An Art application consists of capsule instances that each manage a state machine and communicates with other capsule instances by sending and receiving events. Conceptually we can think about each capsule instance as run by its own thread.
However, in practise it's often necessary to let each thread run more than one capsule instance. The number of capsule instances in an application can be higher than the maximum number of threads the operating system allows per process. And even if that is not the case, having too many threads can consume too much memory and lead to unwanted overhead.
When creating a new Art application it's recommended to start with a minimal number of threads, perhaps only the main thread initially. During the design work you will then add new threads when you identify capsules that need to perform long-running tasks. Such a capsule should not run in the main thread since during the long-running task all other capsules run by that thread will be unresponsive (i.e. cannot respond to incoming events).
Another input to which threads to use is how capsule instances communicate with each other. Those capsule instances that communicate frequently with each other benefit from being run by the same thread since sending an event within the same thread is faster than sending it across threads.
Example
You can find a sample application that uses threads here.
"},{"location":"target-rts/threads/#physical-and-logical-threads","title":"Physical and Logical Threads","text":"Code RealTime makes a difference between physical and logical threads. Physical threads are the real threads that exist in the application at run-time. A logical thread is a conceptual thread which application code uses when it needs to refer to a thread. Hence, it is an indirection which prevents hard-coding the application against certain physical threads.
Logical and physical threads are defined in the transformation configuration (TC) using the threads property. Each logical thread is mapped to a physical thread. Having all information about threads in the TC has several benefits:
To ensure that each logical thread is mapped to a physical thread, the logical threads are defined implicitly when they are mapped to a physical thread. Here is an example where there are two physical threads MainThread
and PT1
, and three logical threads L1
, L2
and L3
. The logical threads L1
and L2
are both mapped to the MainThread
while L3
is mapped to PT1
.
tc.threads = [\n{\n name: 'MainThread',\n implClass: 'RTPeerController',\n stackSize: '20000',\n priority: 'DEFAULT_MAIN_PRIORITY',\n logical: [\n 'L1', 'L2'\n ]\n},\n{\n name: 'PT1',\n implClass: 'RTPeerController',\n stackSize: '20000',\n priority: 'DEFAULT_MAIN_PRIORITY',\n logical: [\n 'L3'\n ]\n}\n];\n
Take care to map a logical thread to exactly one physical thread.
"},{"location":"target-rts/threads/#library-threads","title":"Library Threads","text":"Physical threads can only be defined in executable TCs. A library TC can, however, define logical threads. An executable TC that has such a library TC as its prerequisite must map those logical threads to physical threads. Here is an example of a library TC that defines a logical thread. Note that in this case the threads
property contains a list of strings rather than a list of objects as is the case for an executable TC.
tc.threads = [ 'LibraryThread' ];\n
If you anyway define physical threads for a library TC they will be ignored by the C++ code generator, and only the logical threads will be considered.
"},{"location":"target-rts/threads/#running-a-capsule-instance-in-a-custom-thread","title":"Running a Capsule Instance in a Custom Thread","text":"Capsule instances are connected in a tree structure where the top capsule instance is the root. A capsule instance always lives inside a part of another (container) capsule. The top capsule instance is always run by the main thread, but for all other capsule instances you can choose which thread that should run it.
When a new capsule instance is created it will by default be run by the same thread that runs the container capsule instance. This means that by default all capsule instances in the application will be run by the main thread.
The picture below outlines the capsule instances of an Art application. C1
is the top capsule. For simplicity we have assumed that all capsule parts are fixed with multiplicity 1 so they only can contain one capsule instance.
The capsule instances contained in cp1
and fp1
are run by the logical thread Logical1
while the capsule instances contained in dp1
, cp2
and ep2
are run by the logical thread Logical2
. Other capsule instances are run by the main thread. Note that to accomplish that we need to explicitly reference the MainThread
when incarnating ep1
since by default it would be run by the thread that runs its container capsule, i.e. Logical2
. In fact we need to explicitly mention a logical thread for all capsule instances in this example except ep2
since it runs in the same logical thread as its container capsule instance cp2
.
If you don't want a capsule instance to be run by the same thread that runs its container capsule you can specify another thread when creating the capsule instance. When incarnating a capsule instance into an optional part this can be done in a call to incarnate()
on a Frame port. Here is an example:
frame.incarnate(myPart, nullptr /* data */, nullptr /* type */, LogicalThread, -1);\n
Here LogicalThread
refers to a logical thread that must exist in the TC. The physical thread to which it is mapped will run the created capsule instance.
If the part is fixed you need to use a capsule factory for specifying the thread that should run a capsule instance that is incarnated into the part. For example:
fixed part server : Server [[rt::create]]\n`\n return new Server(LogicalThread, rtg_ref);\n`;\n
"},{"location":"target-rts/threads/#targetrts-implementation","title":"TargetRTS Implementation","text":"The implClass
property of a physical thread that is defined in a TC refers to the class in the TargetRTS that implements the thread. This class must inherit from RTController. A default implementation is provided by the RTPeerController. It implements a simple event loop that in each iteration delivers the most prioritized message to the capsule instance that should handle it.
You can implement your own controller class by creating another subclass of RTController. As an example, look at RTCustomController.
See Message Communication for more details about how controllers work.
If the application uses timers it needs a timer thread for implementing the timeouts. The TargetRTS provides a default implementation RTTimerController which implements basic support for processing timeout events and timer cancellation.
"},{"location":"target-rts/threads/#default-threads-and-thread-properties","title":"Default Threads and Thread Properties","text":"If no threads are specified in the TC the application will use two threads; one main thread that runs all capsule instances and one timer thread that implements support for timers as explained in the documentation of the threads property. If your application is single-threaded and doesn't use timers, it's unnecessary to have a timer thread and you can then remove it by only defining the MainThread in the threads property:
tc.threads = [\n{\n name: 'MainThread',\n implClass: 'RTPeerController',\n stackSize: '20000',\n priority: 'DEFAULT_MAIN_PRIORITY'\n}\n];\n
A thread object defines a physical thread by means of the following properties:
name The name of the thread. It's recommended to choose a name that describes what the thread is doing. Many C++ debuggers can show the thread name while debugging, and you can also access it programmatically by calling the RTController::name() function. Note that names of physical threads in the application must be unique.
implClass This is the name of the TargetRTS class that implements the thread. See TargetRTS Implementation. If omitted it will default to RTPeerController
.
stackSize The thread stack size in bytes. This value is interpreted by the target environment, and some operating systems may have special values (such as 0) that can be used to avoid hard-coding a certain stack size. If omitted it will default to 20000
.
priority The thread priority. By default it's DEFAULT_MAIN_PRIORITY
(or DEFAULT_TIMER_PRIORITY
for a timer thread). These are macros with values that are interpreted by the target environment.
logical A list of names of logical threads that are mapped to the physical thread. Logical threads must have unique names and each logical thread must only be mapped to one physical thread. Except for the main thread and timer threads this property should not be empty, since it's through the logical threads that the application code can use the physical thread.
Thread information specified in the TC is generated into the unit files (by default called UnitName.h
and UnitName.cpp
). You will find there functions _rtg_createThreads()
and rtg_deleteThreads()
which contain the code for creating and deleting the physical threads that you have added in addition to the default MainThread and TimerThread. There is also a function _rtg_mapLogicalThreads()
where the logical threads are mapped to physical threads.
Some target environments only support one thread. In this case the macro USE_THREADS
will be unset when compiling generated C++ code and the TargetRTS, and it will remove all code related to threads.
Capsules can use timers to get notified when some time has passed. A timer is implemented by means of a port typed by the predefined Timing
protocol.
Note
A timer port should always be a non-service behavior port. This is checked by the validation rule ART_0035.
Example
You can find sample applications that use timers here:
When you set a timer you specify the time when it should timeout. At that time the capsule will receive the event timeout
on the timer port, and it can trigger a transition in the capsule state machine that handles the timeout.
There are three ways to set a timer:
informAt
you set a one-shot timer that will timeout once, at a specific point in time (absolute time).informIn
you set a one-shot timer that will timeout once, when a certain time has passed (relative time). informEvery
you set a periodic timer that will timeout repeatedly at certain intervals (relative time).The same timer port can be set in any of these ways, and you can \"reuse\" the timer by setting it again when it has timed out. If you set the same timer multiple times, before it has timed out, you will get multiple timeout events (one for each time the timer was set).
Note
While it's possible to implement a periodic timer by re-setting a one-shot timer each time it times out, it's not recommended to do so. You will get a higher precision by using a proper periodic timer. This is because it takes some time to set the timer which may add to some drift in the timeouts.
The time specified for a one-shot timer, or interval for a periodic timer, can be specified in three ways:
std::chrono::duration
(for relative time) or std::chrono::time_point
(for absolute time). You need to include the <chrono>
header file and use a C++ 11 compiler.<chrono>
header file and use a C++ 14 compiler.All functions that set a timer return an RTTimerNode*
, and in case the timer could not be set nullptr
is returned. It's good practise to always check this return value, to ensure the timer was successfully set. If you later need to operate on the timer (for example to cancel it) you should construct an RTTimerId
object from the RTTimerNode*
. You can then call isValid()
on that object to make sure the timer was successfully set.
The example below shows some different ways to set timers and to handle the timeouts:
capsule Timers {\n behavior port timer1 : Timing, timer2 : Timing, timer3 : Timing; \n\n statemachine {\n state S {\n timeout : on timer1.timeout, timer2.timeout, timer3.timeout\n `\n // TODO: Handle timeouts here\n `;\n };\n\n initial -> S\n `\n RTTimerId tid1 = timer1.informIn(RTTimespec(2, 0)); // one-shot timer to time out in 2 s\n if (!tid1.isValid()) {\n // timer1 could not be set\n }\n\n std::chrono::system_clock::time_point t = std::chrono::system_clock::now() + std::chrono::milliseconds(50);\n RTTimerNode* t2 = timer2.informAt(t); // one-shot timer to timeout in 50 ms from now\n if (!t2) {\n // timer2 could not be set\n }\n\n RTTimerId tid3 = timer3.informEvery(800ms); // periodic timer to timeout every 800 ms\n if (!tid3.isValid()) {\n // timer3 could not be set\n }\n `;\n }\n}\n
If you set a timer with an absolute time that has already passed, or a relative time of 0, the timeout will happen almost immediately. Note the word \"almost\", because in practise it always takes a little time for the timeout event to be placed in the controller's event queue, and from there be dispatched to the capsule.
"},{"location":"target-rts/timers/#timer-priority","title":"Timer Priority","text":"If you want a timeout event to be processed as quickly as possible you can use a higher than default priority when setting the timer. The last parameter of informIn()
, informAt()
and informEvery()
specifies the priority of the timeout event (by default it's General
which is the normal priority of an event). In the same way you can lower the priority, if you want the timeout event to be handled at a lower priority.
The timer set in the example below will timeout immediately and the timeout event will be processed with a higher than normal priority.
timer.informIn(0s, High); \n
"},{"location":"target-rts/timers/#cancel-a-timer","title":"Cancel a Timer","text":"To cancel a timer you need the RTTimerId
object that you constructed when the timer was set. Call cancelTimer()
on the timer port, with the RTTimerId
as argument, to cancel the timer. Here is an example where a timer is set, and then immediately cancelled.
RTTimerId tid = timer.informIn(RTTimespec(10, 0)); // 10 s\nif (!tid.isValid()) {\n // error when setting timer\n}\nelse {\n timer.cancelTimer(tid); \n // now tid.isValid() will return false\n}\n
If you need to cancel a timer from a different code snippet from where it was set, you need to store the RTTimerId
object in a member variable of the capsule.
Important
When you create an RTTimerId
object from an RTTimerNode*
, the RTTimerNode
will store a pointer to internal data of the RTTimerId
object. The TargetRTS keeps track of RTTimerNode
s for active timer requests, and may access that internal data of your RTTimerId
object. It's therefore important to make sure that the address of an RTTimerId
object doesn't change, as that would make the RTTimerNode
reference invalid memory. For example, you should not insert RTTimerId
objects into an std::vector
as that could change their addresses, and hence invalidate such pointers, when the vector is modified. Either use a collection that doesn't do this (e.g. std::list
) or store pointers to RTTimerId
objects in the collection instead of the objects themselves.
Cancelling a timer guarantees that its timeout event will not be received by the capsule. This is true even if, at the time of cancellation, the timeout period has already lapsed, and the timeout event is waiting in the controller's event queue to be dispatched to the capsule. In this case cancelling the timer will remove the timeout event from the queue so that it doesn't get dispatched to the capsule.
However, when the timeout event already has been dispatched, it's too late to cancel the timer. If you still do it you will receive an error. In the same way, it's an error to cancel the same timer more than once. You can call RTTimerId.isValid()
to check if the RTTimerId
is still valid (meaning that its timeout event has not been dispatched) before cancelling the timer.
Just like other events, the timeout event that is sent when a timer has timed out, can have data. At most one data object can be passed, and if you need more you can use a struct or class as data type.
Contrary to data of user-defined events, timer data is untyped (void*
). You therefore need to provide the type descriptor of the data as an extra argument when setting the timer. The TargetRTS will copy the provided data into the timeout event, so the type descriptor must provide a copy function.
Here are examples of setting timers with timer data:
// Pass a boolean as timer data\nbool b = true;\ntimer1.informIn(RTTimespec(5, 0), &b, &RTType_bool);\n\n// Pass the current time as timer data\nRTTimespec now;\nRTTimespec::getclock(now);\ntimer2.informIn(1s, &now, &RTTimespec::classData);\n
Note
Since timer data is untyped, any timeout event can carry any kind of data. While this is flexible, it requires caution since in the timeout transition you need to explicitly cast rtdata
from void*
to a pointer to the timer data. You must therefore be sure what type of data each timeout event carries. It's recommended to not use different types of data for the same timer.
Here is an example of how to access the data of timer2 from the above example:
timeout: on timer2.timeout\n`\n const RTTimespec then = *(static_cast<const RTTimespec*>(rtdata));\n`;\n
The data pointed at by rtdata
for a timeout event is owned by the TargetRTS. It is allocated to a copy of the data that is provided when setting the timer, and deallocated if the timer is cancelled. For a one-shot timer it's also deallocated after the timeout event has been dispatched and handled by the capsule, while for a periodic timer the same data object will be used for each timeout event that is produced.
Sometimes you may need to adjust the clock of your realtime application. For example, distributed applications that run on different machines in a network may use the Network Time Protocol (NTP) to synchronize the system time over the network. While any timer port can be used for adjusting the system time, it's recommended to only do it through one specific timer port on one specific capsule within the application.
Adjusting the clock is a three-step process:
adjustTimeBegin()
on the timer port. This suspends the timing service so no timeouts can happen, and no new timers can be set.adjustTimeEnd()
on the timer port, and provide the time adjustment as argument. The TargetRTS will recompute new timeout time points for all active timers that have been set with a relative time, for example periodic timers. After that the timing service is resumed.Note that adjustTimeEnd()
takes a relative time as argument (positive to move the clock forwards, and negative to move it backwards). However, operating system functions for setting the system clock usually take an absolute time. Here is an example of a capsule member function for setting the system clock to a new absolute time. Replace sys_setclock()
with the actual function for setting the system clock in your operating system.
void AdjustTimeCapsule_Actor::setClock(const RTTimespec& new_time)\n{\n RTTimespec old_time, delta;\n\n timer.adjustTimeBegin();\n\n RTTimespec::getclock(old_time); // Read system clock\n if (sys_setclock(new_time)) { // Set system clock with OS function\n delta = new_time;\n delta -= old_time;\n }\n\n timer.adjustTimeEnd(delta);\n}\n
Error handling is important in this function; if the function for setting the system clock fails (for example because the application doesn't have enough privileges to change the clock), it must call adjustTimeEnd()
with a zero time argument, to restart the timing service without changing the clock. adjustTimeEnd()
works by simply adding a time offset to account for the changed system time, and if the system time was not modified that offset must be zero.
Example
You can find a sample application that changes the system clock here. The sample is for Windows but can easily be modified for other operating systems.
"},{"location":"target-rts/versions/","title":"Versions","text":"Note
Some of the information in this chapter is only applicable for the Commercial Edition of Code RealTime since it includes the source code for the TargetRTS. With the Community Edition comes only precompiled versions of the TargetRTS for a limited number of commonly used target configurations.
It's common to extend and modify the TargetRTS with your own utilities and customizations. In this case you will build your application against a copy of the TargetRTS that contains these changes. However, when a new version of the TargetRTS is released, you then must incorporate the changes in that new version into your own copy of the TargetRTS. This document helps with this process by documenting all changes made in the TargetRTS. It also describes some strategies for more easily managing multiple versions of the TargetRTS.
Note
The version of the TargetRTS is defined in the file RTVersion.h
by means of the macro RT_VERSION_NUMBER
.
To simplify the process of adopting changes from a new version of the TargetRTS, so called patch files are provided in the folder TargetRTS_changelog
(located next to the TargetRTS
folder). The patch files have names <from-version>_<to-version>.patch
and contain all changes made from one version to another. You can use the command-line tool patch
to automatically apply the changes of a patch file to your own copy of the TargetRTS.
For example, to apply the changes from version 8000 to version 8002, go to the folder that contains the TargetRTS
and TargetRTS_changelog
folders and run this command:
patch -p3 < TargetRTS_changelog/8000_8002.patch\n
You can also downgrade the version of the TargetRTS by running the same command but with the -R
flag.
Note
The patch
command is included in Linux and Unix-like operating systems, but on Windows you have to download it separately. You can for example get it through the Git for Windows set of tools.
The patch files in the TargetRTS_changelog
folder have been created by a Bash script TargetRTS/tools/createPatch.sh
. You can use this script if you have your version of the TargetRTS in a Git repo and want to produce a patch file for the changes you have made to the TargetRTS. You can then later use that patch file to apply your changes to a newer version of the TargetRTS.
Whether it's best to adopt changes in a standard TargetRTS into your version of the TargetRTS, or to do the opposite, i.e. adopt your changes into a standard TargetRTS, may depend on how big changes you have made. If your changes are small and limited the latter may be easiest, while if you have made substantial changes the former may be the better option.
"},{"location":"target-rts/versions/#change-log","title":"Change Log","text":"Below is a table that lists all changes made in the TargetRTS since version 8000 (which were delivered with Code RealTime 1.0.0). For changes in older versions of the TargetRTS, which were done for Model RealTime, see this document.
TargetRTS Version Included Changes 8001 JSON Decoding 8002 Building without rtperl JSON parser Script for creating TargetRTS patch files Pointers in JSON encoding/decoding 8003 Align terminology in comments Configurable max TCP Connections 8004 Improved implementation of JSON parser JSON encoding/decoding for RTByteBlock New target configuration for MacOS on AArch64"},{"location":"target-rts/versions/#json-decoder","title":"JSON decoder","text":"A new decoder class RTJsonDecoding
is now available for decoding messages and data from JSON. JSON produced from data by the JSON Encoder (RTJsonEncoding
) can be decoded back to (a copy of) the original data.
New macros were added in makefiles to support building generated applications without using rtperl
.
A new class RTJsonParser
can be used for parsing arbitrary JSON strings. It has a more general use than RTJsonDecoding
which is specifically for decoding JSON that has been produced by RTJsonEncoding
. See this chapter for more information.
A Bash script createPatch.sh
is now available in the tools
folder of the TargetRTS. It can be used for producing patch files describing the differences between two versions of the TargetRTS. See Patch Files for more information.
Data of pointer type is now encoded to a string by the JSON encoder (RTJsonEncoding
) and can be decoded back to a memory address by the JSON decoder (RTJsonDecoding
).
Several comments were updated to align the terminology used in Code and Model RealTime. This was done so that the Doxygen documentation that is generated from the TargetRTS header files will be easy to understand for users of both products.
"},{"location":"target-rts/versions/#configurable-max-tcp-connections","title":"Configurable max TCP connections","text":"The RTTcpSocket
class has a new function setMaxPendingConnections()
which can be used for setting the maximum number of clients that can connect to the TCP socket. Previously this limit was always 5, and this is still the default in case you don't call this function to change it.
The RTJsonParser
now has an improved recursive implementation that uses a map instead of a vector for storing keys and values in the RTJsonResult
object. The new implementation provides new functions RTJsonResult::keys_begin()
and RTJsonResult::keys_end()
which allows to iterate over the keys in the parse result without knowing their names.
The RTJsonEncoding
and RTJsonDecoding
now support JSON encoding/decoding for objects of the RTByteBlock
class.
A new target configuration for the Clang 15 compiler for MacOs with ARM processor is now available. It has the name MacT.AArch64-Clang-15.x
.
An Art file is primarily edited using its textual syntax in the text editor. However, many parts of the Art language also has a graphical syntax which can be shown (and to some extent also edited) from graphical diagrams. Visual Studio Code and Eclipse Theia also provide a few views that provide value for Art, such as the Outline view and the References view. These views do not support any editing, but provide useful overview and navigation possibilities.
"},{"location":"working-with-art/art-editor/","title":"Text Editor","text":"You can use any text editor for editing Art files, but it's highly recommended to edit them in Code RealTime. Thereby you will have access to features such as syntax coloring, content assist and semantic validation.
"},{"location":"working-with-art/art-editor/#syntax-coloring","title":"Syntax Coloring","text":"Code RealTime provides color themes that have been specifically designed for being used for editing Art files. Activate one of these color themes from File - Preferences - Color Theme.
This feature, which also is known as IntelliSense or Code Completion, helps you when editing an Art file by proposing commonly used Art constructs that are valid at the current cursor position. Invoke Content Assist by pressing Ctrl+Space. Depending on where the cursor is placed you will get different proposals to choose from. There are four kinds of proposals as shown in the picture below:
State
variable for the capsule code template which occurs both in the state definition and as a state reference in the initial transition. All occurrances of a variable are updated simultaneously when you replace the variable with a string.Note that code templates are also available in some C++ code snippets (e.g. rt::decl
and rt::impl
) and can help you insert pieces of C++ code that are commonly used in Art applications.
name
item in the proposals list tells you that you can use an arbitrary identifier as the name of an Art element at that position. For example, in the proposals list shown in the picture above name
appears since a triggered transition may have an optional name before its declaration. The code template for the triggered transition will not insert a name, since many transitions don't have names, but you can manually add it afterwards:MyTransition: State -> X on timer.timeout\n
:
or .
where applicable. For example, after you have typed the name for the triggered transition shown above you can use Content Assist to learn that it may be followed by either a ->
or :
token:If you use Content Assist within a C++ code snippet, Code RealTime will delegate the request to the C++ language server extension that is installed. It works by computing valid completions from the cursor position based on the generated C++ file that contains the code from the code snippet.
Note that the \"Microsoft C++\" and \"clangd\" language servers work slightly differently in this regard. It can happen that you in some cases need to invoke Content Assist twice, before the correct results appear. This has to do with how the language servers keep cached information from C++ files, and will hopefully improve in future versions.
Hint
The \"clangd\" language server supports an argument --completion-parse=always
which you can add in its settings. It's recommended to set this argument, since it will force the generated C++ file to be parsed each time Content Assist is invoked, without relying on cached information.
To rename an Art element place the cursor on the element's name and press F2 (or invoke the command Rename Symbol from the context menu). This performs a \"rename refactoring\" that updates all references to the renamed element too.
Note
Avoid renaming an element by simply editing its name. For Code RealTime to understand that you want to rename an element, rather than replacing it with another element, you need to use the approach described above.
"},{"location":"working-with-art/diagrams/","title":"Diagrams","text":"Art is a textual language but there is also a graphical notation for many parts of the language. You can therefore visualize (and in many cases also edit) some of the Art elements using graphical diagrams. The following diagrams can be used:
The picture below shows an example of what these diagrams may look like:
"},{"location":"working-with-art/diagrams/#opening-diagrams","title":"Opening Diagrams","text":"To open a diagram from an Art file place the cursor inside an Art element. Bring up the context menu and invoke a command for opening a diagram for the Art element: Open State Diagram, Open Structure Diagram or Open Class Diagram. Note that all these three commands are always available, but if the selected Art element cannot be shown in the selected kind of diagram, you will get an error and no diagram will open.
If the cursor is placed on an Art element that has a graphic representation in the form of a symbol or line in the diagram, for example a state in a state diagram, the symbol or line will be highlighted in the opened diagram by selecting it. You can use this feature as a way to navigate from an element in an Art file to the corresponding symbol or line in a diagram. If the diagram is already open, it will be made visible and the selection will be updated.
You can also open diagrams from the context menu of an Art file in the Explorer view. In this case the Art file will be searched for an element that can be shown in the selected kind of diagram. If more than one such Art element is found, you will be prompted to pick the one to show in the diagram. For example:
The same prompting happens if you open a diagram from an Art file when the cursor position doesn't indicate which Art element to open the diagram for. All valid Art elements in the file will be listed and you can choose which one to open the diagram for.
You can open multiple diagrams of the same kind in one go by selecting multiple Art files in the Explorer view, and then invoke a command for opening diagrams from the context menu. However, in this case only diagrams for the first element found in each file will be opened (i.e. in this case you will not be prompted in case a file contains multiple elements for which the selected kind of diagram could be opened).
"},{"location":"working-with-art/diagrams/#related-diagrams","title":"Related Diagrams","text":"If you already have a diagram open, you can open another diagram that is related to that diagram. And if a symbol or line is selected on the diagram, diagrams related to the selected symbol or line can be opened. Press Ctrl+Space to open the diagram's pop-up menu. If the diagram, or the selected symbol or line, has any related diagrams you may see the following commands:
For a capsule that inherits from another capsule you can open the state diagram of the inherited base capsule by means of the command Open Inherited State Diagram. If this command is performed on an element that is inherited, redefined or excluded in the state diagram, then the corresponding element in the base capsule will be highlighted. This command is therefore useful for navigating in an inherited state machine.
"},{"location":"working-with-art/diagrams/#navigating-from-diagram-to-art-file","title":"Navigating from Diagram to Art File","text":"If you double-click a symbol or a line in a diagram, the Art element that corresponds to that symbol or line will be highlighted in the Art file. Note that you need to double-click on the symbol or line itself, and not on a text label shown in the symbol or on the line. However, as an alternative you can instead hold down the Ctrl key and then click on the text label. It will then become a hyperlink that navigates to the Art element that corresponds to that text label. You need to use this approach in case a symbol has multiple text labels each of which represent different Art elements. For example:
In state diagrams you can also double-click on icons that are shown for transitions that contain effect and/or guard code. The presence of effect code is indicated by a blue icon, and guard code with a yellow icon.
Double-clicking these icons will highlight the code snippets in the Art file.
"},{"location":"working-with-art/diagrams/#working-with-diagrams","title":"Working with Diagrams","text":""},{"location":"working-with-art/diagrams/#zooming-and-panning","title":"Zooming and Panning","text":"When a diagram is opened it is initially centered and with medium zoom level which makes all text labels big enough for reading. However, if the diagram is big then all contents may not be visible unless you zoom out. You can zoom the diagram using either the mouse scroll wheel or by means of the two-finger zoom gesture on a touch pad. You can also zoom using the buttons in the Properties view toolbar. There you will also find a Center button which will restore the diagram to its original zoom level.
Alternatively you can use the command Fit to Screen which will set the zoom level so that the entire diagram fits the size of the diagram editor. Note that this command must be invoked from the general Command Palette or by means of the keyboard shortcut Ctrl+Shift+F.
It's also possible to work with a big diagram without zooming, but instead panning the viewport so that a different part of the diagram becomes visible. To pan the viewport click anywhere on the diagram and drag while holding down the mouse button. Note that there are no limits to panning which means you can move the viewport as far away from the center of the diagram as you like. Use the Center or Fit to Screen command for panning back the viewport to its original position. Note that if a symbol or line is selected, the Center command will move the viewport so that the selected symbol or line appears in the middle.
"},{"location":"working-with-art/diagrams/#collapsing-and-expanding-symbols","title":"Collapsing and Expanding Symbols","text":"State and structure diagrams can be hierarchical. A state diagram is hierarchical if it contains a composite state with a nested state machine. A structure diagram is hierarchical if it contains a part typed by another capsule with nested parts or ports. By default symbols that contain nested symbols are collapsed to minimize the size of the diagram:
To expand a collapsed symbol click the yellow button. The symbol will then be resized to show the nested symbols. Click the button again to collapse the symbol and hide the nested symbols. You can use the Expand All and Collapse All buttons in the Properties view toolbar to expand or collapse all symbols so that the full hierarchical diagram becomes visible or hidden.
Information about which symbols that are currently expanded will be remembered if you save the diagram. This information is stored in the file .vscode/art_diagram_settings.json
.
Many diagram commands mentioned above can be invoked using the keyboard. Press Ctrl+Space in a diagram to open a pop-up menu from where you can invoke a diagram command.
In this pop-up menu you also find convenient commands for navigating to related diagrams. For example, from the state diagram of a capsule you can navigate to the structure and class diagrams of that same capsule.
"},{"location":"working-with-art/diagrams/#diagram-appearance","title":"Diagram Appearance","text":"Certain properties on Art elements control how they will appear in a diagram. Currently it's possible to configure which color to use for most elements in diagrams. See the color property for more information.
"},{"location":"working-with-art/diagrams/#diagram-filters","title":"Diagram Filters","text":"To avoid cluttered diagrams with too many text labels, certain information is by default hidden. If you click in the background of the diagram, the Properties view will show various filters that you can turn on or off for showing or hiding such additional information. Here is an example of the filters available for a state diagram:
Information about applied filters will be remembered if you save the diagram. This information is stored in the file .vscode/art_diagram_settings.json
.
Diagram filter properties that have been modified are shown in boldface, and a \"Restore default\" button appears for them. You can click this button to restore the filter property to its default value.
You can also set diagram filters globally using diagram settings. Such filters will apply to all diagrams unless a more specific filter has been set on an individual diagram. You can find these settings by filtering on code-rt.diagram
in the Settings editor:
Note that some diagram filters can only be set globally, and not for individual diagrams.
"},{"location":"working-with-art/diagrams/#elements-in-the-properties-view","title":"Elements in the Properties View","text":"The Properties view can show additional Art elements when you select a symbol or a line. For example, it shows internal transitions of a state.
Showing such elements in the diagram itself would risk making it cluttered, especially when there is a large number of elements.
You can double-click the Art elements in the Properties view to highlight them in the Art file. For internal transitions the same blue and yellow dots are shown as for regular transitions in diagrams. Double-click the blue dot to navigate to the transition effect code and the yellow dot for navigating to the transition guard code.
"},{"location":"working-with-art/diagrams/#renaming-elements","title":"Renaming Elements","text":"You can rename an Art element shown in a diagram by double-clicking on the text label that shows its name. Alternatively select the symbol or line to which the text label belongs and press F2.
Note that this is a \"rename refactoring\" and all references to the renamed element will be updated too.
"},{"location":"working-with-art/diagrams/#creating-and-editing-elements","title":"Creating and Editing Elements","text":"Note
Creating and editing elements is supported in state and structure diagrams but not in class diagrams.
To create a new element in a diagram use one of the New ... commands in the pop-up menu that appears when you press Ctrl+Space. These commands are the same as appear when you use Content Assist in the Art text editor. Which commands that are available depends on what is currently selected in the diagram. If an element is selected in the diagram, a new element will be created inside that element. Otherwise the new element will be created as a top-level element (possible in state diagrams but not in structure diagrams).
To edit an existing element, select it in the diagram and use the Properties view for editing it. There are certain properties that are common for many elements, such as the color property, but most properties are specific for the element that is selected.
Elements are created and edited by updating the Art file, which in turn will update the diagram. Just like when you use Content Assist in the Art text editor a created element will initially get default values for its properties, for example the name. The default value becomes selected so you can directly type to replace it with something else.
You can of course undo a change by pressing Ctrl+z (Undo) in the Art text editor.
"},{"location":"working-with-art/diagrams/#state-diagram-editing","title":"State Diagram Editing","text":"In a state diagram where nothing is selected, the New ... commands will create new top-level elements directly in the state machine.
If a state is selected you can create the following elements inside it (turning the state into a composite state, if it was not already composite).
To create a transition in a state diagram you first need to select the source state (or pseudo-state) and then the target state (or pseudo-state). Then press Ctrl+Space and perform either New Triggered Transition or New Non-Triggered Transition (depending on if the transition needs any triggers or not).
You can redirect a transition, i.e. to change either its source (state or pseudo-state) or its target (state or pseudo-state). You can do it from a state diagram by selecting both the transition and the new source or target. Then press Ctrl+Space and perform either Set Transition Source or Set Transition Target. This will redirect the transition by changing its source or target. If you want to change both the source and target just repeat the procedure once more.
For example, in the diagram below we have selected the transition between states Ready and Heating and also the CoolOffState. We then select Set Transition Source in the menu. This will redirect the transition to instead go from state CoolOffState to state Heating.
"},{"location":"working-with-art/diagrams/#structure-diagram-editing","title":"Structure Diagram Editing","text":"In a structure diagram it's not possible to create anything unless something is selected in the diagram. This is because a structure diagram always has a single capsule as its top-level element. If the capsule is selected you can create parts and ports in it.
If a part is selected you can create a port in the capsule that types the part. In the example below a port will be created in capsule BB.
Both parts and ports have several properties that can be edited using the Properties view.
"},{"location":"working-with-art/diagrams/#deleting-elements","title":"Deleting Elements","text":"Note
Deleting elements is supported in state and structure diagrams but not in class diagrams.
You can delete an Art element shown in a diagram by selecting the symbol or line that represents the element and then press the Delete key. Alternatively use the command Delete in the Ctrl+Space pop-up menu. Multiple symbols or lines can be selected in order to delete many Art elements in one go.
Note that elements are deleted by removing them from the Art file, which in turn will update the diagram. All content within the deleted element will be lost, including any comments. However, you can of course undo the deletion by pressing Ctrl+z (Undo) in the Art text editor.
"},{"location":"working-with-art/outline-view/","title":"Outline View","text":"The Outline view shows information about the Art elements that are defined in an Art file. You can see the most important information for each element, such as its name and other important properties. You can also see the containment hierarchy, i.e. which elements that contain other elements. Below is an example of what it can look like:
You can use the Outline view for getting an overview of what elements an Art file contains, and for searching and navigating to elements.
"},{"location":"working-with-art/outline-view/#navigating","title":"Navigating","text":"To navigate to an element in the Art file, double-click on the element in the Outline view. The cursor will be placed just before the element's name in the Art file (or where the name would be in case it has no name).
You can also single-click on elements to just make the clicked element visible in the Art editor, without changing the cursor position. In this case the element is marked with a thin rectangle:
If you hold down the Ctrl key when clicking, a new Art editor showing the same Art file will open to the side, in a new editor area to the right. The element will then be made visible and marked in that new editor. This can be useful if you don't want to change the original Art editor, for example when comparing two elements located in the same Art file.
It's also possible to navigate in the other direction, i.e. from the Art editor to the Outline view. To do this, set the Outline view to follow the cursor:
Now the Outline view will automatically highlight the element that corresponds to the cursor position in the Art editor.
"},{"location":"working-with-art/outline-view/#searching","title":"Searching","text":"You can use the Outline view when searching for one or many Art elements, as an alternative to searching textually in the Art editor. Start by selecting the element shown first in the Outline view, and then type quickly the first few characters of the element name. After every keystroke the selection will move downwards to an element with a name that matches the typed characters. If you make a brief pause, you can then start to type again to proceed searching further down in the Outline view.
Another way to search is to press Ctrl+f when the Outline view has focus. A small popup will then appear where you can type a few characters. Nodes in the Outline view with a label that matches the typed characters will be highlighted. The matching allows additional characters between the typed characters which is why the typed string \"init\" also matches the transition Waiting -> Terminated
:
When the search has matched a few elements that look interesting you can press the Filter button next to the text field to filter the Outline view so it only shows the matching elements. This can avoid lots of scrolling if the matching elements are far apart.
"},{"location":"working-with-art/references/","title":"References View","text":"The References view shows how Art elements reference each other. Depending on what kind of reference you are interested in, there are different commands to use.
"},{"location":"working-with-art/references/#referencing-elements","title":"Referencing Elements","text":"To find all elements that reference a certain Art element, right-click on the name of the Art element (it must have a name, otherwise it cannot be referenced) and perform the context menu command Find All References. The References view will in this case list all referencing elements, and group them by the Art file where they are located. For example, it could look like this if the command was invoked on a state.
Double-click the items in the References view to navigate to the referencing element in the Art file. You can remove a referencing element from the view by clicking the Dismiss (x) button. This can for example be useful if you are going through a large list of references and want to remove those you have already examined to make the list more manageable. You can restore all referenced element to be shown again by pressing the Refresh button in the toolbar.
An alternative way of finding and going through all referencing elements is to instead use the context menu command Go to References. This commands works the same as Find All References but will show the referencing elements inline in a popup in the Art editor instead of using the References view.
"},{"location":"working-with-art/references/#type-hierarchy","title":"Type Hierarchy","text":"To find how Art elements relate to each other in terms of inheritance, right click on the name of an Art element that can be inherited (i.e. a class, capsule or protocol) and perform the context menu command Show Type Hierarchy. The References view will show the subtypes or supertypes of the selected Art element. For example:
Use the leftmost toolbar button to toggle between showing subtypes or supertypes. Double-click on items in the tree to navigate to a subtype or supertype.
Note that an alternative to using the References view for looking at type hierarchies is to visualize them graphically using class diagrams (see Diagrams).
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"contributing/","title":"Contributing","text":"We appreciate your interest in contributing to Code RealTime. This article guides to propose changes in Code RealTime public repository on GitHub, and join the development process.
"},{"location":"contributing/#prerequisites","title":"Prerequisites","text":"Navigate to the Code RealTime repository: secure-dev-ops/code-realtime.
Click the \"Fork\" button in the top-right corner. This creates a copy of the repository in your GitHub account.
git clone
command to create a local copy of your forked repository in your workspace. Replace <your-username>
with your GitHub username: git clone https://github.com/<your-username>/code-realtime.git
cd <your-local-directory>
git checkout -b contribute-feature-x
where contribute-feature-x is your new branch name.git add <filename1> <filename2> ...
git commit -m \"Proposed a new change in feature X\"
git push origin contribute-feature-x
Visit your forked repository on GitHub (e.g., 'https://github.com/your-username/code-realtime').
Locate the Pull requests tab and click the New pull request button.
Select your branch containing the changes (e.g., contribute-feature-x
) and compare it with the main branch of the upstream repository secure-devops/code-realtime.
Provide a clear and concise title and description for your pull request. In the description, explain the purpose of your changes and how they address an issue or improve the project.
Click Create pull request.
The Art Language - State Machine - Transition - Frequent Transition
"},{"location":"draft-documentation/#frequent-transition","title":"Frequent Transition","text":"Sometimes you may have a state where one or a few outgoing transitions can be expected to execute much more frequently than others. You can then set a frequent
property on the transition trigger that you expect will trigger the transition frequently. The Art compiler uses this information to optimize generated C++ code so that such transition triggers are evaluated before other triggers that are expected to trigger the transition less frequently.
interrupted: Working -> Stopped on [[rt::properties(\n frequent=true\n )]] external.interrupt\n `\n // Interrupted while working...\n `;\n
Note
The frequent property relies on optimization features in the C++ compiler that may or may not be available depending on which target compiler that is used. Only use frequent transitions if profiling has shown that you have a need to do this optimization.
======================================================== The Art Language - Property
"},{"location":"draft-documentation/#frequent","title":"frequent","text":"Triggers for which this property is true
will lead to generated code that handles these triggers faster than other triggers. This is done by placing their if-statements early in the rtsBehavior
function to ensure that as little code as possible needs to execute when dispatching a message for a frequent trigger.
| Capsule, Class | generate_file_header | Boolean | true | Capsule, Class | generate_file_impl | Boolean | true | Capsule, Class, Protocol, Port,
| Class, Protocol | version | Integer | 0 | Class | generate_descriptor | Enumeration (true, false, manual) | true | Class | kind | Enumeration (_class, struct, union) | _class | Class | generate_class | Boolean | true | Class | generate_statemachine | Boolean | true | Class | const_target_param_for_decode | Boolean | false | Class | default_constructor_generate | Boolean | true | Class | default_constructor_explicit | Boolean | false | Class | default_constructor_inline | Boolean | false | Class | default_constructor_default | Boolean | false | Class | default_constructor_delete | Boolean | false | Class | default_constructor_visibility |
"},{"location":"draft-documentation/#generate_file_header","title":"generate_file_header","text":"By default a capsule or class is translated to one header file (.h
) and one implementation file (.cpp
). Set this property to false
to prevent generation of the header file, for example if you prefer to write it manually.
By default a capsule or class is translated to one header file (.h
) and one implementation file (.cpp
). Set this property to false
to prevent generation of the implementation file, for example if you prefer to write it manually.
By default a type descriptor will be generated for each class. The TargetRTS uses the type descriptor to know how to initialize, copy, move, destroy, encode or decode an instance of that class. Set this property to false
for classes that don't need a type descriptor. Set it to manual
if the class needs a type descriptor but you want to implement it manually rather than using the implementation that is generated by default. Note that even if you set this property to true
so that a default type descriptor is generated, you can still override individual type descriptor functions for the class.
By default a class is translated to a C++ class. You can use this property to instead translate it to a struct
or union
.
If set to false
no C++ code will be generated for the class.
If set to false
code generation for the class' state machine will be suppressed. You can use this if the state machine is informal, and you prefer to implement it manually in another way.
By default a decode function uses a non-const target
parameter. This is because usually a decode implementation must call non-const functions on the decoded object to populate it with data from the decoding. However, if it doesn't need to call such functions you can set this property so that the target
parameter is declared as const.
If set to false
a default (i.e. parameterless) constructor will not be generated for the class.
If set to true
the default (i.e. parameterless) constructor will be declared as explicit.
If set to true
the default (i.e. parameterless) constructor will be declared as inline. It's implementation will then be generated into the header file.
If set to true
the default (i.e. parameterless) constructor will be declared as defaulted. This tells the compiler to synthesize a default constructor even if one normally would not be synthesized (for example because there is a user-defined constructor with parameters).
If set to true
the default (i.e. parameterless) constructor will be declared as deleted. This will cause the compiler to generate an error if it is invoked. This can be used for preventing objects of the class to be created.
This property can be used for setting the visibility of the default (i.e. parameterless) constructor. By default it will be public
but you can change it either to protected
or private
.
Code RealTime can be installed on top of Visual Studio Code or Eclipse Theia.
The latest version of Code RealTime is available on the Visual Studio Marketplace and on the Open VSX Registry. To install that version into Visual Studio Code or Eclipse Theia follow these steps:
1) Click \"Extensions\" in the activity bar to open the Extensions view.
2) Type \"Code RealTime\" in the search field.
3) Click the \"Install\" button to install the Code RealTime extension
Once the installation is finished you will see Code RealTime appear in the \"Installed\" section of the Extensions view:
The screenshot above also shows that an extension for working with C/C++ has been installed. See Setup C++ Build Tools for more information.
After you have installed Code RealTime it's recommended to restart Visual Studio Code or Eclipse Theia, or at least to perform the command Developer: Reload Window
which is available in the Command Palette (Ctrl+Shift+P).
Another way to install Code RealTime is to use a .vsix file. This can be useful if you want to install another version than the latest. You can download .vsix files for all released versions of Code RealTime from both the Visual Studio Marketplace and the Open VSX Registry (click \"Version History\"). Once you have downloaded the .vsix file follow these steps to install it:
1) If you already have a version of Code RealTime installed, you can manually uninstall it first (see Uninstalling). Note that this step is usually not required since the newly installed version of the extension will automatically replace the old one.
2) Open the menu of the Extensions view and select the command \"Install from VSIX\".
3) In the file dialog that appears, select the .vsix file to install.
If the installation completes successfully you should see the following message:
If instead the installation fails, this message will tell you the reason. One common reason for failure is that your version of Visual Studio Code or Eclipse Theia is not compatible (i.e. too old) for Code RealTime.
It should also be noted that it's possible to directly install any published version of Code RealTime by using the \"Install Another Version\" command that is available in the context menu of an extension shown in the \"Installed\" section.
"},{"location":"installing/#install-from-docker-image","title":"Install from Docker Image","text":"Yet another way to install Code RealTime is to use the Docker image that is available on DockerHub. This image contains Eclipse Theia with the latest version of Code RealTime installed. Run the docker image using this command:
docker run -p <host-port>:<container-port> -e isDocker=true baravich/theia-code-realtime:1.0
Replace <host-port>
with a port that is available on your computer, and <container-port>
with the port you want the Docker container to use. For example, if you run this command
docker run -p 4000:3000 -e isDocker=true baravich/theia-code-realtime:1.0
then after less than a minute you can access Code RealTime from a web browser at http://localhost:4000.
"},{"location":"installing/#viewing-installation-information","title":"Viewing Installation Information","text":"If you are unsure about which version of Code RealTime you have installed, you can see the version in the extension's tooltip, and the full build version is available in the page that appears if you double-click the extension:
You can also see the version and the exact date of the installed Code RealTime in the Changelog that is present on the extension's page. There you can also see what has been fixed and improved compared to older releases. Note that for Theia this information is not present on the extension's page, but you can see it if you double-click on the extension's name (the web page of the extension will then open).
"},{"location":"installing/#portable-mode-installation","title":"Portable Mode Installation","text":"You can install multiple versions of Code RealTime by using the portable mode of Visual Studio Code. See Portable Mode for how to install Visual Studio Code in portable mode, which will allow you to install a version of Code RealTime that won't affect other Visual Studio Code installations on the machine. Portable mode also allows to move or copy an installation from one machine to another, which makes it useful in scenarios where installs should be centralized in an organization.
"},{"location":"installing/#post-installation-configuration","title":"Post-Installation Configuration","text":"After a successful installation you need to perform a few configuration steps before you can start to use Code RealTime.
"},{"location":"installing/#setup-java","title":"Setup Java","text":"Code RealTime uses a Java language server and hence needs a Java Virtual Machine (JVM). It's required to use a JVM for Java 17 or newer. If an appropriate JVM cannot be found when the Code RealTime extension is activated (which for example happens the first time you open an Art file), you will receive an error message.
Code RealTime follows the steps below in priority order when it looks for an appropriate JVM to use:
1) The setting code-rt.languageServer.jvm
is examined. If it specifies a path to a JVM it will be used. You can edit this setting by invoking File - Preferences - Settings and then type the setting id mentioned above in the filter box.
2) The environment variable JAVA_HOME
is examined. If it specifies a path to a JVM it will be used.
3) An attempt is made to launch the java
command without using a path. The first JVM found in the system path, if any, will be used.
You may also need to adjust the arguments for the JVM. By default the JVM is launched with the below argument:
-Xmx4024m
To change the JVM arguments set the setting code-rt.languageServer.jvmArgs
shown in the image above.
When the Code RealTime extension is activated information about which Java that is used is printed to the Art Server output channel.
Here you will also see if the launching of the language server for some reason failed.
"},{"location":"installing/#setup-c-build-tools","title":"Setup C++ Build Tools","text":"When Code RealTime builds generated C++ code it uses C++ build tools such as a make tool, a C++ compiler, a C++ linker etc. These tools need to be in the path when you start Visual Studio Code or Eclipse Theia. If you have multiple C++ build tools installed, make sure the correct ones are present in the path before launching Visual Studio Code or Eclipse Theia. For example, if you use the Microsoft C++ compiler, it's recommended to launch from a Visual Studio native tools command prompt with the correct version (e.g. 32 bit or 64 bit). Build errors caused by inconsistent versions of C++ build tools being used can be tricky to find.
You also need to install an extension for C/C++ development into Visual Studio Code or Eclipse Theia. Even if you can use any such extension, Code RealTime provides the best integration with either C/C++ for Visual Studio Code or clangd.
"},{"location":"installing/#uninstalling","title":"Uninstalling","text":"To uninstall Code RealTime follow these steps:
1) Click \"Extensions\" in the left side-bar.
2) Find the Code RealTime extension in the \"Installed\" section and invoke the \"Uninstall\" command (in Visual Studio Code the command is available in the context menu, while in Theia it shows up as a button to click).
Once the uninstallation is finished you will no longer see Code RealTime in the \"Installed\" section.
"},{"location":"overview/","title":"Overview","text":"Code RealTime lets you create stateful, event-driven realtime applications in C++.
It runs as an extension of Visual Studio Code or Eclipse Theia. Follow the installation instructions for installing it.
Code RealTime supports the Art language which extends the C++ language with high-level concepts useful when designing stateful, event-driven realtime applications. Examples of such concepts include capsules, state machines and protocols. Art is a textual language, but also provides a graphical notation that includes class, state and structure diagrams.
Code RealTime translates Art files into efficient C++ code which can be compiled on any target system. The generated code makes use of the Target RunTime System which is a C++ library that implements the concepts of the Art language.
Watch this video to get an overview of how Code RealTime uses the Art language for implementing stateful, event-driven realtime applications.
"},{"location":"overview/#art-history","title":"Art History","text":"The Art language as implemented in Code RealTime builds on a foundation with a long history in industry. In the early 1990s the Canadian company ObjecTime Limited developed a language called ROOM to address the challenges of building realtime applications consisting of communicating state machines. ROOM introduced concepts such as capsules, protocols and ports and was first implemented in the tool ObjecTime Developer. This tool got adopted in a wide range of industrial domains for example telecom and embedded systems development.
In 2000 ObjectTime was acquired by Rational Software and ObjecTime Developer was merged with Rational Rose, a UML modeling tool. The result was Rational Rose RealTime (Rose RT). At the same time many of the ROOM language concepts made its way into the, by then, new modeling language called UML-RealTime.
In 2003 Rational Software was acquired by IBM which at the time was investing heavily in the Eclipse platform. As a result, work started to create an Eclipse-based tool as the successor of Rose RT. This new product got the name Rational Software Architect RealTime Edition (RSARTE) and was first released in 2007.
In 2016 HCL entered a partnership with IBM, which led to a rebranded version of RSARTE called HCL RTist. A few years later both RSARTE and RTist were renamed to DevOps Model RealTime.
Work on Code RealTime began in 2020 with the aim of supporting other IDEs than Eclipse. As part of this effort a textual language syntax, Art, was developed. Hence it's fair to describe the Art language as a new syntax for concepts that have a rather old history and have already been used in the industry for more than 30 years. It should also be mentioned that the Target RunTime System used in Code RealTime is the same as is used in Model RealTime. In fact, the implementation of this C++ library started with ObjectTime Developer and has since then been gradually extended and modernized.
"},{"location":"settings/","title":"Settings","text":"Code RealTime provides several settings that can be used for configuring many aspects of how it works. To view these settings perform File - Preferences - Settings and then select Extensions - Code RealTime.
Below is a table that lists all Code RealTime settings. Each setting is described in a section of its own below the table.
Setting Id Purpose Language Server - Jvm code-rt.languageServer.jvm Set the JVM to use for running the Code RealTime language server Language Server - Jvm Args code-rt.languageServer.jvmArgs Set arguments for the JVM that runs the Code RealTime language server Validation - Rule Configuration code-rt.validation.ruleConfiguration Customize which validation rules to run on Art files and their severity Build - Output Folder code-rt.build.outputFolder Set the location where to place generated code Build - Cancel On Error code-rt.build.cancelOnError Cancel a launched build if errors exist in TCs or Art files Diagram - Show Junction Names code-rt.diagram.showJunctionNames Show junction names on state diagrams Diagram - Show Choice Names code-rt.diagram.showChoiceNames Show choice names on state diagrams Diagram - Show Entry Exit Point Names code-rt.diagram.showEntryExitPointNames Show entry/exit point names on state diagrams Diagram - Show Transition Names code-rt.diagram.showTransitionNames Show transition names on state diagrams Diagram - Show Diagnostics code-rt.diagram.showDiagnostics Show error, warning and information icons on diagrams"},{"location":"settings/#language-server","title":"Language Server","text":"Settings related to running the Code RealTime language server.
"},{"location":"settings/#jvm","title":"Jvm","text":"When the Code RealTime extension gets activated it will attempt to launch its language server. If this setting holds a valid location of a Java VM (JDK or JRE) it will be used for running the language server. Otherwise the JAVA_HOME
environment variable will be used. If that is also not set, it's required to have java
in the path. See Setup Java for more information.
By default the JVM is launched with the argument -Xmx4024m
. Refer to the documentation of your JVM for a list of available JVM arguments.
Settings related to validation of Art files.
"},{"location":"settings/#rule-configuration","title":"Rule Configuration","text":"This setting can be used for customizing which validation rules that should run when you edit Art files. You can also completely disable those validation rules you don't want to run. For more information see this page.
"},{"location":"settings/#build","title":"Build","text":"Settings related to building Art files, via C++ code, to libraries or executables.
"},{"location":"settings/#output-folder","title":"Output Folder","text":"This setting specifies a folder where all generated code will be placed. More precisely, it's used for resolving relative paths specified in TCs (using the TC property targetFolder
). If you leave this setting unset, relative paths will instead be resolved against the location of the TCs. The Output Folder
must be specified as an absolute path that points at a writable folder in the file system.
When a TC is built the Problems view is scanned to see if there are errors reported on the built TC or its prerequisites, as well as all Art files that will be built. If at least one such error is found it's recommended to cancel the build, fix the errors and then redo the build again. The default value for this setting is Prompt
which means you will be prompted by a dialog where you can choose if you want to cancel the build, or proceed anyway. In the latter case you may encounter compilation errors when generated code is compiled or run-time problems when the built executable is run. Therefore you should only proceed if you are confident that the errors are safe to ignore. You may set this setting to Always
to suppress the dialog and always cancel the build when errors are present. You can also (but this is not recommended) set the setting to Never
to always ignore any errors and proceed with the build anyway.
Settings related to graphical diagrams that visualize elements of Art files.
"},{"location":"settings/#show-junction-names","title":"Show Junction Names","text":"Junctions usually have short and uninteresting names, and are therefore by default not shown on state diagrams. Turn on this setting to make them visible. Note that a certain state diagram may override this setting by means of setting the corresponding diagram property in the diagram's Properties view. See Diagram Filters for more information.
"},{"location":"settings/#show-choice-names","title":"Show Choice Names","text":"Choices usually have short and uninteresting names, and are therefore by default not shown on state diagrams. Turn on this setting to make them visible. Note that a certain state diagram may override this setting by means of setting the corresponding diagram property in the diagram's Properties view. See Diagram Filters for more information.
"},{"location":"settings/#show-entry-exit-point-names","title":"Show Entry Exit Point Names","text":"Entry and exit points usually have short and uninteresting names, and are therefore by default not shown on state diagrams. Turn on this setting to make them visible. Note that a certain state diagram may override this setting by means of setting the corresponding diagram property in the diagram's Properties view. See Diagram Filters for more information.
"},{"location":"settings/#show-transition-names","title":"Show Transition Names","text":"If you feel that showing the names of transitions makes state diagrams too cluttered you can turn off this setting. By default they are shown. Note that a certain state diagram may override this setting by means of setting the corresponding diagram property in the diagram's Properties view. See Diagram Filters for more information.
"},{"location":"settings/#show-diagnostics","title":"Show Diagnostics","text":"By default diagram elements will be decorated by icons corresponding to diagnostics generated by validation rules. Turn off this setting if you don't want to see these icons on diagrams.
There are three kinds of diagnostic icons corresponding to the problem severity levels Error, Warning and Information. See Problem Severity for more information.
"},{"location":"support/","title":"Support Procedures","text":"If you find a bug in Code RealTime please report it with a GitHub Issue. Please include steps to reproduce and any additional files that can help in troubleshooting. For example, it can be good to include all log files. You can find the location of these logs by invoking the command Developer: Open Logs Folder
. You can zip the entire logs folder and attach it to the issue. You can also check these logs using the Output view. In particular, the following two output logs are relevant:
Code RealTime checks for semantic problems in your application. It does this by running a large number of validation rules each time an Art file or a TC file has been changed. The rules run automatically as soon as you have made a change to the file (even before saving it). This ensures that errors and warnings (i.e. potential problems) are found as early as possible.
"},{"location":"validation/#problem-severity","title":"Problem Severity","text":"Each validation rule has a default severity which will be used for the problems that are reported by the rule:
Error An error is a problem that is severe enough to prevent building a correct application. Errors must be fixed, and it will not be possible to build the Art files into a C++ application until all errors have been resolved.
Warning A warning is a potential problem, which you may or may not choose to fix. It can for example indicate a deviation from common conventions and best practises and it can indicate that the application will not behave as you may expect.
Information An information is just a message that you should be aware of. It doesn't really indicate a problem, and you don't need to fix it.
You can customize the default severity of any validation rule, and you can also choose to completely disable a certain validation rule that you don't think provides any value. See Configuring Validation for more information.
"},{"location":"validation/#problem-reporting","title":"Problem Reporting","text":"When a validation rule has found a problem in an Art file, it is marked by underlining one or several Art elements in the file. The underlining is red for errors, orange for warnings and blue for information messages. For example, in the capsule shown below one warning and two errors have been found.
You can hover the cursor over these underlinings to get a tooltip with information about the problem. Every problem has a message that describes it. Often this message gives enough information for understanding how to fix the problem. If this is not the case you can go to the documentation about the validation rule to find more information, examples and suggestions for how the problem can be fixed. To easily find the documentation click the hyperlink that consists of the unique id of the validation rule (it starts with a prefix such as \"ART_\", followed by a 4 digit number and a name). Alternatively you can search for the validation rule id on this page.
Often a problem may be associated with more than one Art element. There is a main element on which the problem will be shown, but there often also are other elements that are related to the problem in one way or another. You can navigate to related elements to get a better understanding of why a problem is reported and how to fix it. In the screenshot above the problem has a single related element (the capsule Another
) but in general a problem can have an arbitrary number of related elements.
Problems are also reported by means of icons in diagrams. Below are three states with problems of different severity:
A problem icon has a tooltip that shows the message of the problem. You can disable problem reporting in diagrams by means of a configuration setting code-rt.diagram.showDiagnostics
.
For a TC file, all properties it contains will be validated, and problems that are found during this validation are shown by underlining TC properties.
"},{"location":"validation/#problems-view","title":"Problems View","text":"Too see all problems found in all Art files and all TC files in the workspace, open the Problems view. The total number of problems found are shown in the Problems view heading. By default problems are shown in a tree grouped by the files where they were found. However, you can also view them as a flat table instead (but note that related elements can only be seen when using the tree view).
If there are many problems, it can help to filter the Problems View by typing some text in the filter box. For example, you can filter using a regular expression that matches only some of the files in the workspace, to reduce the number of problems shown.
"},{"location":"validation/#quick-fix","title":"Quick Fix","text":"Some problems have one or several typical solutions that are possible to apply automatically by means of \"code actions\". If a problem has at least one such code action defined, a yellow light bulb icon will appear and a Quick Fix command will be available in the problem tooltip.
Note that most semantic errors cannot be automatically resolved like this, but in some simple cases it's possible.
"},{"location":"validation/#configuring-validation","title":"Configuring Validation","text":"Validation can be configured to change which rules that should run, and what severity they should report found problems with. By default every validation rule is enabled and uses a predefined severity level. Validation rules can be configured either globally by means of a setting, or locally by means of a property rule_config. In both cases the rule configuration consists of a comma-separated list of 5 letter strings where the first letter specifies if the rule is disabled or it's severity (X,I,W,E) and remaining letters specify the rule id. For example, the rule configuration X0003,I0004,W0009,E0005
means the following:
To configure validation rules globally, use the configuration setting code-rt.validation.ruleConfiguration
. A global configuration will apply for all Art files in the workspace, and all Art elements within those files, unless a local rule configuration has been set on an element.
To configure validation rules locally, set the property rule_config on an Art element. It will affect the validation of that Art element itself, as well as all elements contained within that Art element. Here is an example of how to disable the validation rule ART_0003_nameShouldStartWithUpperCase on a capsule. Note that it also will disable this rule for elements contained within the capsule, such as states.
capsule customCapsule // no warning even if capsule name is not capitalized\n[[rt::properties(\n rule_config=\"X0003\"\n)]]{\n\n statemachine {\n state customState; // no warning here too\n initial -> customState;\n };\n};\n
Note
Certain validation rules cannot be disabled or have their severity changed. These are known as \"core validation rules\" and they run before semantic validation starts (which is why they cannot be customized).
Note
Local configuration of validation rules is only supported for Art files. For TC validation you cannot provide a local rule configuration in the TC file.
"},{"location":"validation/#validation-rules","title":"Validation Rules","text":"This chapter lists all validation rules which Code RealTime checks your Art application against. These rules find problems in Art files and all problems found have the \"ART_\" prefix.
"},{"location":"validation/#art_0001_invalidnamecpp","title":"ART_0001_invalidNameCpp","text":"Severity Reason Quick Fix Error An Art element has a name that is not a valid C++ name, or a name that will cause a name clash in the generated code. Prepend UnderscoreArt elements are translated to C++ elements without changing the elements' names. Hence you need to choose names for Art elements that are valid in C++. For example, C++ keywords cannot be used.
Furthermore, names of such Art elements must not clash with global names used by the TargetRTS or names within generated C++ files.
If you ignore this error you can expect errors when compiling the generated C++ code.
A Quick Fix is available that will fix the problem by adding an underscore to the beginning of the name, in order to make it a valid C++ name.
protocol InvalidNameProtocol {\n in virtual(); // ART_0001 (\"virtual\" is a C++ keyword)\n};\n\ncapsule Exception { // ART_0001 (\"Exception\" is a name reserved for use by the TargetRTS)\n\n};\n
"},{"location":"validation/#art_0002_duplicatenamesinscope","title":"ART_0002_duplicateNamesInScope","text":"Severity Reason Quick Fix Error Two or more Art elements in the same scope have the same names or signatures. N/A Names of Art elements must be unique within the same scope. The following is checked:
All elements with clashing names or signatures will be reported as related elements. Use this to find the element(s) that need to be renamed.
protocol DupProto { // ART_0002 (name clash for inEvent1)\n in inEvent1(); \n in inEvent1(); \n out inEvent1(); // OK (symmetric event)\n};\n\nclass DNIS {\n trigger op1(`int` p);\n trigger op1(); // OK (signatures are unique)\n statemachine {\n state State;\n initial -> State;\n };\n};\n
Note that inheritance brings inherited elements into a scope and this can cause name clashes too. However, a problem is only reported if at least one of the elements with conflicting names is defined locally.
capsule B0002 {\n statemachine {\n state State;\n initial -> State;\n junction j1;\n state Composite {\n state Nested;\n };\n };\n};\n\ncapsule D0002 : B0002 {\n statemachine { // ART_0002 (name clashes with B0002::State and B0002::j1)\n state State; \n choice j1; \n state redefine Composite { // ART_0002 (name clash with B0002::Composite::Nested)\n state Nested; \n };\n }; \n};\n\ncapsule C0002 : D0002 {\n\n statemachine { // OK. Even if this capsule inherits elements with conflicting names from D0002, none of them are defined in this state machine.\n };\n};\n
"},{"location":"validation/#art_0003_nameshouldstartwithuppercase","title":"ART_0003_nameShouldStartWithUpperCase","text":"Severity Reason Quick Fix Warning An Art element's name doesn't follow the naming convention to start with uppercase. Capitalize Name Just like in most languages Art has certain conventions on how elements should be named. The following elements should have names that start with an uppercase letter:
A Quick Fix is available that will fix the problem by capitalizing the name. Note, however, that it will only update the element's name, and not all references. If you have references to the element you may instead want to fix the problem by performing a rename refactoring (context menu command Rename Symbol).
capsule myCapsule { // ART_0003\n statemachine {\n state sstate; // ART_0003\n initial -> sstate; \n };\n};\n
In this context an underscore (_
) is considered a valid upper case character, so all names that start with underscore are accepted by this validation rule.
Just like in most languages Art has certain conventions on how elements should be named. The following elements should have names that start with a lowercase letter:
A Quick Fix is available that will fix the problem by decapitalizing the name. Note, however, that it will only update the element's name, and not all references. If you have references to the element you may instead want to fix the problem by performing a rename refactoring (context menu command Rename Symbol).
protocol LowerCaseTestProtocol {\n out MyEvent(); // ART_0004\n};\n
In this context an underscore (_
) is considered a valid lower case character, so all names that start with underscore are accepted by this validation rule.
If no outgoing transition of a choice is enabled at runtime (because no outgoing transition has a guard condition that is fulfilled) then the state machine will get stuck in the choice for ever. To avoid this you should ensure that at least one outgoing transition is enabled. A good way to do this is to use 'else' as the guard condition for one of the outgoing transitions. Such an else-transition will then execute if no other outgoing transition of the choice is enabled.
capsule ChoiceSample {\n statemachine {\n state State;\n initial -> State;\n choice x; // ART_0005\n State -> x;\n x -> State when `return getVal() == 5;`;\n };\n};\n
Note that a transition without any guard condition is equivalent to a transition with a guard condition that is always fulfilled (i.e. a guard condition that returns true). An outgoing transition from a choice or junction without any guard is therefore also an else-transition.
"},{"location":"validation/#art_0006_choicewithoutoutgoingtransitions","title":"ART_0006_choiceWithoutOutgoingTransitions","text":"Severity Reason Quick Fix Error A choice has no outgoing transitions. N/AA choice should typically have at least two outgoing transitions to be meaningful. Having only one outgoing transition is possible if it is an else-transition (i.e. a transition with an 'else' guard, or without any guard at all). However, a choice without any outgoing transition is not allowed since the state machine always will get stuck when reaching such a choice.
capsule ChoiceSample {\n statemachine {\n state State;\n initial -> State;\n choice x; // ART_0006\n State -> x; \n };\n};\n
"},{"location":"validation/#art_0007_choicewithtoomanyelsetransitions","title":"ART_0007_choiceWithTooManyElseTransitions","text":"Severity Reason Quick Fix Error A choice has more than one outgoing else-transition. N/A It's good practise to have an outgoing else-transition (i.e. a transition with an 'else' guard, or without any guard at all) for a choice since it will prevent the state machine from getting stuck in the choice at runtime. However, there should not be more than one such else-transition defined, since otherwise it's ambiguous which one of them to trigger in the case none of the other outgoing transitions from the choice are enabled.
capsule ChoiceSample {\n statemachine {\n state State, State2;\n initial -> State;\n choice x; // ART_0007\n State -> x;\n x -> State;\n x -> State2 when `else`;\n };\n};\n
"},{"location":"validation/#art_0008_initialtransitioncount","title":"ART_0008_initialTransitionCount","text":"Severity Reason Quick Fix Error A state machine has too many initial transitions, or no initial transition at all. N/A A state machine of a capsule or class must have exactly one initial transition. A common reason for this error is that you have introduced inheritance between two capsules which both have state machines with an initial transition. Because of that the derived capsule will have two initial transitions (the one it defines itself locally plus the one it inherits from the base capsule). In this case the error can be fixed by either deleting or excluding the initial transition from the derived capsule, or to let it redefine the initial transition from the base capsule.
capsule InitTransCap2 {\n statemachine {\n state State;\n initial -> State;\n };\n};\n\ncapsule InitTransCap3 : InitTransCap2 {\n statemachine { // ART_0008 (1 local initial transition and 1 inherited)\n state State2;\n initial -> State2; \n };\n};\n\ncapsule NoInitTrans { \n statemachine { // ART_0008 (no initial transition)\n state State;\n };\n};\n
Note that if the initial transition in the base capsule has no name, the derived capsule cannot exclude or redefine it. It's therefore good practise to name the initial transition if you expect your capsule to be inherited from.
The validation rule also checks nested state machines. Such a state machine doesn't need any initial transition, since the composite state can be entered via an entrypoint which takes the nested state machine to its first state. However, if the nested state machine has an initial transition there cannot be more than one.
capsule InitTransCap2 {\n statemachine { \n initial -> Composite;\n\n state Composite {\n state NS;\n initial -> NS;\n };\n };\n};\n\ncapsule InitTransCap3 : InitTransCap2 {\n statemachine {\n state redefine Composite { // ART_0008 (3 local initial transitions and 1 inherited)\n state S1, S2, S3;\n initial -> S1; \n initial -> S2; \n _ini: initial -> S3; \n };\n };\n};\n
"},{"location":"validation/#art_0009_invalidproperty","title":"ART_0009_invalidProperty","text":"Severity Reason Quick Fix Error A non-existing property is set for an element. Remove Property Most Art elements have properties that can be set to change their default values. Different elements have different properties and if you get this error it means you have referenced a non-existing property for an Art element.
A Quick Fix is available for removing the setting of the invalid property. Use Content Assist (Ctrl+Space) to get a list of valid properties for an Art element.
protocol IP_PROTO [[rt::properties(\n no_property = 4 // ART_0009 (a protocol has no property called \"no_property\")\n)]] {\n};\n\ncapsule CN \n[[rt::properties(\n colour=\"#ff0000\" // ART_0009 (misspelled property \"color\")\n)]]\n{\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0010_invalidpropertyvalue","title":"ART_0010_invalidPropertyValue","text":"Severity Reason Quick Fix Error A property is set to a value of incorrect type. N/A Most Art elements have properties and every property has a type that is either boolean, integer, string or an enumeration. The type of the value assigned to a property must match the property's type. For example, you cannot assign an integer value to a boolean property.
capsule IPV_Cap [[rt::properties(\n generate_file_header=4 // ART_0010 (\"generate_file_header\" is a boolean property)\n)]]{\n statemachine {\n state State;\n initial -> State;\n };\n}; \n
"},{"location":"validation/#art_0011_propertysettodefaultvalue","title":"ART_0011_propertySetToDefaultValue","text":"Severity Reason Quick Fix Warning A property is set to its default value. Remove Property Most Art elements have properties and every property has a default value. It's unnecessary to explicitly set a property to its default value.
A Quick Fix is available for removing the setting of the property.
capsule C_PropDefaultValue [[rt::properties(\n generate_file_header=true // ART_0011\n)]]{\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0012_invalidcodesnippet","title":"ART_0012_invalidCodeSnippet","text":"Severity Reason Quick Fix Error A code snippet is invalid in one way or the other. Remove Code Snippet A code snippet's kind is specified after the prefix rt::
. Different Art elements may have different kinds of code snippets. Also, some Art elements may have multiple code snippets of a certain kind, while others only may have one code snippet of each kind.
A Quick Fix is available for removing the invalid code snippet.
[[rt::header_preface]] // ART_0012 (code snippet for capsule/class placed at file level)\n`\n // YourCodeHere\n`\n\ncapsule Name {\n [[rt::unknown]] // ART_0012 (non-existing kind of code snippet)\n `\n // YourCodeHere\n `\n\n part x : OtherCap \n [[rt::create]]\n `\n return new DemoCap(rtg_rts, rtg_ref);\n `\n [[rt::create]] // ART_0012 (duplicated code snippet)\n `\n return new DemoCap(rtg_rts, rtg_ref);\n `;\n\n statemachine {\n state State;\n initial -> State;\n };\n}; \n
"},{"location":"validation/#art_0013_partmultiplicityerror","title":"ART_0013_partMultiplicityError","text":"Severity Reason Quick Fix Error The part's lower multiplicity must be less than its upper multiplicity. N/A If a part has a multiplicity that specifies a range (i.e. both a lower and upper multiplicity), then the lower multiplicity must be less than the upper multiplicity.
capsule PME_Cap {\n part myPart : OtherCap [2..2]; // ART_0013\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0014_partkindmultiplicityinconsistency","title":"ART_0014_partKindMultiplicityInconsistency","text":"Severity Reason Quick Fix Warning The part's kind is inconsistent with its multiplicity. N/A The multiplicity of a capsule part must match the part's kind. The following is checked:
In case any of these inconsistencies is detected, the faulty multiplicity will be ignored and a default multiplicity (see Part) will be used instead.
capsule PKMI_Cap { \n fixed part myPart : OtherCap [0..2]; // ART_0014 (Fixed part should not have lower multiplicity 0)\n optional part myPart2 : OtherCap [1..5]; // ART_0014 (Optional part should not have lower multiplicity > 0)\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0015_internaltransitionoutsidestate","title":"ART_0015_internalTransitionOutsideState","text":"Severity Reason Quick Fix Error An internal transition is defined outside a state, in the top state machine. N/A An internal transition specifies events that can be handled while a state machine is in a certain state without leaving that state. Hence it's only possible to define an internal transition inside a state. It does not make sense to define an internal transition directly in the top state machine.
capsule IntTransOutsideState {\n behavior port timer : Timing;\n\n statemachine {\n state State {\n t1 : on timer.timeout ` `; \n };\n initial -> State;\n terror : on timer.timeout ` `; // ART_0015\n };\n};\n
"},{"location":"validation/#art_0016_circularinheritance","title":"ART_0016_circularInheritance","text":"Severity Reason Quick Fix Error A capsule, class or protocol inherits from itself directly or indirectly. N/A When you use inheritance for capsules, classes and protocols you need to ensure there are no inheritance cycles. Cyclic inheritance means that an element would inherit from itself, directly or indirectly, which is not allowed.
Note
Both capsules and classes, but not protocols, may have C++ base classes specified by means of C++ code snippets. Such inheritance relationships are not checked by this validation rule, but by the C++ compiler.
The elements that form the inheritance cycle will be reported as related elements. Use this to decide how to break the inheritance cycle.
protocol PR1 : PR2 { // ART_0016\n\n};\n\nprotocol PR2 : PR1 { // ART_0016\n\n};\n\nclass C1 { \n statemachine {\n state State;\n initial -> State;\n };\n};\n\nclass C2 : C1 {\n statemachine {\n state State;\n initial -> State;\n };\n};\n\nclass C3 : C2, C4 { // ART_0016\n statemachine {\n state State;\n initial -> State;\n };\n};\n\nclass C4 : C3 { // ART_0016\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0017_circularcomposition","title":"ART_0017_circularComposition","text":"Severity Reason Quick Fix Error A capsule contains itself through a cycle in the composition hierarchy. N/A Parts of a capsule must form a strict composition hierarchy. At run-time the root of this hierarchy is the top capsule instance, and all other capsule instances in the application must be directly or indirectly owned by that capsule instance. For a fixed part the creation of contained capsule instances happen automatically when the container capsule is incarnated. It's therefore possible to statically analyze the fixed parts and check for cycles in the composition hierarchy.
Note
Only the static type of fixed capsule parts are used when looking for composition cycles. If a part has a capsule factory that specifies a create function using C++ code, then a different dynamic type may be specified for the created capsule instances for that part. This opens up for more possibilities of introducing cycles in the composition hierarchy that will not be detected by this validation rule.
The fixed parts that form the composition cycle will be reported as related elements. Use this to decide how to break the composition cycle.
capsule CComp2 { \n fixed part p2 : CComp3; // ART_0017\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n\ncapsule CComp3 { \n part p3 : CComp2; // ART_0017\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0018_circulartransitions","title":"ART_0018_circularTransitions","text":"Severity Reason Quick Fix Error A state machine has a cycle in the transitions that execute when leaving a junction or entry/exit point. N/A A junction or an entry or exit point can split an incoming transition flow into multiple outgoing transition flows based on evaluating guard conditions for the outgoing transitions. If care is not taken it's possible to introduce cycles in the outgoing transition flows. Even if the code generator also detects such cycles and prevents them from leading to infinite recursion at run-time, the transitions that form the cycle will not reach a state or choice when (or if, depending on the guard conditions) they execute. If guard conditions on such transitions have side-effects (which guard conditions should not have), it's possible that the application can work correctly even if there is a transition cycle. But it's very much recommended to change the state machine so it doesn't have any transition cycles.
The transitions that form the cycle will be reported as related elements. Use this to decide how to break the transition cycle.
capsule CT_cap {\n statemachine { // ART_0018\n state S1;\n initial -> S1;\n junction j1, j2;\n t1: S1 -> j1;\n t2: j1 -> j2;\n t3: j2 -> j1;\n };\n};\n
With entry and exit points a transition cycle can involve transitions in a nested state machine too.
capsule Cap0018 {\n statemachine { // ART_0018\n state State {\n entrypoint ep;\n exitpoint ex;\n junction j1;\n state N1;\n ep -> j1;\n j1 -> ex;\n };\n initial -> State.ep;\n State.ex -> State.ep; \n state Final;\n State.ex -> Final when `true`;\n };\n};\n
With state machine inheritance it's possible to introduce transition cycles when an inherited state machine is redefined or extended. This validation rule will also detect such cycles and report them on the inheriting state machine that contains the cycle.
"},{"location":"validation/#art_0019_unwiredportbothpublisherandsubscriber","title":"ART_0019_unwiredPortBothPublisherAndSubscriber","text":"Severity Reason Quick Fix Error An unwired port is declared as being both a subscriber and publisher at the same time. Make Publisher, Make SubscriberAn unwired port can at runtime be connected to another unwired port. One of the connected ports will be a publisher port (a.k.a SPP port) while the other will be a subscriber port (a.k.a SAP port). An unwired port can either be statically declared as being a publisher or subscriber port, or it can be dynamically decided at port registration time if the port should be a publisher or subscriber. The same port can not be both a subscriber and a publisher port at the same time.
Two Quick Fixes are available for making the port a publisher or a subscriber port by removing either the subscribe
or publish
keyword.
capsule UnwiredCapsule { \n subscribe publish behavior port p1 : UnwiredProtocol; // ART_0019\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0020_wiredportwithunwiredproperties","title":"ART_0020_wiredPortWithUnwiredProperties","text":"Severity Reason Quick Fix Warning A property that only is applicable for an unwired port is specified for a wired port. N/A An unwired port may have properties that control how it will be registered at runtime (see registration and registration_name). These properties have no meaning and will be ignored for wired ports.
capsule UnwiredCapsule2 {\n behavior port p1 [[rt::properties(\n registration_name=\"hi\"\n )]]: UnwiredProtocol; // ART_0020\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0021_unwiredportregnamesameasportname","title":"ART_0021_unwiredPortRegNameSameAsPortName","text":"Severity Reason Quick Fix Warning An unwired port is set to use a registration name that equals the name of the port. N/A When an unwired port is registered a name is used that by default is the name of the port. The property registration_name can be used for specifying another name. It's hence unnecessary to use that property for specifying the name of the port, since it is the default name that anyway would be used.
capsule UnwiredCapsule3 {\n unwired publish behavior port p1~ [[rt::properties(\n registration_name = \"p1\"\n )]]\n : UnwiredProtocol; // ART_0021 \n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0022_ruleconfigproblem","title":"ART_0022_ruleConfigProblem","text":"Severity Reason Quick Fix Warning The rule_config property has a malformed value. N/A The rule_config property can be set on Art elements to configure which validation rules to run for that element (and for all elements it contains). It can also be used for setting a custom severity for those rules. The value of the rule_config property should be a comma-separated list of 5 letter strings where the first letter specifies if the rule is disabled and it's severity (X,I,W,E) and remaining letters specify the rule id. See Configuring Validation for more information and examples.
capsule RCP [[rt::properties(\n rule_config=\"X0000\" // ART_0022 (a validation rule with id 0000 does not exist)\n)]]{\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0023_entryexitcodecount","title":"ART_0023_entryExitCodeCount","text":"Severity Reason Quick Fix Error A state has too many entry and/or exit actions. N/A A state can at most have one entry and one exit action. Solve this problem by merging all entry and exit actions of the state to a single entry and exit action that performs everything that should be done when the state is entered and exited.
capsule CX {\n statemachine {\n state Composite {\n entry // ART_0023\n `\n entry1();\n `;\n entry // ART_0023\n `\n entry2();\n `;\n };\n initial -> Composite;\n };\n};\n
"},{"location":"validation/#art_0024_unwiredportnotbehavior","title":"ART_0024_unwiredPortNotBehavior","text":"Severity Reason Quick Fix Error An unwired port is not defined as a behavior port. Make Behavior Port, Make Wired Port An unwired port cannot be connected to another port by means of a connector. Hence, it's required that an unwired port is defined to be a behavior port. Otherwise it would not be possible for the owner capsule to send and receive events on an unwired port.
capsule Pinger {\n service publish unwired port p1 : PROTO; // ART_0024\n\n statemachine {\n state State1;\n initial -> State1;\n };\n};\n
Two Quick Fixes are available for fixing this problem. Either the port can be turned into a behavior port, or it can be turned into a wired port.
"},{"location":"validation/#art_0025_portconnectionerror","title":"ART_0025_portConnectionError","text":"Severity Reason Quick Fix Error A wired port is not properly connected, or an unwired port is connected. N/AAn unwired port must not be connected to another port by means of a connector. Instead you should register such a port dynamically so that it can be connected at runtime with another matching port.
A wired port, however, should be connected. A service port that is not a behavior port should be connected both on the \"inside\" and on the \"outside\" by two connectors. That is because the purpose of such a relay port is to simply relay communication from one port to another. By \"inside\" we mean the composite structure of the capsule that owns the port, and by \"outside\" we mean the composite structure to which the part that is typed by the capsule belongs. If the service port is instead a behavior port, it should only be connected on the \"outside\".
The sample below contains several unwired ports that are connected (although they should not be). The validation rule also detects the problem that the service behavior port p1
is connected on the \"inside\". (The fact that it's also not connected on the \"outside\" is reported by ART_0039_portPartMultiplicityMismatch).
protocol PROTO {\n in in1();\n out out1();\n};\n\ncapsule Top { \n part ping : Pinger, // ART_0025 (unwired_port1 is connected in capsule Top) \n pong : Ponger; // ART_0025 (unwired_port2 is connected in capsule Top)\n\n part a : Another; // ART_0025 (behavior port p1 is connected on the inside)\n // ART_0039 (behavior port p1 is not connected on the outside)\n\n connect ping.unwired_port1 with pong.unwired_port2;\n\n service publish behavior port unwired_port1 : PROTO; // ART_0025 (publish implies unwired and unwired ports must not be connected)\n service subscribe behavior port unwired_port2~ : PROTO; // ART_0025 (subscribe implies unwired and unwired ports must not be connected)\n connect unwired_port1 with unwired_port2; \n\n unwired behavior port unwired_port3~ : PROTO; // ART_0025 (unwired ports must not be connected)\n\n connect ping.p2 with unwired_port3; \n\n statemachine {\n state T21;\n initial -> T21;\n };\n};\n\ncapsule Pinger {\n service publish behavior port unwired_port1 : PROTO;\n\n service port p2 : PROTO;\n\n statemachine {\n state State1;\n initial -> State1;\n };\n};\n\ncapsule Ponger {\n service subscribe behavior port unwired_port2~ : PROTO;\n\n statemachine {\n state State1; \n initial -> State1;\n };\n};\n\ncapsule Another {\n service behavior port p1 : PROTO; \n part inner : Inner;\n connect p1 with inner.p; \n};\n\ncapsule Inner {\n service behavior port p : PROTO;\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0026_connectedportswithincompatibleconjugations","title":"ART_0026_connectedPortsWithIncompatibleConjugations","text":"Severity Reason Quick Fix Error A connector connects two ports with incompatible conjugations. N/A Ports connected by a connector must have compatible conjugations. If the ports are at the same level in the capsule's structure (e.g. both ports belong to capsules typing capsule parts owned by the same capsule), then the connected ports must have opposite conjugations. This is because events that are sent out from one of the ports must be able to be received by the other port. However, if the ports are at different levels in the capsule's structure (e.g. one of them belongs to a capsule typing a capsule part owned by the capsule and the other belongs to the capsule itself), then the ports must have the same conjugation. This is because in this case events are simply delegated from one capsule to another.
capsule Top { \n part ping : Pinger, pong : Ponger; \n connect ping.p1 with pong.p2; // ART_0026 (same port conjugations but should be different)\n\n statemachine {\n state T21;\n initial -> T21;\n };\n};\n\ncapsule Inner {\n service behavior port p : PROTO;\n\n statemachine {\n state State;\n initial -> State;\n };\n};\ncapsule Pinger { \n service port p1~ : PROTO; \n part inner : Inner;\n connect p1 with inner.p; // ART_0026 (different port conjugations but should be same)\n\n statemachine {\n state State1;\n initial -> State1;\n };\n};\n\ncapsule Ponger {\n service behavior port p2~ : PROTO; \n\n statemachine {\n state State1; \n initial -> State1;\n };\n};\n
Here we see that both connectors are invalid. Port p2
and port p1
are at the same level in Top
's structure so their conjugations should be different, while port p1
and port p
are at different levels in Top
's structure so their conjugations should be the same.
Ports connected by a connector must have compatible protocols. For Code RealTime this means that the protocols must be the same.
Note
Model RealTime uses a different criteria for protocol compatibility. There two protocols are compatible if all events that can be sent by a port typed by the source protocol can be received by the other port typed by the target protocol. Also in Model RealTime the most common case is that the source and target protocols are the same, but they can also be different as long as all their events (both in-events and out-events) match both by name and parameter data type. This is a legacy behavior which is not recommended to use, and hence not supported by Code RealTime.
protocol PROTO1 { \n in pong(); \n out ping();\n};\n\nprotocol PROTO2 {\n in pong();\n out ping();\n};\n\nprotocol PROTO3 { \n in pong(); \n out ping3();\n};\n\ncapsule Top {\n service port p1 : PROTO1; \n service port p2~ : PROTO2;\n service port p3~ : PROTO3;\n\n connect p1 with p2; // ART_0027 (but OK in Model RealTime)\n connect p1 with p3; // ART_0027 (also not OK in Model RealTime due to event ping3)\n\n statemachine {\n state T21;\n initial -> T21;\n };\n};\n
"},{"location":"validation/#art_0028_unwiredportwithautoregneitherpublishernorsubscriber","title":"ART_0028_unwiredPortWithAutoRegNeitherPublisherNorSubscriber","text":"Severity Reason Quick Fix Error An unwired port is registered automatically but is neither specified as a publisher or subscriber. Make Publisher, Make Subscriber An unwired port is either a publisher (SPP) or subscriber (SAP) at run-time. Unless the port has the registration property set to application
it will be registered automatically when the container capsule instance gets created. Hence it's necessary in this case to declare the port either as a publisher or subscriber.
capsule CCXX {\n unwired behavior port pu // ART_0028\n [[rt::properties(\n registration=automatic_locked\n )]]\n : PPX;\n\n unwired service behavior port pu2 : PPX; // ART_0028 (default registration is \"automatic\")\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
Two Quick Fixes are available for fixing this problem. Either the port can be declared as a publisher (keyword publish
) or as a subscriber (keyword subscribe
).
If a composite state is entered without using an entry point, the behavior may be different the first time the state is entered compared to subsequent times it's entered. The first time the initial transition of the composite state will execute, while after that it will be entered using deep history (i.e. directly activate the substate that was previously active in the composite state). This difference in behavior is not evident just by looking at the state diagram, and can therefore be surprising and cause bugs. It's therefore recommended to always enter a composite state using an entry point. See Hierarchical Statemachines for more information.
capsule Cap {\n statemachine {\n state BS {\n entrypoint ep1;\n initial -> Nested;\n state Nested;\n };\n _Initial: initial -> BS; // ART_0029\n };\n};\n
"},{"location":"validation/#art_0030_transitiontocompositestatenoentrynoinitialtrans","title":"ART_0030_transitionToCompositeStateNoEntryNoInitialTrans","text":"Severity Reason Quick Fix Error A composite state is entered without using an entry point, and its state machine has no initial transition. N/A This validation rule is related to ART_0029_transitionToCompositeStateNoEntry. If a composite state is entered without using an entry point, and the nested state machine of the composite state has no initial transition, then it is undefined what to do when entering the state. This is therefore not allowed.
capsule Cap {\n statemachine {\n state BS {\n entrypoint ep1; \n state Nested;\n };\n _Initial: initial -> BS; // ART_0030\n };\n};\n
"},{"location":"validation/#art_0031_portbothnonserviceandnonbehavior","title":"ART_0031_portBothNonServiceAndNonBehavior","text":"Severity Reason Quick Fix Error A port is both a non-service and a non-behavior port at the same time. Make Behavior Port, Make Service Port A port that is not a service port is internal to a capsule. For such a port to be useful it must be a behavior port; otherwise the capsule cannot send and receive events on the port. Hence, a non-service port cannot at the same time be a non-behavior port.
Quick Fixes are available for either declaring the port as a behavior or service port.
capsule C31 {\n port fp : Proto; // ART_0031\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0032_unrecognizedcolor","title":"ART_0032_unrecognizedColor","text":"Severity Reason Quick Fix Warning A color is specified for an element but the color was not recognized. N/A A color can be assigned to most elements and will be used when showing the element on a diagram. Colors should be specified as RGB values using 6 hexadecimal digits. In case the color value is on another format it will not be recognized and will be ignored when rendering the diagram.
capsule C32 {\n behavior port frame [[rt::properties(color=\"#gd1d1d\")]]: Frame; // ART_0032 (invalid hex digit 'g')\n behavior port p [[rt::properties(color=\"#cc\")]] : Proto; // ART_0032 (too few digits)\n\n statemachine {\n state State [[rt::properties(color=\"#5d04040\")]]; // ART_0032 (too many digits)\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0033_connectednonserviceportonpart","title":"ART_0033_connectedNonServicePortOnPart","text":"Severity Reason Quick Fix Error A connector connects a port on a part but the port is not a service port. N/A A port is only visible from the outside of a capsule if it is a service port. Hence, a connector cannot connect a port on a part unless the port is a service port.
capsule C33 {\n optional part thePart : Other;\n behavior port bp~ : Proto; \n connect bp with thePart.bp2; // ART_0033\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n\ncapsule Other {\n behavior port bp2 : Proto;\n\n statemachine {\n state State;\n initial -> State;\n };\n}\n
In a structure diagram this error means that a connector \"crosses a capsule boundary\" by connecting two ports at different levels in the composite structure.
The solution here is to either make bp2
a service port, or to create another service port in the Other
capsule and then connect that port both to bp
on the \"outside\" and to bp2
on the \"inside\".
A service port is part of the externally visible communication interface for a capsule. Hence the protocol that types a service port should have at least one event, otherwise the service port doesn't add any value. The only exception is a notification port which receives the rtBound
and rtUnbound
events when the port gets connected or disconnected to another port at runtime. This means that a notification port can be useful even if its protocol doesn't contain any events.
protocol EmptyProtocol {\n};\n\ncapsule C34 {\n service port myPort : EmptyProtocol; // ART_0034\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0035_timerserviceport","title":"ART_0035_timerServicePort","text":"Severity Reason Quick Fix Warning A timer port is a declared to be a service port. Make Non-Service Behavior Port A timer port is typed by the predefined Timing protocol. It has one event timeout
which is sent to the port after a certain timeout period (either once or periodically). Other capsules cannot send the timeout
event to the capsule that owns the timer port. Hence a timer port should always be a non-service behavior port.
A Quick Fix is available that will remove the service
keyword for the port and if necessary also add the behavior
keyword.
capsule C35 {\n service port t : Timing; // ART_0035\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0036_unexpectedtriggers","title":"ART_0036_unexpectedTriggers","text":"Severity Reason Quick Fix Error A transition that originates from a pseudo state must not have triggers, but still has at least one. Remove Triggers A transition can only have triggers if it originates from a state, because it's only when the state machine is in a state that a received and dispatched message can trigger a new transition to execute. A transition that originates from a pseudo state (such as a choice or junction) must therefore not have any triggers.
Note that even if entry and exit points are pseudo states, the rule mentioned above does not apply for them unless they are connected with an incoming transition. If there is no incoming transition, an entry/exit point represents the enclosing state and the transition that leaves it can (in fact, should) have triggers. Read more about this here.
A Quick Fix is available that will remove the triggers of the transition (hence converting it from a triggered to a non-triggered transition).
capsule C36 {\n behavior port t : Timing;\n statemachine { \n state State, State1, State2;\n state Composite {\n entrypoint ep, ep2;\n exitpoint ex, ex2;\n state Nested;\n ep -> Nested on t.timeout; // OK (transition originates from entry point without incoming transition)\n ep2 -> Nested on t.timeout; // ART_0036 (transition originates from entry point with an incoming transition) \n Nested -> ex on t.timeout;\n };\n junction j;\n choice c;\n initial -> j;\n State -> Composite.ep2 on t.timeout;\n t1 : j -> State on t.timeout; // ART_0036 (transition originates from junction)\n t2 : State1 -> c on t.timeout;\n t3: c -> State2 on t.timeout; // ART_0036 (transition originates from choice)\n t4: Composite.ex -> State2 on t.timeout; // ART_0036 (transition originates from exit point)\n t5: Composite.ex2 -> State2 on t.timeout; // OK (transition originates from exit point without incoming transition)\n };\n};\n
In case the state machine is inherited, and the problem is detected for an inherited transition, then it is reported on the state machine instead. The inherited transition with the unexpected trigger(s) will be reported as a related element. In this case the Quick Fix can not be used for removing the triggers. Here is an example that shows how inheritance can cause a transition that is correct in a base state machine to become incorrect in a derived state machine:
capsule B2 {\n behavior port t : Timing;\n statemachine {\n state State;\n state Composite {\n entrypoint ep;\n state Nested;\n t1 : ep -> Nested on t.timeout; // OK (ep has no incoming transition here)\n };\n initial -> State;\n };\n};\n\ncapsule D2 : B2 { \n statemachine { // ART_0036 (here ep has an incoming transition which makes the inherited t1 incorrect)\n tx : State -> Composite.ep on t.timeout; \n };\n};\n
"},{"location":"validation/#art_0037_missingtriggers","title":"ART_0037_missingTriggers","text":"Severity Reason Quick Fix Error A transition that originates from a state must have at least one trigger, but has none. N/A A transition that originates from a state is only meaningful if it specifies as least one trigger. Otherwise the transition cannot be triggered and would be useless. As mentioned in this chapter an entry or exit point without incoming transition represents the state that owns the entry or exit point. A transition that originates from such an entry or exit point must therefore have a trigger.
capsule C37 {\n behavior port t : Timing; \n statemachine { \n state State, State1, State2, State3;\n state Composite {\n entrypoint ep, ep2;\n exitpoint ex, ex2;\n state Nested; \n t8: ep -> Nested; // ART_0037 (transition originates from entry point without incoming transition)\n ep2 -> Nested; // OK (transition originates from entry point with incoming transition)\n t6: Nested -> ex2; // ART_0037 (transition originates from state)\n }; \n t7: State -> Composite.ep2; // ART_0037 (transition originates from state)\n junction j;\n choice c;\n initial -> j;\n t1 : j -> State;\n t2 : State1 -> c; // ART_0037 (transition originates from state)\n t3: c -> State2; \n t4: Composite.ex -> State2; // ART_0037 (transition originates from exit point without incoming transition)\n t5: Composite.ex2 -> State3; // OK (transition originates from exit point with incoming transition)\n };\n};\n
In case the state machine is inherited, and the problem is detected for an inherited transition, then it is reported on the state machine instead. The inherited transition with the missing trigger will be reported as a related element. Here is an example that shows how inheritance can cause a transition that is correct in a base state machine to become incorrect in a derived state machine:
capsule BB2 {\n behavior port t : Timing;\n statemachine {\n state State;\n state Composite {\n entrypoint ep;\n state Nested;\n t1 : ep -> Nested; // OK (ep has an incoming transition here)\n };\n initial -> State;\n tx : State -> Composite.ep on t.timeout; \n };\n};\n\ncapsule DD2 : BB2 { \n statemachine { // ART_0037 (here ep has no incoming transition which makes the inherited t1 incorrect)\n exclude tx;\n };\n};\n
"},{"location":"validation/#art_0038_portwithpredefinedprotocolnotcorrectlydeclared","title":"ART_0038_portWithPredefinedProtocolNotCorrectlyDeclared","text":"Severity Reason Quick Fix Warning A port with a predefined protocol is declared in a way that is only applicable for ports with a user-defined protocol. N/A Ports typed by a predefined protocol (Timing, Log, External, Exception or Frame) cannot be used in the same way as ports that are typed by user-defined protocols. For example, it does not make sense to connect such ports with connectors since no events can be sent to them. Because of this, the following keywords are not applicable for such ports: notify
, publish
, subscribe
, unwired
.
If you use one or many of these keywords when declaring a port with a predefined protocol, they will be ignored and this problem will be reported. The problem will also be reported if you declare such a port as conjugated.
capsule C38 {\n unwired publish behavior port x : Timing; // ART_0038 ('unwired' and 'publish' not applicable)\n behavior port log~ : Log; // ART_0038 (cannot be conjugated)\n notify behavior port frame : Frame; // ART_0038 ('notify' not applicable)\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#art_0039_portpartmultiplicitymismatch","title":"ART_0039_portPartMultiplicityMismatch","text":"Severity Reason Quick Fix Warning The multiplicities of two connected ports are inconsistent, or a port is missing an expected connector. N/A Wired ports should be connected, and connected in a way that is consistent with their multiplicities. The following problems can otherwise occur at runtime:
In the example below a connector is missing between port p2
of part pong
and port p1
of part ping
. Without this connector no runtime connection can be established between ports p2
and p
which means that any message sent on those ports will be lost.
capsule Top { \n part ping : Pinger, // ART_0039 (not connected in capsule Top)\n pong : Ponger; // ART_0039 (not connected in capsule Top) \n\n statemachine {\n state T21;\n initial -> T21;\n };\n};\n\ncapsule Inner {\n service behavior port p : PROTO; \n\n statemachine {\n state State;\n initial -> State;\n };\n};\n\n\ncapsule Pinger {\n service port p1 : PROTO; \n part inner : Inner;\n connect p1 with inner.p; \n\n statemachine {\n state State1;\n initial -> State1;\n };\n};\n\ncapsule Ponger {\n service behavior port p2~ : PROTO; \n\n statemachine {\n state State1; \n initial -> State1;\n };\n};\n
In the example below the connectors connect ports and ports with parts where the multiplicities don't match.
capsule Top { \n part ping : Pinger; \n\n behavior port topP1 : PROTO[2]; // ART_0039\n behavior port topP2~ : PROTO; // ART_0039\n\n connect topP1 with topP2;\n\n statemachine {\n state T21;\n initial -> T21;\n };\n};\n\ncapsule Inner {\n service behavior port p~ : PROTO[2]; \n\n statemachine {\n state State;\n initial -> State;\n };\n};\n\ncapsule Pinger { \n part inner : Inner [4]; // ART_0039\n\n behavior port px : PROTO[3]; // ART_0039\n connect px with inner.p;\n behavior port px2 : PROTO[4]; // ART_0039\n connect px2 with inner.p;\n\n statemachine {\n state State1;\n initial -> State1;\n };\n};\n
Note that the runtime capacity of port p
is 8 (the port multiplicity times the part multiplicity; 2 * 4 = 8). This port is connected to two other ports (px
and px2
) with a total capacity of 4 + 3 = 7. Raising the multiplicity of px
to 4 will remove the inconsistency.
Some problems in an Art file cannot be detected until it's translated to C++ code. The code generator implements validation rules for detecting and reporting such problems.
Note
When an Art file is edited in the UI code generation validation rules run just after the Art validation rules. However, they will only run if there is an active TC, since otherwise code generation will not happen.
When you run the Art Compiler and the Art validation rules found at least one problem with Error severity, then code generation will not happen and hence these validation rules will not run.
Validation rules that are related to code generation can be enabled and disabled, and have their severity customized, in the same way as the Art validation rules. Code generation validation rules have the prefix \"CPP\" and ids in the range starting from 4000 and above. They are listed below.
"},{"location":"validation/#cpp_4000_eventtypewithouttypedescriptor","title":"CPP_4000_eventTypeWithoutTypeDescriptor","text":"Severity Reason Quick Fix Warning An event type has a type for which no type descriptor could be found. N/AIf an event has a data parameter the type of this parameter must have a type descriptor. Otherwise the TargetRTS doesn't know how to copy or move the data at run-time when the event is sent. The TargetRTS provides type descriptors for most predefined C++ types. For user-defined types the code generator assumes a type descriptor will be available for it (either automatically generated by means of the rt::auto_descriptor
attribute, or manually implemented). It's necessary that such a user-defined type is defined so that it can be referenced from the event with a simple name without use of qualifiers, type modifiers or template arguments. Use a typedef or type alias to give a simple name to an existing type that for example is defined in a different namespace.
If the code generator doesn't find a type descriptor for the event parameter type, CPP_4000 will be reported, and the C++ function that is generated for the event will have void type (i.e. the same as if the event doesn't have a data parameter).
protocol PROT {\n out e1(`MyClass*`); // ART_4000 (type modifier present)\n out e2(`std::string`); // ART_4000 (qualified name)\n out e3(`TplClass<int>`); // ART_4000 (template parameter present) \n};\n
"},{"location":"validation/#cpp_4001_unreachabletransition","title":"CPP_4001_unreachableTransition","text":"Severity Reason Quick Fix Warning One or many transitions are unreachable due to ambiguous triggers. N/A If there are multiple outgoing transitions from a state with identical triggers (i.e. having the same port and the same event, and no guard condition), then it's ambiguous which one of them that will be triggered. In this situation the code generator will pick one of them to execute, and report the other ones as unreachable. The unreachable transitions will be reported as related elements so you can navigate to them and decide how to resolve the ambiguity.
Common solutions for this problem is to add a guard condition, either on the trigger or the transition. With a guard condition the ambiguity is resolved, but it's of course then important to ensure that guard conditions are mutually exclusive so that only one of the transitions can execute. The code generator cannot ensure this, since guard conditions are evaluated at run-time.
capsule XCap { \n behavior port t : Timing;\n statemachine {\n state State1, State2;\n initial -> State1 \n `\n t.informIn(RTTimespec(1,0));\n `;\n t1: State1 -> State2 on t.timeout;\n t2: State1 -> State2 on t.timeout; // CPP_4002\n };\n};\n
Code for calling unreachable transitions will still be generated, but preceeded with a comment. Here is an example:
case Timing::Base::rti_timeout:\n chain2_t1( );\n return ;\n // WARNING: unreachable code;\n chain3_t2( );\n return ;\n
"},{"location":"validation/#cpp_4002_guardedinitialtransition","title":"CPP_4002_guardedInitialTransition","text":"Severity Reason Quick Fix Error The initial transition leads to a junction where all outgoing transitions have guard conditions. N/A The initial transition is the first transition that executes in a state machine. It must always lead to the activation of a state where the state machine will stay until it receives its first event. It is allowed to use junctions in the initial transition to let guard conditions decide which state that should be activated. However, in this case it's required that there is at least one junction transition without a guard condition (or with an else
guard). If all transition paths are guarded there is a risk that none of the guard conditions will be fulfilled, which would mean that no state will be activated. That would effectively break the functioning of the state machine.
capsule N { \n statemachine {\n state State;\n junction j;\n initial -> j;\n j -> State when `x == 5`; // CPP_4002\n };\n};\n
"},{"location":"validation/#tc-validation-rules","title":"TC Validation Rules","text":"TC files are validated to detect problems related to TC properties. The rules that perform this validation can be enabled and disabled, and have their severity customized, in the same way as the Art validation rules (with the exception that it's only possible to configure these rules globally). They use the prefix \"TC\" and ids in the range starting from 7000 and above. These rules are listed below.
"},{"location":"validation/#tc_7000_wrongvaluetype","title":"TC_7000_wrongValueType","text":"Severity Reason Quick Fix Error A TC property has the wrong type of value. N/AEach TC property has a type as shown in this table. The value provided for a TC property must have the expected type. Here are some examples where TC property values have types that don't match the types of these TC properties:
tc.copyrightText = true; // TC_7000 (expects a string, and not a boolean)\ntc.cppCodeStandard = 98; // TC_7000 (expects an enum string such as \"C++ 98\", and not a number)\ntc.sources = ''; // TC_7000 (expects a list of strings, and not a single string)\n
"},{"location":"validation/#tc_7001_tcpropertynotyetsupported","title":"TC_7001_tcPropertyNotYetSupported","text":"Severity Reason Quick Fix Warning A TC property is assigned a value, but Code RealTime does not yet support this property. N/A TC files are not only used by Code RealTime but also by Model RealTime. Even if the format of TC files is the same in these products, there are certain TC properties which are supported by Model RealTime, but not yet supported by Code RealTime. You can still assign values to such properties but they will be ignored.
tc.compilationMakeInsert = ''; // TC_7001\n
"},{"location":"validation/#tc_7002_propertynotapplicableforlibrarytc","title":"TC_7002_propertyNotApplicableForLibraryTC","text":"Severity Reason Quick Fix Warning A TC property that is only applicable for an executable TC is used in a library TC. N/A Certain TC properties are only meaningful if used in a TC that builds a library. For example, setting linkCommand
does not make sense on a library TC since a library is not linked.
let tc = TCF.define(TCF.CPP_TRANSFORM);\n// The \"topCapsule\" property is not set, which means this is a library TC\ntc.linkCommand = 'ld'; // TC_7002\n
"},{"location":"validation/#tc_7003_prerequisitepatherror","title":"TC_7003_prerequisitePathError","text":"Severity Reason Quick Fix Error A prerequisite TC cannot be resolved. N/A TCs that are specified as prerequisites must exist. If the path cannot be resolved to a valid TC file then it must either be corrected or deleted. A relative path is resolved against the location of the TC where the prerequisites
property is set.
tc.prerequisites = [\"../../TestUtils/testlibX.tcjs\"]; // TC_7003 (referenced TC file does not exist)\n
"},{"location":"validation/#tc_7004_invalidtopcapsule","title":"TC_7004_invalidTopCapsule","text":"Severity Reason Quick Fix Error The specified top capsule cannot be found. N/A The topCapsule
property is mandatory for executable TCs. In fact, it's the presence of this property that makes it an executable TC. A capsule with the specified name must exist in one of the Art files that is built by the TC (directly or indirectly). If you specify a top capsule that cannot be found, make sure you have spelled it correctly (use Content Assist in the TC editor so you can avoid typos). Also make sure that the Art file where the top capsule is defined is not excluded from the build by use of the sources
property.
tc.topCapsule = 'NonExistentCapsule'; // TC_7004 (referenced top capsule does not exist)\n
"},{"location":"validation/#tc_7005_invalidunitname","title":"TC_7005_invalidUnitName","text":"Severity Reason Quick Fix Error The unitName
property contains characters that are illegal in a file name. N/A The unitName
property specifies the name of the generated unit files (by default called UnitName.h
and UnitName.cpp
). Hence, it cannot contain characters that are not allowed in a file name. Different operating systems have different rules that a valid file name must adhere to.
tc.unitName = 'UnitName:1'; // TC_7005 (colon is not a valid file name character on Windows)\n
"},{"location":"validation/#tc_7006_invalidtargetrtslocation","title":"TC_7006_invalidTargetRTSLocation","text":"Severity Reason Quick Fix Error The specified path to the TargetRTS does not exist or is invalid. N/A The targetRTSLocation
property must specify a folder that exists and contains a TargetRTS to compile generated code against.
tc.targetRTSLocation = \"C:\\\\MyTargets\\\\\"; // TC_7006 (if that folder does not exist)\n
"},{"location":"validation/#tc_7007_invalidtargetconfig","title":"TC_7007_invalidTargetConfig","text":"Severity Reason Quick Fix Error The specified target configuration does not exist. N/A The targetConfiguration
property must specify the name of a target configuration that exists in the folder specified by the targetRTSLocation
property. If you specify a target configuration that cannot be found, make sure you have spelled it correctly (use Content Assist in the TC editor so you can avoid typos).
tc.targetConfiguration = \"WinT.x64-MinGW-12.2.0\"; // TC_7007 (misspelled \"MinGw\")\n
"},{"location":"validation/#tc_7008_invalidcodestandard","title":"TC_7008_invalidCodeStandard","text":"Severity Reason Quick Fix Error The specified C++ code standard does not exist. N/A The cppCodeStandard
property must specify a valid C++ code standard. If you specify a code standard that cannot be found, make sure you have spelled it correctly (use Content Assist in the TC editor so you can avoid typos).
tc.cppCodeStandard = \"C++ 18\"; // TC_7008 (there is no C++ language standard C++ 18)\n
"},{"location":"validation/#tc_7009_invalidtargetfolder","title":"TC_7009_invalidTargetFolder","text":"Severity Reason Quick Fix Error The specified target folder is invalid. N/A The targetFolder
property specifies the folder where to place generated files. The folder doesn't have to exist, since it will be created automatically by the code generator if needed. However, it's required that the folder has a name that is valid. Different operating systems have different rules that a valid folder name must adhere to.
tc.targetFolder = 'capsule_cpp_inheritance_target:'; // TC_7009 (invalid character ':' in target folder)\n
"},{"location":"validation/#tc_7010_physicalthreadwithoutlogicalthread","title":"TC_7010_physicalThreadWithoutLogicalThread","text":"Severity Reason Quick Fix Warning A physical thread has no logical thread mapped to it. N/A A physical thread is referenced through a logical thread that is mapped to it in the TC. Multiple logical threads can be mapped to the same physical thread, but if a physical thread has no logical threads mapped to it, it's useless since it then cannot be referenced by the application. Note that this rule does not apply for the default MainThread and TimerThread.
Solve this problem either by deleting the Thread object that represents the physical thread from the TC, or map a logical thread to it using the logical
property.
See the threads
property for more information.
tc.threads = [\n{\n name: 'MyThread',\n logical: [ ] // TC_7010\n}\n];\n
"},{"location":"validation/#tc_7011_duplicatephysicalthreadname","title":"TC_7011_duplicatePhysicalThreadName","text":"Severity Reason Quick Fix Error There are multiple physical threads with the same name. N/A The names of physical threads in an application must be unique. See the threads
property for more information.
tc.threads = [\n{\n name: 'MyThread', \n logical: [ 'L1' ] \n},\n{\n name: 'MyThread', // TC_7011 (MyThread already defined)\n logical: [ 'L2' ] \n}\n];\n
"},{"location":"validation/#tc_7012_duplicatelogicalthreadname","title":"TC_7012_duplicateLogicalThreadName","text":"Severity Reason Quick Fix Error There are multiple logical threads with the same name. N/A The names of logical threads in an application must be unique. In a library TC the logical threads are specified as a list of strings in the threads
property and this list should not contain duplicates. In an executable TC the logical threads are instead defined implicitly when mapping them to physical threads using the logical
property on the Thread object. Also in this case, the same logical thread name should not be used more than once.
tc.threads = [\n{\n name: 'MyThread', \n logical: [ 'L1', 'L1', 'L2' ] // TC_7012 (L1 defined (and mapped to MyThread) twice)\n},\n{\n name: 'MyThread2', \n logical: [ 'L2' ] // TC_7012 (L2 already mapped to MyThread above)\n}\n];\n
A special situation is when an executable TC has several library TCs as prerequisites (direcly or indirectly). These library TCs may define logical threads with clashing names. You must make sure that names of logical threads in all prerequisite libraries are unique. One way to accomplish this could be to prefix a logical thread in a library with the name of the library.
"},{"location":"validation/#tc_7013_physicalthreadsinlibrary","title":"TC_7013_physicalThreadsInLibrary","text":"Severity Reason Quick Fix Warning Physical threads are defined in a library TC. N/AIn a library TC you should only define logical threads. These must be mapped to physical threads in the executable TC which has the library TC as a prerequisite. If you anyway define physical threads in a library TC, they will be ignored.
See the threads
property for more information.
// tc.topCapsule not defined, i.e. this is a library TC\ntc.threads = [ // TC_7013\n{\n name: 'MyThread', \n logical: [ 'L1' ] \n}\n];\n
"},{"location":"validation/#tc_7014_incorrectthreadproperty","title":"TC_7014_incorrectThreadProperty","text":"Severity Reason Quick Fix Error A thread property is incorrectly specified. N/A This problem is reported if the threads
property contains a value of unexpected type. For an executable TC this property should be set to a list of Thread objects representing physical threads, while for a library TC it should be set to a list of strings which are the names of the logical threads defined in the library.
The problem is also reported if a Thread object for a physical thread has a property of unexpected type. All properties of such a Thread object should be of string type, except logical
which should be a list of strings.
tc.threads = [ \n{\n name: 'MyThread',\n priority: 20000, // TC_7014 (the priority should be specified as a string and not a number)\n logical: [ 'L1' ] \n}\n];\n
"},{"location":"validation/#tc_7015_librarythreadnotmappedtophysicalthread","title":"TC_7015_libraryThreadNotMappedToPhysicalThread","text":"Severity Reason Quick Fix Error A logical thread in a library TC is not mapped to a physical thread. N/A A library TC uses the threads
property for specifying logical threads. When an executable TC uses a library TC as its prerequisite, all logical threads of the library must be mapped to physical threads. Read more about library threads here.
// In a library TC lic.tcjs:\ntc.threads = [ 'LibThread1', 'LibThread2' ];\n\n// In an executable TC exe.tcjs:\ntc.prerequisites = [\"lib.tcjs\"];\ntc.threads = [ \n{\n name: 'MyThread',\n logical: [ 'LibThread1' ] // TC_7015 (library logical thread 'LibThread2' not mapped)\n}\n];\n
"},{"location":"validation/#tc_7016_prerequisiteswithoutactivetc","title":"TC_7016_prerequisitesWithoutActiveTC","text":"Severity Reason Quick Fix Warning A TC has prerequisites specified, but its workspace folder has no active TC. Set TC as active Setting a TC as active impacts on how references in an Art file are resolved. If your TC has one or many prerequisites, it's typical that the Art files built by the TC have at least some references to Art elements that are built by those prerequisite TCs. For those references to be possible to resolve, it's required to set the TC as active. This validation rule helps you remember to do so by printing a warning if it finds a TC with prerequisites that is located in a workspace folder without an active TC.
A Quick Fix is available that will fix the problem by setting the TC as active. If your workspace folder contains more than one TC with prerequisites, the warning will be reported for all of them, and you then need to decide which of the TCs that should be made active.
Note that the concept of an active TC is not applicable when you build an application with the Art Compiler, because then you always specify explicitly which TC that should be built. Therefore, this validation rule only runs in the IDE, and not in the Art Compiler.
"},{"location":"validation/#core-validation-rules","title":"Core Validation Rules","text":"There are certain core rules that run before the semantic validation rules mentioned above. They are responsible for making sure that the Art file is syntactically correct and that all references it contains can be successfully bound to valid Art elements.
Since these rules run before semantic validation rules they cannot be disabled or have their severity changed. Core validation rules have ids in the range starting from 9000 and above and are listed below.
"},{"location":"validation/#art_9000_syntaxerror","title":"ART_9000_syntaxError","text":"Severity Reason Quick Fix Error Parsing of the Art file failed due to a syntax error. Remove Extraneous InputIf an Art file contains a syntax error, it cannot be parsed into valid Art elements and the parser will then report this error. There are many ways to introduce syntax errors in an Art file and the error message from the parser will usually help you understand what is wrong by telling you both what incorrect thing was encountered and what is instead expected at that position.
If the syntax error is caused by extra characters at a place where no extra characters are expected a Quick Fix can be used for removing the \"extraneous input\".
capsule A { \n state machine // ART_9000 (mismatched input 'state' expecting 'statemachine')\n};\n\ncapsule B { \n statemachine {\n state State;\n initial -> State;\n };\n prt // ART_9000 (extraneous input 'prt' expecting '}')\n};\n
"},{"location":"validation/#art_9001_unresolvedreference","title":"ART_9001_unresolvedReference","text":"Severity Reason Quick Fix Error A referenced Art element cannot be found. N/A For an Art file to be well-formed, all references it contains must be possible to resolve to valid Art elements (located either in the same Art file, or in another Art file in the workspace). Aside from simple spelling mistakes, the most common reason for this error is that you forgot to add the folder that contains the Art file with the target Art element into the workspace. Also note that if the referenced Art element is in a different workspace folder you must have an active TC which specifies a TC in that workspace folder as a prerequisite.
capsule C { \n port p : Unknown; // ART_9001 (Couldn't resolve reference to Protocol 'Unknown'.)\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"validation/#internal-errors","title":"Internal Errors","text":"A special validation rule is used for detecting and reporting so called internal errors. These are errors that should never occur, but if they still do they are caused by a defect in Code RealTime. If you encounter an internal error please report it as described here.
"},{"location":"validation/#art_9999_internalerror","title":"ART_9999_internalError","text":"Severity Reason Quick Fix Error An internal error has occurred. N/AInternal errors may arise from bugs and often result from unexpected situations. While it may be possible to workaround an internal error, the problem can only be fully solved by updating Code RealTime. Therefore, the first thing you should do if you get an internal error is to make sure you are running the latest version of Code RealTime (see Releases). If you don't, then please uplift to the latest version as there is a chance the problem has been fixed in that version. If that doesn't help, please report the internal error as described here.
"},{"location":"art-lang/","title":"The Art Language","text":"Art is a language for developing stateful and event-driven realtime applications. By stateful we mean that the application consists of objects whose behavior can be described with state machines. By event-driven we mean that these objects communicate with each other by sending events, which can cause their state machines to transition from one state to another when received.
The Art language provides high-level concepts not directly found in the C++ language. All these high-level concepts are transformed into C++ code by the Art compiler. Generated code uses a run-time library known as the TargetRTS (Target RunTime System). The TargetRTS is a C++ library that acts as a layer between the generated code and the underlying platform (hardware, operating system etc) on which the realtime application runs.
Art is well suited for describing both the behavior and structure of a realtime application, but it uses C++ as expression and action language. C++ is also used for declaring types, variables, functions etc. As a rule of thumb, Art uses C++ for everything where C++ is a good fit, and only provides new language concepts where no appropriate constructs exist in C++. This means that if you already know C++, you can quickly learn Art too, and existing C++ code you have already written can be used in your Art application.
Note that the translation of Art to C++ also involves analysis of the C++ code that is present in the Art files. The code generator supports certain C++ extensions in such embedded C++ code and will \"expand\" them to C++ code as part of code generation for an Art file.
"},{"location":"art-lang/#concepts-and-terminology","title":"Concepts and Terminology","text":"In Art the concept of a capsule is central. A capsule is like a C++ class, but with a few differences and extensions. A C++ class is passive in the sense that a caller can access its public member functions and variables at any time. Hence a C++ object always executes in the context of the caller, and when a member function is called, the caller is blocked until the function call returns. A capsule, however, is active and has its own execution context. This means that we never call a capsule member function or access a capsule member variable from outside the capsule itself. Instead we communicate with the capsule by sending events to it. Each capsule instance has a queue of events it has received and those events will be dispatched to the capsule instance one by one. The sender of the event is not blocked, as the event will be handled by the capsule instance asynchronously when it is later dispatched.
The picture below shows 3 capsule instances each holding a queue with events that have been received, but not yet dispatched. Note that this picture is conceptual. In a real implementation several performance optimizations are applied, for example it's common to let a single thread drive more than one capsule instance, and several capsule instances can share a common event queue. But from a conceptual point of view each capsule instance has its own queue of events that are waiting to be dispatched to it. Events have a priority which determines how they are ordered in the queue. Events with high priority are placed before events with lower priority, and if two events have the same priority they are ordered according to when they arrive.
A capsule may have ports. A port is typed by a protocol which defines the events that may be sent in to the port (these are known as in-events), as well as the events the capsule itself may send out through the port for others to receive (these are called out-events). Ports can be used both for internal and external communication. A port used for external communication is called a service port. Together, the service ports constitute the communication interface of the capsule, and decide what \"services\" the capsule provides for other capsules to use.
A simple capsule which only handles a small number of events, may be able to handle all these events using a single state machine. However, when new ports are added (or new events in protocols typing existing ports), the capsule interface grows and the state machine has to grow with it, since there will be more events for it to handle. Eventually a point is reached where it will not be practical for a capsule to handle any more events in its own state machine, because it has grown too large or complex. If not before, this is the time to define a composite structure for the capsule.
A composite structure is created by decomposing a capsule using capsule parts. A capsule part (or, for simplicity, just part) is typed by another capsule and is a way for a capsule to delegate some of its responsibilities to other capsules. Such a decomposition is purely an implementation detail that is not visible from the outside of the capsule. When you send an event to a capsule you cannot know if the capsule will handle the event itself, or if it will forward the event to another capsule typing one of its capsule parts. The ability to decompose a capsule into parts is important for managing complexity. When a capsule has grown too big and complex you can decompose it into capsule parts without changing the communication interface of the capsule.
Ports of capsules typing capsule parts are connected to each other by means of connectors. A connector is a conceptual construct for showing how events are routed in the composite structure of a capsule. At run-time connectors don't exist, and ports are directly connected to each other. Because of this, it's not mandatory to use connectors. You can also choose to dynamically connect (and disconnect) ports at run-time. Although this provides for more flexibility, it has the drawback of making it impossible to statically visualize the communication paths of a capsule. Ports that connect statically to other ports via connectors are called wired ports. Ports that are connected dynamically without use of static connectors are called unwired ports.
The picture below shows the structure of a capsule Top
which consists of two capsule parts ping
and pong
each holding a capsule instance (a Pinger
capsule and a Ponger
capsule respectively). The connector between the wired ports p
on these capsules makes it possible for these capsules to communicate with each other. Communication can also happen using the unwired ports q1
and q2
if they are connected at run-time. The picture also shows that the capsule Ponger
is further decomposed using a capsule part inner
. All events sent to port p
of Ponger
will be further routed to port i
of the Internal
capsule.
Regardless if ports are statically connected by connectors (wired ports), or dynamically connected at run-time (unwired ports), they must be compatible with each other. This means that the out-events of one port must match the in-events of the other port, for the ports to be possible to connect. This constraint ensures that events are never lost when traveling between two connected ports. To make it possible to describe the events that may be sent between two connected ports using a single protocol, one of the ports can be declared as conjugated. For a conjugated port the meaning of in-events and out-events are swapped, so that the in-events are the events that may be sent out through the port, and the out-events are the ports that may be sent to the port. In the picture above port q1
is non-conjugated () while port q2
is conjugated ().
Both capsule parts and ports may have multiplicity. You can think about a capsule part with multiplicity > 1 as an array that holds capsule instances at run-time. In the same way you can think about a port with multiplicity > 1 as an array that holds connections to port instances at run-time. The multiplicity of ports and parts must match when connecting two ports with each other. Once again, this constraint ensures that events will not be lost when traveling between the connected ports at run-time. The picture below shows a capsule with a part and a port that both have multiplicity > 1. In structure diagrams such parts and ports are shown as \"stacked boxes\".
In addition to regular C++ member functions a capsule may have a state machine as its behavior. A state machine describes how an instance of the capsule may move between different states through its life-time. A transition that connects a source state with a target state may be triggered when a received event from a capsule's event queue is dispatched. Several conditions must hold true for the transition to trigger. For example, the event must match a trigger that specifies the expected type of event and the port on which it was received. It's also possible to associate a boolean guard condition with the transition and/or with the trigger which must be true for the transition to trigger. A transition may have an effect, which is a piece of C++ code that executes when the transition gets triggered.
The picture below shows a state machine containing a few states and transitions. The presence of transition guard code is shown with a yellow dot and the presence of transition effect code is shown with a blue dot. Both these are C++ code snippets that are embedded in the Art file.
When a capsule instance is created (this is sometimes referred to as capsule incarnation), it's state machine starts to execute by triggering the transition that goes out from the initial state (the circular blue symbol to the left in the above diagram). Each state machine must have exactly one such initial state with an outgoing transition. Since this initial transition is triggered automatically when the capsule instance is created it cannot have constraints such as triggers and guard conditions. The initial transition is an example of a non-triggered transition since it cannot have triggers.
The path from the source state to the target state can sometimes consist of more than one transition. In that case only the first of these is a triggered transition that may have triggers that specify when it will trigger. Once the first transition in this path has triggered, subsequent non-triggered transitions will always execute, one by one according to how they are connected in the state machine. However, also non-triggered transitions (with the exception of the initial transition) may have guards. Such guards are usually evaluated before the triggered transition triggers to ensure that they all are enabled, so that it's guaranteed that the target state can be reached. There is one exception to this rule, for transitions that leave a choice. Such guards are only evaluated once the choice has been reached to dynamically decide which outgoing transition to take next. This also means that guards of such transitions must be written so that at least (and at most) one outgoing transition is enabled, or there is a risk that the state machine will get stuck in the choice.
In the state machine shown below the transitions t2
and t5
are triggered transitions, while other transitions are non-triggered. Transition t5
can only be triggered if either the guard of t7
or t6
is true, while t2
can be triggered even if neither the guard of t3
nor t4
is true. The target of transition t5
is a junction which is used for either splitting or merging transition paths depending on evaluated guard conditions.
A state may be decomposed by a sub state machine. Such a state is called a composite state and a state machine that has composite states is called a hierarchical state machine. Transitions enter a composite state through an entry point and exit it through an exit point.
Usually an entry point is connected to a nested state inside the state machine of the composite state, but it can also connect to a deep history. Reaching the deep history of a composite state means that all sub states that were previously active will become active again. Hence, deep history is a way to restore a composite state so all its nested states will be reactivated again recursively.
The picture below shows a state machine with a composite state Composite
containing two nested states S1
and S2
. When this state machine starts to execute state S1
first becomes active since Composite
is entered using the ep1
entry point. Later, when leaving S2
through the ex1
exit point, state X
becomes active. Then when leaving X
through the transition that connects to the ep2
entry point the state S1
once again becomes active since ep2
is connected to the deep history. Of course, whenever a nested state is active, the enclosing composite state is also active. At any point in time a state machine has an active state configuration, which consists of the set of currently active states.
A state may have an entry action and/or exit action which is a C++ code snippet that gets executed whenever the state is entered or exited. Note that state entry actions for nested states also run when those states are entered because of a deep history. In state diagrams the presence of entry and/or exit actions are shown by icons just below the state name. In the state machine shown below state S1
has an entry action, state S2
has an exit action and state S3
has both an entry and an exit action.
A transition where the source and target state is the same state is called a self-transition. A special kind of self-transition is an internal transition, which is a transition that when triggered doesn't leave the current state. Hence, when an internal transition is triggered the active state configuration remains unchanged, and neither the entry nor exit action of the state gets executed. In the state machine shown below the state has two self-transitions; t
which is a regular self-transition (a.k.a. external self-transition) and it
which is an internal transition. Since a state may have a large number of internal transitions they are not shown inside the state symbol, but if you select the state symbol you can see them in the Properties view. An icon is shown in the upper right corner of states that contain internal transitions.
State machines can not only be defined for capsules but also for regular classes. This can be useful if you want a plain passive C++ class to have a state machine. Contrary to a capsule a class may not have ports and doesn't execute in its own context. It's therefore common to associate such a class with a capsule that it can use for sending events through its ports. Transitions of a passive class state machine are triggered by calling trigger operations on the class. Such operations have no code, but just trigger transitions in the class state machine.
The realtime application needs to designate one capsule as the top capsule. This is done in the transformation configuration, which is a file containing all the properties used for building the application (e.g. code generator options, compiler settings etc.). There is no language construct in Art for defining a top capsule; any capsule that you define can act as the top capsule. However, in practise you typically decide at an early stage which capsule that will be the top capsule.
The top capsule is the entry point of the realtime application. When it starts to execute one instance of the top capsule will be automatically created, and its state machine starts to execute. If you build a library rather than an executable you don't have a top capsule.
"},{"location":"art-lang/#embedded-c-code","title":"Embedded C++ Code","text":"Art uses C++ as action and expression language. It also uses C++ for defining types, variables and functions. A C++ code snippet can be embedded into an Art file at many places by enclosing it with backticks. Here is an example of how to write the code that should execute when a transition triggers:
S1 -> S2 on timer.timeout\n`\n std::cout << \"Hello World!\" << std::endl;\n`;\n
Here is another example that shows how to include some C++ code as the implementation preface of a capsule:
capsule BrewControl {\n [[rt::impl_preface]]\n `\n #include <iostream>\n `\n};\n
If the code snippet needs to contain the backtick character, for example in a comment or a string literal, you need to escape it by preceeding it with a backslash (\\
). For example:
capsule StringProcessor {\n [[rt::decl]]\n `\n // A member variable with default value \\`\n char escapeChar = '\\`'; \n `\n statemachine {\n state State;\n initial -> State;\n };\n}; \n
"},{"location":"art-lang/#file-level-code-snippets","title":"File-level Code Snippets","text":"Code snippets can not only be associated with Art language constructs as in the above two examples, but can also be placed at the Art file level. There are two such file-level code snippets:
May contain arbitrary C++ declarations. All these code snippets will be generated into a C++ header file with the same name as the Art file.
May contain arbitrary C++ implementations. All these code snippets will be generated into a C++ implementation file with the same name as the Art file.
As an example, assume we have an Art file sample.art
with the following contents
[[rt::decl]]\n`\n typedef C* Cptr;\n Cptr func1();\n`\n[[rt::impl]]\n`\n Cptr func1() {\n return nullptr;\n }\n`\n
Two C++ files will be generated from this Art file:
sample.art.h
typedef C* Cptr;\nCptr func1();\n
sample.art.cpp
#include \"sample.art.h\"\n\nCptr func1() {\n return nullptr;\n}\n
File-level code snippets are useful whenever you need to include some C++ code in your application that doesn't naturally belong to any particular Art element. They can for example be used for declaring and implementing utility functions or types that are needed by many different Art elements. To use the declared elements from an Art element, you need to add an #include
for the generated header file using a code snippet on the Art element. Note that an #include
is needed even if the Art element is located in the same Art file as the declared elements it wants to use.
Below is an example that shows how a protocol and a capsule can use the type Cptr
defined in sample.art
by adding #include
s:
protocol MyEvents {\n [[rt::header_preface]]\n `\n #include \"sample.art.h\"\n `\n out alert(`Cptr`);\n};\n\ncapsule Cx {\n [[rt::header_preface]]\n `\n #include \"sample.art.h\"\n `\n [[rt::decl]]\n `\n protected:\n Cptr m_ptr; \n `\n // ...\n};\n
Here an rt::header_preface
code snippet is used for making the generated capsule and protocol header files include sample.art.h
while an rt::decl code snippet is used for declaring a member variable m_ptr
for the capsule. See the documentation of the different Art elements below to learn about what code snippets that are available for each kind of Art element.
Hint
As an alternative to placing common C++ code in an Art file you can also use regular C++ files and then include these into the build. See Non-Generated C++ Files for more information.
"},{"location":"art-lang/#art-files-and-folders-and-reference-binding","title":"Art Files and Folders and Reference Binding","text":"Any but the simplest of applications will consist of multiple Art files organized into folders. You can create as many Art files as you like, and every Art file may contain one or several Art elements. Art files containing Art elements that are related to each other should be grouped in a folder. For example, if you build a library from certain Art elements it makes sense to put the Art files with those elements in their own folder.
Folders with Art files should be added as workspace folders, either using the command File - Add Folder to Workspace (to add a folder to an existing workspace), or using the command File - Open Workspace from File (to open an existing workspace from a file that defines the workspace folders). If your application consists of more than a couple of workspace folders use of a workspace file is recommended as it makes it quick and easy to add all workspace folders in one go with a single command.
Note
Art files must be on the top level in a workspace folder. Do not place them in subfolders.
When an Art file contains a reference to an Art element that cannot be found within the same file, other Art files in the workspace will be searched for an Art element with the referenced name. This search starts with the Art files in the same workspace folder. If a matching Art element is found in one of these files, the reference is bound to it. Otherwise an active transformation configuration (TC) is required, which specifies one or several prerequisites. The Art files in the workspace folders where the prerequisite TCs are located will then be searched. The search continues recursively if the prerequisite TC itself has prerequisites.
If a matching Art element cannot be found in any of these locations the reference will be unresolved and an error will be reported. For example:
Couldn't resolve reference to Protocol 'UnknownPort'. (ART_9001_unresolvedReference)\n
For more information about unresolved references, see this validation rule.
"},{"location":"art-lang/#textual-and-graphical-notations","title":"Textual and Graphical Notations","text":"The Art language is a textual language, but many parts of it also have a graphical notation. For example, a state machine can be shown using a graphical state diagram, and the composite structure of a capsule can be shown in a structure diagram. Relationships between capsules, protocols and classes, such as inheritance, can be shown in class diagrams.
Below are examples of these three kinds of diagrams:
Diagrams are automatically updated when the corresponding Art file is modified. They use automatic layout to avoid the need for manual tidy-up of diagrams when something changes. This also significantly reduces the need for storing diagram specific properties in the Art files, such as coordinates or symbol dimensions. However, there are some properties used when rendering diagrams that are stored in the Art file. For example, if you assign a custom color to a state symbol it will be stored as a property on the state.
capsule Cap { \n statemachine {\n state ColorfulState[[rt::properties(color=\"#b40e0e\")]]; \n };\n};\n
Art elements are mostly edited using their textual notation, but diagrams also provide some editing capabilities. However, all edit commands performed from a diagram are actually mapped to corresponding textual modifications of the Art file. Editing from a diagram is therefore simply an alternative, and sometimes more convenient way, of editing the textual Art file.
"},{"location":"art-lang/#syntax","title":"Syntax","text":"Art uses a syntax that should be familiar to developers with knowledge about languages like C++ and Java.
;
,
are used for separating the elements{}
are used for grouping nested elements[]
are used for specifying cardinality (i.e. multiplicity) of elements.
) is used as scope resolution operator//
and block /* */
comments may be freely used for commentingNames of Art elements must be valid C++ identifiers since they will be used as names of C++ definitions in generated code. Names also must not clash with names used in the TargetRTS. Don't worry - the Art language editor will let you know if you choose a name that won't work.
Just like any language, Art has certain keywords that are reserved and which cannot be used as names. These keywords are listed below:
Art keywords abstract behavior capsule choice class connect entry entrypoint exclude exit exitpoint fixed history in initial junction notify on optional out part plugin port protocol publish redefine service state statemachine subscribe template trigger typename unwired when withArt is a case-sensitive language and names may use any capitalization. However, just like with most languages, there are conventions for how to capitalize names. Those conventions are described below where each Art language construct is described in detail.
"},{"location":"art-lang/#comments","title":"Comments","text":"The same kinds of comments as in C++ can be used, i.e. line and block comments.
// line comment\n\n/* block comment */\n\n/* multi-line\n block comment */\n
"},{"location":"art-lang/#capsule","title":"Capsule","text":"A capsule defines an active class with its own execution context. It may have ports through which it can receive events. A capsule has a state machine that describes how instances of the capsule transitions between different states in the response to received events.
Names of capsules are typically nouns, often describing something that performs some form of activity. For example \"Controller\", \"TrafficLight\" or \"FaultHandler\". By convention names of capsules start with uppercase.
Embedded C++ code can be used for declaring member variables, member functions, nested types etc for the capsule.
Here is an example of a capsule with a simple state machine and a member variable.
capsule Elevator {\n [[rt::decl]]\n `\n unsigned int currentLevel = 0;\n `\n statemachine {\n state Waiting;\n initial -> Waiting;\n };\n};\n
Note
Capsule member variables and member functions may be private or protected, but should usually not be public. To avoid threading issues all communication with a capsule should be done using events, and therefore public members are not recommended. An exception is capsule constructors which need to be accessible from other capsules that create instances of the capsule using a capsule factory. If you anyway let a capsule have public members you need to ensure they are only accessed from the same thread that runs the capsule.
The example above uses an rt::decl
code snippet for declaring a capsule member variable. It will have the default visibility which is private. Here is the list of all code snippets that can be used for a capsule:
Just like a regular class a capsule may have constructors. A capsule constructor is declared using an rt::decl
code snippet and defined using an rt::impl
code snippet. All capsule constructors have two mandatory parameters:
RTController*
) which will execute an instance of the capsule. It corresponds to the thread that runs the capsule instance.RTActorRef*
) into which the capsule instance will be inserted. Every capsule instance, except the top capsule, resides in exactly one part.After these parameters you can add your own parameters, to pass arbitrary initialization data to the capsule instance. Below is an example where a capsule MyCap
has a reference variable m_c
. To initialize this variable a capsule constructor is used.
capsule MyCap { \n [[rt::decl]]\n `\n public:\n MyCap(RTController*, RTActorRef*, MyClass&);\n private:\n MyClass& m_c;\n `\n [[rt::impl]]\n `\n MyCap::MyCap(RTController* rtg_rts, RTActorRef* rtg_ref, MyClass& c) \n :RTActor(rtg_rts, rtg_ref), m_c(c) { }\n `\n};\n
If you don't define any constructor for your capsule, a default capsule constructor will be generated automatically. It only has the two mandatory constructor parameters, and passes them to the TargetRTS which will create the capsule instance. But if your capsule has at least one user-defined constructor, a default capsule constructor will not be generated, and you then need to make sure to provide arguments that match the constructor parameters, when an instance of the capsule should be created. For this you need to use a capsule factory. You can either specify such a capsule factory statically on a part that is typed by the capsule (see Part with Capsule Factory), or you can provide a capsule factory dynamically when calling incarnateCustom()
on a Frame port to incarnate an optional capsule part. Here is C++ code for doing the latter (assuming the optional part is called thePart
):
RTActorId id = frame.incarnateCustom(thePart,\n RTActorFactory([this](RTController * c, RTActorRef * a, int index) {\n return new MyCap(c, a, getMyClass()); // Use capsule constructor\n }));\nif (!id.isValid()) {\n // Failed to incarnate thePart\n}\n
Note the following:
RTActor
constructor in its initializer.Example
You can find a sample application that uses a capsule constructor here.
Read more about capsule factories here.
"},{"location":"art-lang/#capsule-destructor","title":"Capsule Destructor","text":"The destructor of a capsule frees the memory used for representing its states, ports etc. You can provide your own destructor if you want to perform additional clean-up activites when a capsule instance is destroyed (for example, freeing resources allocated in the capsule constructor).
capsule CapsuleWithDestructor {\n [[rt::decl]]\n ` \n public:\n virtual ~CapsuleWithDestructor();\n `\n [[rt::impl]]\n ` \n CapsuleWithDestructor::~CapsuleWithDestructor() {\n // Clean-up code here\n }\n ` \n};\n
Example
You can find a sample application with a capsule that implements its own destructor here.
Clean-up code can also be placed in a function _predestroy()
which overrides RTActor::_predestroy()
. This function gets called just before the capsule instance is destroyed and may often be a better place where to write clean-up code. If you are using a custom capsule factory which doesn't destroy capsule instances by means of the standard delete
operator, then the capsule destructor will not be called, and you can instead use _predestroy()
which always is called when a capsule instance is about to be destroyed.
Here is an example of how to use _predestroy()
:
capsule C { \n [[rt::decl]]\n `\n public: \n virtual void _predestroy() override { \n // Clean-up and free allocated resources here\n SUPER::_predestroy();\n } \n `\n // ...\n};\n
It's important to remember to invoke the inherited function by calling SUPER::_predestroy()
.
Example
You can find a sample application with a capsule that overrides _predestroy()
here.
A protocol defines events that may be sent in to a port (so called in-events) and events that may be sent out from the same port (so called out-events). By grouping events into protocols, and then typing ports with such protocols, we can precisely define which events the capsule may send and receive through that port.
By convention names of protocols start with uppercase, while names of events start with lowercase and use camelCase if the name consists of multiple words.
A protocol event may have a parameter, which enables it to carry data. You declare a parameter for an event by specifying the C++ type of the data to be carried by the event.
Note
An event can have at most one parameter. If you need to send multiple data objects with an event you can declare an event parameter of struct or class type.
The following code snippets can be used for a protocol:
Code snippet C++ mapping Example of use rt::header_preface Inserted at the top of the protocol class header file Adding #includes of header files defining types used as event parameter types rt::header_ending Inserted at the end (or near the end) of the protocol class header file Undefining a local macro that was defined in rt::header_prefaceHere is an example of a protocol that defines some in-events and some out-events:
protocol MachineEvents {\n in start();\n in startDeferred(`unsigned long` /* milliseconds */);\n\n out success();\n out error(`std::string` /* error message */);\n\n in relayEvent(); out relayEvent();\n};\n
The event relayEvent
above is both an in-event and an out-event. Such symmetric events are useful in protocols typing ports that may receive and send the same events (for example a port that just forwards received events to another port). By convention a symmetric event is declared on a single line.
At run-time we often talk about a message rather than an event. A message is an instance of an event, similar to how a capsule instance is an instance of a capsule. In other words, a message is a run-time concept while an event is a design-time concept.
"},{"location":"art-lang/#port","title":"Port","text":"A port defines a named point of communication for a capsule. A port is typed by a protocol which defines the events that may be sent in to (in-events) and out from (out-events) the port. A port may be conjugated in order to swap the meaning of in-events and out-events. That is, a capsule may send out-events on its non-conjugated ports, but in-events on its conjugated ports. A port becomes conjugated if you add a tilde (~) after its name.
Ports are often named to describe the role or purpose of the communication that takes place on them. By convention names of ports start with lowercase and use camelCase if the name consists of multiple words.
Here is an example of a capsule with a few ports. Note that Code RealTime provides several predefined protocols that can be used right away, for example Timing
. Also note that you can declare multiple ports on a single line if the ports are of the same kind (p1
and p2
below are both service ports).
capsule Machine {\n service port control : MachineEvents;\n behavior port timer : Timing; // predefined Timing protocol\n service behavior port control2 : CtrlEvents;\n service port p1~ : MoreEvents, p2~ : OtherEvents; \n\n // ...\n};\n
Service ports constitute the externally visible communication interface for a capsule, and together they define which events can be sent to the capsule, and which events the capsule can send out for other capsules to receive. In a structure diagram the service ports are shown on the border of a capsule or part symbol.
A behavior port is logically connected to the behavior (i.e. state machine) of a capsule. This means that an event that a capsule receives on a behavior port will be handled by the state machine of that capsule. A non-behavior port, however, will simply route an event to another port to which it is connected. Every event that is sent will ultimately reach a behavior port (provided ports are properly connected), and the state machine of the capsule owning that behavior port will handle the event. In a structure diagram, behavior ports are connected to a small ellipse which represents the capsule state machine.
Note that ports can also be shown in a class diagram.
When a capsule wants to send an event to another capsule it calls a function on the port. There is one such function for each out-event (or in-event if the port is conjugated). These functions return an object on which a send()
function can be called. Note that the sending capsule doesn't need to know which capsule that will receive and handle the sent event.
Here is C++ code for sending events on ports with and without data:
pongPort.pong().send(); // Send event \"pong\" without data on the \"pongPort\"\npingPort.ping(5).send(); // Send event \"ping\" with data (an integer) on the \"pingPort\"\n
Example
You can find a sample application that sends events on ports with and without data here.
Note that send()
is not the only function you can call. For example, you can call invoke()
if you want to wait until the receiver has received and replied to the event. This is useful for implementing synchronous communication between capsules.
At run-time an instance of a port can be connected to a port instance on another capsule. Such connections is what make a sent event be routed from the port on which it is sent, through a number of non-behavior ports, until it finally reaches a behavior port. By default a port has single multiplicity (1) meaning that at most one such connection can be established. However, you can specify a non-single multiplicity for a port to allow for more connections to be created at run-time.
In the example below a Server
capsule has a port with multiplicity 100. At run-time an instance of that Server
capsule can be connected to 100 different client ports, each of which can send events to the server.
capsule Server {\n service port clients : ComEvents[100];\n\n // ...\n};\n
In a structure diagram a port is shown as \"stacked\" if it has non-single multiplicity.
You can also use a C++ expression to specify the port multiplicity. This can for example be useful if the multiplicity is defined in C++ as a macro or a constexpr. For example:
capsule Server {\n service port clients : ComEvents[`NBR_CLIENTS`];\n\n // ...\n};\n
"},{"location":"art-lang/#notification-port","title":"Notification Port","text":"Every protocol contains two implicit events rtBound
and rtUnbound
. A port can choose to receive those events whenever a connection for the port is established (rtBound) or dropped (rtUnbound) at run-time. Declare a port as a notification port to receive these events.
capsule Server {\n service notify port clients : ComEvents[100];\n\n // ...\n};\n
Port notifications are useful in dynamic systems when capsules need to wait until other capsules are ready, before they can start to communicate with those capsules. For example, a client may need to wait until a server is ready before it sends a request to that server. In the same way it's often useful to get notified when a connection is dropped, since that means communication on that port should no longer take place.
"},{"location":"art-lang/#unwired-port","title":"Unwired Port","text":"Ports are by default wired, meaning that they should be connected with connectors to specify statically how events will be routed. Having a static connector structure defined has the benefit that it becomes possible to look at a capsule's structure diagram to see how events received by the capsule will be routed at run-time. However, in some dynamic systems it's not possible to describe this statically. Ports may be connected and disconnected dynamically and the run-time connections between port instances may hence vary over time. If you need this flexibility you can declare ports as unwired.
Here is an example of an application where a client capsule can connect to different kinds of server capsules. Sometimes it may be connected to server1
and sometimes to server2
. It is therefore not possible to describe the connections of Top
statically using connectors, and we can instead declare the ports as unwired.
capsule Top {\n part client : Client;\n part server1 : Server;\n part server2 : Server;\n\n // ...\n};\n\ncapsule Client {\n service behavior unwired port p : Protocol;\n\n // ...\n};\n\ncapsule Server {\n service behavior unwired port p~ : Protocol;\n\n // ...\n};\n
Note
Only use unwired ports when required. It's strongly recommended to use wired ports whenever possible to enable the visualization of the connector structure in a structure diagram. When unwired ports are required you should write a comment that describes how they will be connected at run-time, since this often cannot easily be concluded by looking at the C++ code of the capsule.
An unwired port is always a behavior port. In a structure diagram an unwired port is drawn with a hollow ellipse, while a wired behavior port is drawn with a filled ellipse. In the structure diagram below port q
is wired while port p
is unwired.
An unwired port is either a service access point (SAP) or a service provision point (SPP) depending on the role it plays in a dynamic connection with another unwired port. The capsule that owns the SAP port uses it to subscribe to a service that is published by another capsule by means of an SPP port. The capsule with the SAP port is often called \"client\" or \"subscriber\" while the capsule with the SPP port is often called \"server\" or \"publisher\".
Unwired ports get connected by means of registering them under a service name that should be unique in the application. Registration of unwired ports can either happen automatically when the container capsule instance is created, or programmatically at a later point in time. It's also possible to deregister unwired ports in order to disconnect them. You can specify how an unwired port should be registered by means of the following properties:
If you choose to register an unwired port programmatically (using the TargetRTS functions registerSPP()
and registerSAP()
) you decide at registration time whether the port should be an SAP or SPP port. However, if you choose to instead let the port be registered automatically you need to declare the port as either a subscribe
(SAP) or publish
(SPP) port. Here is the same example again, but now with automatic registration of the unwired ports using the service name myService
:
capsule Client {\n subscribe behavior port sap [[rt::properties(\nregistration_name = \"myService\")\n]] : Protocol;\n\n // ...\n};\n\ncapsule Server {\n publish behavior port spp~ [[rt::properties(\nregistration_name = \"myService\")\n]] : Protocol;\n\n // ...\n};\n
Note that the keyword unwired
can be implicit when you declare a port as either a subscribe
or publish
port.
Example
You can find a sample application that uses an unwired port here.
"},{"location":"art-lang/#connector","title":"Connector","text":"Connectors describe how events are routed within a capsule by connecting ports in its composite structure. They make it possible to see in a structure diagram which parts of a capsule that can communicate with each other. Each connector connects exactly two ports with each other. A connected port may either be a port of the capsule itself, or a port of a capsule that types one of its capsule parts. A few constraints decide if it's possible to connect two ports:
1) The ports must be wired. Unwired ports cannot be connected.
2) The ports must be typed by the same protocol.
3) The ports' conjugations must match. If the ports are at the same level in the capsule's structure (e.g. both ports belong to capsules typing capsule parts owned by the same capsule), then the connected ports must have the opposite conjugation. This is because events that are sent out from one of the ports must be able to be received by the other port and vice versa. However, if the ports are at different levels in the capsule's structure (e.g. one of them belongs to a capsule typing a capsule part owned by the capsule and the other belongs to the capsule itself), then the ports must have the same conjugation. This is because in this case events are simply delegated from one capsule to another.
4) If a connector is connected to a port and a part (where the port is defined on the capsule that types the part), then the port must be a service port. Only service ports are visible from the outside of a capsule.
The example below shows the structure diagram of a capsule Top
where we can see two connectors.
capsule Top { \n part ping : Pinger, pong : Ponger; \n connect ping.p1 with pong.p2; \n // ...\n};\n\ncapsule Internal {\n service behavior port i~ : PROTO; \n // ...\n};\n\ncapsule Pinger {\n service behavior port p1 : PROTO; \n // ...\n};\n\ncapsule Ponger { \n service behavior port p2~ : PROTO; \n part inner : Internal;\n connect p2 with inner.i;\n // ...\n};\n
The connector between p1
and p2
goes between two ports on the same level which is why these ports must have opposite conjugation. The connector between p2
and i
goes between two ports at different levels which is why these ports must have the same conjugation. The non-behavior port p2
is a so called relay port (it just relays all events it receives to another port) and the connector between p2
and i
is sometimes called a delegation connector to describe the fact that capsule Ponger
uses it for delegating some of its responsiblities to the capsule Internal
. Note that relay ports can be optimized away so they don't exist at run-time (i.e. at run-time port p1
can be directly connected to i
).
A connector doesn't have a direction, so it doesn't matter in which order it connects the two ports. That is, connecting X with Y is equivalent to connecting Y with X.
"},{"location":"art-lang/#local-binding","title":"Local Binding","text":"A connector can connect two behavior ports on the same capsule (with opposite conjugations). At run-time this will lead to a local binding between these ports. This enables the capsule to send events to itself which sometimes can be useful. Here is an example:
capsule Top { \n behavior port pOut : TheProtocol;\n behavior port pIn~ : TheProtocol;\n connect pOut with pIn; \n // ...\n};\n
One reason for a capsule to send events to itself could be to split a big and long-running task into smaller tasks. By sending an event to itself after completion of each small task, the capsule can remain responsive to other events that may arrive in the meantime. When it receives the event it sent it can proceed with the next part of the big task.
Example
You can find a sample application that uses a local binding here.
"},{"location":"art-lang/#part","title":"Part","text":"A capsule can be decomposed by means of parts (also called \"capsule parts\" to emphasize that they are parts of a capsule). A part is a container that at run-time may hold one or many capsule instances. The part has a multiplicity that specifies the maximum number of capsule instances it can contain at run-time, and it has a type which is another capsule. All capsule instances must either be of that specific capsule type, or of a capsule type that inherits from it.
It's common to name parts according to the capsule that types them. For example, a part typed by a capsule Controller
may be called controller
, ctrl
or perhaps theController
. By convention part names start with lowercase and use camelCase if the name consists of multiple words.
There are three kinds of parts which determine how and when they will be populated with capsule instances.
1) Fixed part
In a fixed part capsule instances are created automatically when the container capsule is created, and destroyed when the container is destroyed. Fixed parts by default have multiplicity 1. Such a part will always contain one and only one instance of the capsule that types the part.
Example
You can find a sample application that has a fixed part with a multiplicity here.
2) Optional part
In an optional part capsule instances don't have a strong lifetime relationship with the container capsule as is the case for fixed parts. The capsule instances can be created programmatically using a Frame port at some point after the container capsule has been created, and they can be destroyed before the container capsule is destroyed. However, at the latest they will be automatically destroyed when the container is destroyed. Optional parts by default have multiplicity 0..1. This means that they may either contain zero or one capsule instance at any point in time. The presence of zero in the multiplicity is what makes the part optional. Here is C++ code for creating a capsule instance in an optional part (also known as incarnating the part) and then immediately destroying it:
RTActorId id = frame.incarnate(thePart);\nif (!id.isValid()) {\n // Failed to incarnate thePart\n}\nframe.destroy(id);\n
It's important to check that incarnation was successful since there are many reasons why it can fail (e.g. too low multiplicity to fit the created capsule instance, not enough memory etc).
If the instantiated capsule has a constructor you need to use a capsule factory for providing the constructor arguments (either provided when doing the incarnation as shown here or specified on the part as described here).
Example
You can find a sample application that creates and destroys capsule instances in optional parts here.
3) Plugin part
A plugin part is similar to an optional part in that it is populated by capsule instances programmatically. However, the capsule instances are not created in the plugin part but instead imported into the plugin part from another part. Typically such a capsule instance is first created into an optional part, and then at some later point in time imported into a plugin part. Later it can be deported (i.e. removed) from the plugin part and perhaps imported into another plugin part. This makes it possible to create very dynamic composite structures where the same capsule instance can play different roles in different parts over time. Moving a capsule instance by deporting it from one plugin part and then importing it in another plugin part is more efficient than destroying the capsule instance in one optional part and then creating another capsule instance in another optional part. Plugin parts are typically used together with unwired ports. In general it's possible to import a capsule instance into more than one plugin part at the same time, but it can only be imported if its ports are not already bound in its current location. Plugin parts by default have multiplicity 0..1.
In the example below the capsule C
contains a few parts of different kinds and multiplicities. Note that you may declare multiple parts on the same line if they are of the same kind (both c
and d
below are optional parts).
capsule C {\n part a : D;\n fixed part b : D[4];\n optional part c : D, d : D[0..5];\n plugin part e : D;\n part f : D[`COUNT`];\n\n // ...\n};\n
Part a
is fixed with multiplicity 1 since neither kind nor multiplicity is specified for it. Part b
is also fixed (using the fixed
keyword for more clarity) and with multiplicity 4. When an instance of capsule C
is created 5 instances of capsule D
will be automatically created. One of these instances will be inserted into part a
and the others into part b
. These instances will remain there until the C
capsule instance is destroyed.
Part c
is optional with multiplicity 0..1. At run-time it can contain at most one instance of capsule D
. Part d
is also optional but can contain up to 5 instances of D
as specified by its multiplicity 0..5.
Part e
is plugin with the default multiplicity 0..1. At run-time at most one instance of capsule D
can be imported into it. That instance must already have been created in another part, for example part c
.
Part f
uses a C++ expression for specifying the multiplicity. This can for example be useful if the multiplicity is defined in C++ as a macro or a constexpr.
Parts can be shown in a structure diagram:
Parts are shown as \"stacked\" if they have non-single multiplicity (multiplicities specified with a C++ expression are assumed to be non-single). Optional parts are shown with a \"diagonal\" background pattern, while plugin parts are shown with a \"double diagonal\" background pattern.
Parts can also be shown in a class diagram:
In the above diagram the filled diamonds show that there is a strong life-time relationship between a C
instance and the instances of D
that are located in the fixed and optional parts a
, b
, c
, d
and f
, while this is not the case for the instance located in the plugin part e
as shown by the hollow diamond.
If the capsule that types a part has a capsule constructor with custom constructor parameters, you can define a capsule factory for the part. Such a capsule factory consists of one or both of the below code snippets that define how an instance of that capsule should be created and destroyed.
rt::create
Defines how to create an instance of the capsule. For example, which constructor arguments to pass, which thread to use for running the created capsule instance, at which index to insert the capsule instance into the part (in case it has multiplicity > 1) etc.rt::destroy
Defines how to destroy an instance of the capsule. By default it's destroyed using the delete
operator.Here is an example where a part defines a capsule factory that specifies a create function. The create function gets the mandatory constructor parameters rtg_rts
and rtg_ref
as arguments, as well as an index
argument that specifies the index where the created capsule instance will be inserted.
part engine : Engine [[rt::create]]\n`\n return new Engine(rtg_rts, rtg_ref, true /* custom constructor arg */);\n`;\n
Note that you may want to create a capsule factory for a part also for other reasons than passing custom constructor parameters. For example, you may want to change the default thread (RTController*
) that should execute the created capsule instance, or you may want to instantiate an inherited capsule rather than the capsule that types the part.
Example
You can find a sample application here where a fixed part uses an rt::create
code snippet for invoking a custom capsule constructor.
You can use a global capsule factory by means of setting the capsuleFactory
TC property. Such a capsule factory will be used when creating or destroying any capsule instance in your application, except those that are located in a part for which you have specified a local capsule factory.
Read more about capsule factories here.
"},{"location":"art-lang/#state-machine","title":"State Machine","text":"State machines are used for specifying the behavior of capsules. It is also possible to provide a state machine for a passive class; see Class with State Machine for more information about that. In this chapter we focus on state machines in capsules.
A state machine consists of states and transitions. During its lifetime a capsule instance transitions between the various states of its state machine, as a consequence of receiving events on its behavior ports. When transitioning between two states one or several code snippets may execute. Such code may for example send events to other capsule instances, something that may cause transitions to execute in their state machines.
A state machine may also have pseudo states, which just like states may be connected with transitions, but that unlike states are not places where the state machine should stay for some time. For example, most pseudo states like junctions and entry/exit points merely act as connection points that make it possible to execute more than one transition when transitioning between two states. The notable exception is the choice in which actually the state machine may get stuck for ever, but that would be an error situation that should not happen in a correctly designed state machine.
"},{"location":"art-lang/#state","title":"State","text":"The states of a state machine are the places where the state machine may stay for some time while waiting for a message to arrive that potentially can cause the state machine to transition to another state. States should have names that describe what is happening while the state machine stays there, or what has happened for the state machine to arrive there. For example, \"WaitForInit\", \"Processing\" or \"Terminated\". By convention state names start with uppercase.
You can declare multiple states on the same line using a comma-separated list of state names. It can be good to write a comment in front of the state name, if you want to elaborate more on its meaning than what is possible in the name itself. Here is an example of a state machine with some states:
capsule TrafficLight { \n statemachine {\n state WaitUntilServerReady, CycleLight;\n state /* pedestrians are crossing the street */ PedestriansCrossing;\n initial -> WaitUntilServerReady;\n WaitUntilServerReady -> CycleLight;\n CycleLight -> PedestriansCrossing;\n };\n};\n
Here is another example where the state machine is shown in a state diagram.
A state comment is not visible in a state diagram, but show up in a tooltip when putting the cursor on a reference to the state. They can thereby make it easier to understand a state machine.
States may be nested to create a hierarchical state machine.
"},{"location":"art-lang/#entry-and-exit-action","title":"Entry and Exit Action","text":"A state may have an entry and/or exit action which is a code snippet that runs whenever the state is entered and/or exited.
state Walking {\n entry\n `\n server.walk().send();\n `;\n exit\n `\n server.stop().send();\n `;\n};\n
Example
You can find a sample application where a state has an entry and exit action here.
"},{"location":"art-lang/#transition","title":"Transition","text":"A transition connects a source state (or pseudo state) to a target state (or pseudo state). When a capsule instance handles a message that was received on one of its behavior ports, one or several transitions may execute.
It's not required to give a name to a transition, but it's possible and often makes the state machine easier to understand. At least triggered transitions (i.e. transitions where the source is a state) should have a name. A transition name can be choosen to describe what has happened when the transition executes, for example \"requestReceived\", \"timeout\" etc. By convention transition names start with lowercase and use camelCase if the name consists of multiple words.
A triggered transition has one or several triggers which define when the transition can be triggered. Each trigger specifies a port and an event. The trigger can only trigger its transition if the received message is an instance of the specified event, and was received on the specified port. In addition it's possible to provide guard conditions that must be fulfilled for the trigger to trigger its transition. Such a guard condition can be specified for the transition, but also for each individual trigger.
Here is an example of a capsule state machine with two triggered transitions requestReceived
and timeout
. It also contains an initial transition that has no name.
capsule MyCap {\n statemachine {\n state Waiting, Processing;\n initial -> Waiting; \n requestReceived: Waiting -> Processing on com1.request, com2.request when\n `\n return canHandleNow();\n `\n `\n log.log(\"Handling request\");\n log.commit();\n handle(msg);\n `;\n timeout: Waiting -> Waiting on timer.timeout[`zCount < 10`];\n };\n};\n
Triggers are specified as PORT.EVENT
after the keyword on
. You may specify multiple triggers separated by comma (,
).
It's only valid to specify triggers for transitions that originate from a state. Transitions that originate from a pseudo-state (e.g. a choice or junction) cannot have triggers, i.e. they must be non-triggered transitions. Note, however, that transitions originating from entry and exit points without incoming transitions represent the container state and hence need a trigger.
"},{"location":"art-lang/#transition-code","title":"Transition Code","text":"A transition can have code snippets:
when
keyword), but also a guard for each individual trigger (specified in square brackets ([]
) after the trigger). The guard for an individual trigger is only evaluated when the received message matches the port and event specified by that trigger, while the guard for the transition itself is always evaluated. A transition can only execute if both these guard conditions are fulfilled.A guard code snippet can either be written as a C++ statement that returns the boolean guard condition (as in the guard for transition requestReceived
in the above example), or it can be written as a boolean expression (as in the trigger guard for the timeout
trigger in the above example). If the guard condition is simple, as is often the case, using a boolean expression is recommended. However, if needed you can use any number of C++ statements in a guard condition where the last statement should return a boolean expression. For example, you can declare local variables to store partial results when computing the boolean expression.
Note
Guard code snippets should execute fast and have no side-effects. They are called frequently to decide which transition to execute when a message has arrived.
Both effects and guards are translated to C++ functions in the generated code. These functions have two arguments that can be used in the effect and guard code snippets:
rtdata
: A const pointer to the event data carried by the received messagertport
: A pointer to the RTProtocol that represents the port on which the message was receivedIn some cases rtdata
will be an untyped pointer (const void*
):
Timing::timeout
or External::event
eventsIn these cases you need to cast rtdata
to the correct type. Such casting should normally be avoided, and for cases 1, 2 and 3 above there are ways to do so. For example, instead of one transition with multiple triggers, you can have several transition with one trigger each. And instead of accessing rtdata
in a non-triggered transition, it's better to access it in the triggered transition that precedes it.
You can set the const_rtdata
property to false on a transition, to make rtdata
a non-const pointer (void*
). One reason could be that you want to move the data into a capsule variable, so you can access it later. Moving data can be more efficient than copying it.
[[rt::properties(const_rtdata=false)]] First -> Second on myport.myevent \n`\n pC = std::move(*rtdata);\n`;\n
Example
You can find a sample application that has transitions with the property const_rtdata
unset here.
Every state machine needs exactly one initial transition. When the state machine starts to run, the first thing that happens is that the initial transition executes and takes the state machine to its first state. Therefore, an initial transition is a non-triggered transition and also cannot have a guard condition. But it can of course have an effect code snippet.
The source of the initial transition is the initial pseudo state which is declared using the initial
keyword. Just like for any transition it's optional to give a name to the initial transition (in fact it's often left unnamed).
For capsule instances that are programmatically created (i.e. located in optional capsule parts) you can provide initialization data at the time of creation in the call to incarnate()
on a Frame port. The initialization data can be accessed in the effect code of the initial transition. Here is an example:
initial -> WaitForServerInit\n`\n RTpchar str = *((RTpchar*) rtdata);\n`;\n
Note
Any type of data object can be passed as initialization data which means that rtdata
is an untyped pointer that has to be casted to the expected type. A more type-safe way of passing initialization data is to define a constructor for a capsule. A capsule constructor can take any number of arguments, while with rtdata
only one data object can be passed (even if you of course can group several data objects into a struct or class to circumvent this limitation). With capsule constructors you can pass initialization data also for capsule instances that are located in fixed parts.
An internal transition doesn't change the active state and therefore doesn't have a target state. An internal transition is always a triggered transition. You define an internal transition inside the state to which it belongs. Here is an example:
state Done {\n unexpected: on myPort.*\n `\n std::cout << \"Unexpected event received! << std::endl; \n `;\n};\n
Note the usage of an asterisk (*
) to specify that any event received on myPort
will trigger the internal transition when the state machine is in the Done
state. Such \"receive-any\" events can of course be used for a trigger of any transition, but can in particular be useful for internal transitions that should handle all messages received on a port that are not handled by other triggered transitions leaving substates of the state. If another event is added to the port's protocol in the future, such a trigger will handle the new event too without a need for being updated.
Example
You can find a sample application that has an internal transition with a \"receive-any\" event trigger here.
Internal transitions are examples of so called self-transitions. To learn about other types of self-transitions see this chapter.
"},{"location":"art-lang/#choice-and-junction","title":"Choice and Junction","text":"Choices and junctions are pseudo states that make it possible to split transition flows in a state machine. That is, one incoming transition may be split into multiple outgoing transitions. Which of the outgoing transitions that will execute is decided by evaluating their guard conditions.
For a junction the guard conditions are evaluated already before leaving the currently active state. Only if there exists a path of transitions where all guards are fulfilled, will the active state be exited and the transitions can execute. Otherwise the state machine stays in its current state and attempts to find another path of transitions to execute. For a choice the guard conditions are evaluated after leaving the current state, when reaching the choice itself. The outgoing transition which has a fulfilled guard will execute next.
Note
It's important that there always is an outgoing transition for a choice with a fulfilled guard condition. Otherwise the state machine will get stuck in the choice without any chance of getting out of it.
The same is true if a junction is used in the initial transition. If such a junction doesn't have an outgoing transition with a fulfilled guard condition then the state machine will stay in the initial state for ever.
Choices and junctions must have names, so they can be referenced as the source or target of transitions. You can choose to use a name that gives a hint about what conditions that are checked in the guards of the outgoing transitions. For example, isEnabled
for a choice that checks a boolean condition and checkValue
when the condition has some other type. If you follow this approach you can then name the outgoing transitions accordingly. For example true
and false
for a choice that checks a boolean condition. By convention choice and junction names start with lowercase and use camelCase if they consist of multiple words. Sometimes it may be difficult to come up with a good name and in that case you can choose something short and \"technical\" like j1
, check1
etc.
Below is an example of a state machine containing a choice and a junction.
statemachine {\n state First, Second, Third;\n t1: initial -> First;\n choice isEnabled;\n junction checkThreshold;\n switchTurned: First -> isEnabled; \n true: isEnabled -> Second when\n `\n return isEnabled(); \n `; \n false: isEnabled -> Second when\n `\n else \n `; \n timeout: First -> checkThreshold; \n low: checkThreshold -> Third when\n `\n return t < LIMIT1; \n `;\n medium: checkThreshold -> Third when\n `\n return t >= LIMIT1 && t < LIMIT2; \n `;\n high: checkThreshold -> Third;\n};\n
Note the use of the C++ keyword else
for defining an else-guard. An else-guard will be fulfilled when no other guard of other outgoing transitions is fulfilled. For choices it's good practise to always have exactly one transition with an else-guard to ensure that at least one guard condition will be fulfilled. Thereby we avoid the risk of the state machine getting stuck in the choice. Else-guards can also be useful for junction transitions, but there they are more optional (except when a junction is used in the initial transition; see the note above).
You can also define an else-transition for a choice or junction by simply omitting the guard condition. This is consistent with triggered transitions where the absense of a guard condition is equivalent to a guard condition that always is fulfilled. See the transition high
in the above example.
Guard conditions should be mutually exclusive so that the order in which they are evaluated doesn't matter.
Junctions can also be used for merging multiple incoming transition flows into a single outgoing transition. This can for example be useful if you want to reuse a transition path in the state machine for several triggered transitions.
statemachine {\n state S1, S2;\n junction j1;\n initial -> S1;\n t1: S1 -> j1 on port1.e1 \n `\n // handle e1\n `;\n t2: S1 -> j1 on port2.e2\n `\n // handle e2\n `;\n t3: S1 -> j1 on port3.e3\n `\n // handle e3\n `;\n common: j1 -> S2 \n `\n // common code here\n `;\n};\n
Of course, in the above simple example the same code reuse could also be obtained by putting the common code in a capsule member function which is called by each of the incoming transitions. But if the common transition is followed by more non-triggered transitions the above approach is more feasible.
When multiple triggered transitions converge into a common transition as in the example above, and the events that trigger those transitions have a data parameter, it's best to access that data in the triggered transitions and not in the common transition. This is especially true if the types of those data parameters are not the same, because in that case the rtdata
parameter of the function generated for the common transition will be untyped (void*
). You can of course still cast it to another type, but that requires that you can know which of the triggered transitions that were triggered. It's therefore better to access the data in the triggered transitions and if necessary store it in a capsule variable which you then can access in the common transition if needed.
Example
You can find a sample application that demonstrates usage of a choice and junction here.
"},{"location":"art-lang/#hierarchical-state-machine","title":"Hierarchical State Machine","text":"A state machine is hierarchical if it contains at least one composite state, i.e. a state with a nested state machine. A transition that is triggered in the enclosing state machine (i.e. the state machine that contains the composite state) should enter a composite state by specifying an entry point of the composite state as the target. In the nested state machine another transition can connect that entry point to a state in the nested state machine. A transition in the nested state machine may specify an exit point of the composite state as the target. In the enclosing state machine another transition can connect that exit point to a state in the enclosing state machine.
Entry and exit points are pseudo states that need to be named. The names can be chosen to give a hint about when the composite state is entered or exited through them, for example systemStarted
or errorDetected
. If you want you can prefix the names with ep
or ex
. It's also common to use short and \"technical\" names like ep1
or ex1
if a more descriptive name doesn't make sense. By convention entry and exit point names start with lowercase and use camelCase if they consist of multiple words.
It's also possible to directly enter a composite state without using an entry point. In this case the behavior will depend on whether the composite state is entered for the first time or not. If it is for the first time, the initial transition of the nested state machine will execute after the transition that targets the composite state has executed. Otherwise the composite state will instead be entered using deep history, i.e. by activating the state in the nested state machine that was most recently active (and recursively if that state again is a composite state).
Note
It's recommended to always enter a composite state using an entry point as the behavior then doesn't depend on if the state was previously entered or not.
Below is an example of a hierarchical state machine with a composite state CompositeState
that contains a nested state machine. Note that you can declare multiple entry or exit points on the same line.
statemachine { \n initial -> CompositeState.ep1;\n state CompositeState {\n state Nested;\n entrypoint ep1, ep2;\n exitpoint ex1;\n initial -> Nested;\n ep1 -> Nested;\n Nested -> ex1;\n ep2 -> history*;\n };\n state Other;\n CompositeState.ex1 -> Other;\n Other -> CompositeState.ep2;\n};\n
Note that a dot (.
) is used as scope resolution operator, to make it possible to reference an entry or exit point from the enclosing state machine. Inside the nested state machine the entry and exit points are directly accessible without use of the scope resolution operator (using it there would be an error).
It is possible to only connect an entry point on the \"outside\". Entering the state via such an entry point will behave in the same way as entering the composite state without using an entry point (see above). It's therefore not recommended. In the same way it's possible to exit a composite state using an exit point that only is connected on the \"inside\". In this case the composite state is not exited and instead the previously active substate again becomes active (recursively, just like for deep history). This is also not recommended, unless the transition is a local transition.
Example
You can find a sample application that contains a composite state with an entry and exit point here.
Just like a junction, an entry or exit point can have multiple outgoing transitions. Guards on those transitions decide which of them to execute, and are evaluated before leaving the current state. Therefore, the same recommendations as for guard conditions of junctions apply for entry and exit points.
"},{"location":"art-lang/#entry-and-exit-point-without-incoming-transition","title":"Entry and Exit Point without Incoming Transition","text":"You can choose to not connect an entry or exit point with an incoming transition. In this case the entry or exit point represents the owning state, and a transition that originates from such an entry or exit point behaves the same as if it would originate from the state itself. Contrary to other transitions that originate from an entry or exit point, such a transition is therefore triggered and should have at least one trigger.
An entry point without incoming transition is useful for handling events in a composite state that should be commonly handled regardless of which substate that is active. The composite state remains active when handling the event, and it will not be exited and entered. The target of such a transition may either be a nested state, the deep history pseudo state, or an exit point (see local transition).
In a similar way an exit point without incoming transition can be used for exiting the composite state in a common way regardless of which substate that is active. The behavior is the same as if the transition would originate from the composite state itself, but by using an exit point you can give a descriptive name to it that tells something about why the state is exited. This can be in particular useful if there are multiple such \"exit transitions\" from the composite state.
In the example below the transition tx
originates from an entry point ep2
without incoming transition. When it triggers the active state will change from Composite::Nested
to Composite::Nested2
without leaving the Composite
state. The transition done
originates from an exit point ex2
without incoming transition. It will exit the active substate of Composite
and then exit Composite
itself, before activating the Done
state.
statemachine {\n state Composite {\n entrypoint ep1, ep2;\n exitpoint ex2; \n state Nested, Nested2;\n ep1 -> Nested;\n tx: ep2 -> Nested2 on port1.timeout;\n };\n initial -> Composite.ep1;\n state Done;\n done: Composite.ex2 -> Done on port1.timeout;\n};\n
Example
You can find a sample application that uses an entry and exit point without incoming transitions here.
"},{"location":"art-lang/#deep-history","title":"Deep History","text":"Every nested state machine has an implicit pseudo state with the name history*
(in state diagrams it's shown as H*
to save space). It can be used as a target for any transition inside the nested state machine. When it is reached, the state machine will restore the previously active substate. If that state again is a composite state, its previously active substate will also be restored. This goes on recursively for all nested state machines (which is why it's called a deep history).
In the example above we can see that the transition from ep2
targets the deep history pseudo state. This means that if the Nested
substate is active and then the transition to ex1
gets triggered, the state Other
becomes active. If then the transition to ep2
gets triggered the CompositeState
will be entered using deep history so that the Nested
substate will again become active.
Example
You can find a sample application that uses the deep history pseudo state here.
"},{"location":"art-lang/#local-transition","title":"Local Transition","text":"A transition in a nested state machine that connects an entry point and exit point on the same state, and these entry/exit points only are connected on the \"inside\", is a local transition. A local transition is a self-transition that behaves something in between an internal transition and a regular (a.k.a. external) self-transition. An internal transition defined on a composite state handles a message without exiting neither that composite state, nor any of its substates. However, a local transition will exit the substates, run the effect code, and then enter the substates again. But the composite state itself will not be exited and entered. An external self-transition on the other hand will exit both the composite state and all active substates recursively, run the effect code, and then enter these states again.
Both for local and external self-transitions exiting of states happens bottom-up which means that the deepest nested substate will first be exited, then its parent state, and so on. Entering happens in the opposite order, i.e. in a top-down fashion.
Let's look at an example to understand the difference between these three kinds of self-transitions:
statemachine { \n initial -> SelfTransitionExample;\n state SelfTransitionExample {\n state Nested1 {\n state Nested2;\n };\n internal: on port1.e1\n `\n // Internal transition\n `;\n entrypoint e1;\n exitpoint e2;\n local: e1 -> e2 \n `\n // Local transition\n `;\n };\n external: SelfTransitionExample -> SelfTransitionExample on port2.e2\n `\n // External transition\n `;\n};\n
Assume the currently active state configuration is {SelfTransitionExample
, Nested1
, Nested2
} when one of the self-transitions get triggered:
internal
)No state is exited and the active state configuration remains unchanged.
local
)1) Nested2
is exited.
2) Nested1
is exited.
3) local
executes.
4) Nested1
is entered.
5) Nested2
is entered.
external
)1) Nested2
is exited.
2) Nested1
is exited.
3) SelfTransitionExample
is exited.
4) external
executes.
5) SelfTransitionExample
is entered.
6) Nested1
is entered.
7) Nested2
is entered.
Example
You can find a sample application that has a local transition here.
"},{"location":"art-lang/#class-with-state-machine","title":"Class with State Machine","text":"Art allows you to create passive classes with state machines. This can be an alternative to using a capsule in case you only need a passive stateful data object, and don't need the ability to send events to it, or to let it execute in its own context. A class with a state machine is more lightweight than a capsule at runtime.
Transitions in a class state machine are triggered by calling trigger operations on the class. A trigger operation is similar to a regular member function in C++, but does not have a code behavior of its own. Instead, when you call a trigger operation on an object of a class with a state machine it may trigger a transition in the class' state machine. That transition may have an effect code snippet that will execute.
A trigger operation can have parameters which allows you to pass data when calling them. Those parameters can be accessed in the transition that is triggered by it.
Below is an example of a class with a state machine with two trigger operations initialize
and finalize
. Note that you can define multiple trigger operations on the same line.
class DataObject {\n /* Trigger Operations */\n trigger initialize(`int` data), finalize();\n /* State Machine */\n statemachine {\n state Initial, Initialized, Finalized;\n initial -> Initial;\n init: Initial -> Initialized on initialize(`int`)\n `\n // Initialized\n int i = data;\n `;\n Initialized -> Finalized on finalize()\n `\n // Finalized\n `;\n };\n};\n
Just like for C++ member functions, trigger operations support overloading. That is, you can have many trigger operations with the same name as long as their full signatures are unique. The signature of a trigger operation consists of its name and the types of all its parameters. When you reference a trigger operation with parameters as a transition trigger, you need to include the types of the parameters (see the trigger for the init
transition above).
The same transition can be triggered by multiple trigger operations (just like a transition in a capsule state machine can be triggered by multiple events). However, in that case those trigger operations should agree on the names and types of their parameters so that the transition effect code can access them in a way that works regardless of which of the trigger operations that will trigger the transition.
Names of classes with state machines by convention start with uppercase, while names of trigger operations and their parameters by convention start with lowercase and use camelCase if the name consists of multiple words.
A common design pattern is to let a class-with-statemachine instance be managed by a single capsule instance. This means that the capsule instance is responsible both for creating, using and finally destroying the class-with-statemachine instance. If you follow this pattern it is thread-safe to for example call public member functions defined on the capsule from a transition in the class state machine (or, better, to call non-public member functions by letting the class be a friend of the capsule). This can for example be used as a means for the class state machine to send events through the ports of the capsule (i.e. it can call a capsule member function that sends the event). However, to avoid exposing the full capsule functionality to the class state machine it's recommended to define an interface (i.e. abstract C++ class) which the capsule can implement. This interface can contain only those member functions which the class needs to call on the capsule.
A class state machine can use the same constructs as a capsule state machine with a few exceptions:
The initial transition cannot access initialization data as can a capsule's initial transition. Instead you can define one or several constructors for the class with parameters needed for passing initialization data when the class-with-statemachine instance is created. See Constructor for more information.
The state machine can be hierarchical but the deep history pseudo state is not supported. Instead the shallow history pseudo state can be used.
Even if it's possible for a class with a state machine to inherit from another class with a state machine, this doesn't mean that the state machines will be inherited as is the case for capsule inheritance. Read more about this in Inheritance.
A class with state machine can have the same code snippets as a capsule.
Example
You can find a sample application that uses a class with a state machine here.
"},{"location":"art-lang/#constructor","title":"Constructor","text":"By default the initial transition of a class state machine executes at the time of constructing the class-with-statemachine instance. This happens because the generated default constructor will call an operation rtg_init1()
which contains the code from the initial transition. If you want to wait with \"starting\" the state machine until a later point in time you need to define your own parameterless constructor which doesn't call this function.
You can define any constructors you need on a class with a state machine. They are regular C++ constructors and allow to pass initialization data when creating a class-with-statemachine instance. Remember to call the rtg_init1()
function in all such constructors, if you want the state machine to start at the time of creating the class-with-statemachine instance.
Here is an example of a class with a state machine that has a user-defined constructor:
class PC {\n [[rt::decl]]\n `\n private:\n double m_data;\n\n public:\n PC(double data);\n ` \n\n [[rt::impl]]\n `\n PC::PC(double data) : m_data(data) {\n rtg_init1();\n }\n `\n\n statemachine {\n state First;\n initial -> First\n `\n // State machine started\n `;\n };\n};\n
"},{"location":"art-lang/#shallow-history","title":"Shallow History","text":"Every nested state machine has an implicit pseudo state with the name history
(in state diagrams it's shown as H
to save space). It can be used as a target for any transition inside the nested state machine. When it is reached, the state machine will restore the previously active substate. However, if that state again is a composite state it's previously active substate will not be restored. This is in contrast to the deep history for capsule state machines, and is why for a class state machine this pseudo state is referred to as a shallow history.
Here is an example:
class MyClass {\n\n statemachine {\n state First {\n entrypoint ep1;\n ep1 -> history;\n };\n initial -> First.ep1;\n };\n};\n
"},{"location":"art-lang/#inheritance","title":"Inheritance","text":"By using inheritance you can reuse and customize generic (base) Art elements into more specific (derived) Art elements. An Art element can inherit either from one or several other Art elements, and/or it can inherit from one or several C++ classes. The derived Art element can redefine elements of the base element. The redefining element (located in the derived element) can change one or several properties of the redefined element (located in the base element). This is very similar to how inheritance works in C++, with the difference that in C++ a redefining element has more restrictions on what properties that can be changed in the redefined element. For example, a redefining member function (known as an overridden member function in C++ terminology) must keep the same signature as the redefined member function (known as a virtual base member function in C++ terminology), and can only (in fact, must) change its implementation.
In some cases Art inheritance not only allows to redefine inherited elements, but also to completely exclude them. An excluded element is not present in the derived element, so exclusion can be seen as a special form of redefinition where the whole element is removed in the derived element. In C++ it's not possible to exclude any inherited members.
"},{"location":"art-lang/#capsule-inheritance","title":"Capsule Inheritance","text":"A capsule can inherit from another capsule. Only one base capsule is allowed; multiple inheritance is not supported for capsules. In addition a capsule can inherit from any number of C++ classes (or structs).
The derived capsule is type compatible with the base capsule in the sense that if you have a capsule part typed by the base capsule, you can at runtime incarnate it with instances of the derived capsule.
Capsule inheritance has multiple dimensions. One dimension is the usual C++ inheritance between classes (remember that a capsule is an active class). In this dimension it is for example possible to redefine (a.k.a override) a virtual member function defined in the base capsule or in another base C++ class. But there is also a second dimension where the state machine of the derived capsule will implicitly inherit from the state machine of the base capsule. This makes it possible to redefine transitions and states. For example, a redefining transition in a derived capsule can change the effect code, the guard condition or the target state or pseudo state. And a redefining state in a derived capsule can change the entry or exit action, as well as any substate or subtransition in case the state is composite and has a nested state machine. It's also possible to completely exclude a state or a transition, either in the capsule's top state machine, or in a nested state machine.
Below is an example of a capsule D
that inherits from another capsule B
. In addition the capsule D
inherits from two C++ classes IDataManager
and IController
.
capsule B { \n [[rt::decl]]\n ` \n protected:\n virtual void doSmth();\n `\n [[rt::impl]]\n ` \n void B::doSmth() {\n // ...\n }\n `\n statemachine {\n state BS, BS2;\n _Initial: initial -> BS;\n };\n};\n\ncapsule D : B, `IDataManager`, `IController` { \n [[rt::decl]]\n `\n // IDataManager impl\n protected:\n void manageData() override;\n\n // IController impl \n void control() override;\n\n void doSmth() override;\n `\n\n [[rt::impl]]\n `\n // impl of manageData() and control()\n\n void D::doSmth() {\n // ...\n SUPER::doSmth(); // Call inherited function\n }\n `\n\n statemachine {\n state DS;\n state exclude BS2;\n redefine _Initial: initial -> DS;\n };\n};\n
In the example we can see that D
overrides functions from the base C++ classes that are assumed to be virtual (or pure virtual). For brevity the implementations of these functions have been omitted but would be placed in the rt::impl
code snippet. D
also overrides a virtual function doSmth()
from the base capsule B
. The implementation of that function (also placed in the rt::impl
code snippet) calls the inherited function by using a macro SUPER
. This macro expands to the name of the base capsule class. Using this macro, instead of the base capsule class name, makes it easier to copy/paste code from one capsule to another.
Example
You can find a sample application where a capsule inherits from both another capsule and from C++ classes here.
We can also see an example of a state machine redefinition. The initial transition _Initial
of B
's state machine is redefined in D
's state machine so that it targets state DS
instead of state BS
. In the state diagram of D
the state BS
and the initial pseudo state are drawn with gray color and dashed outline, to show that they are inherited. The transition _Initial
is also drawn with dashed outline, but with a different line style (\"dash-dot-dot\"), and with a green label to show that it's redefining the inherited initial transition. The state BS2
is excluded in D
's state machine. In state diagrams excluded elements are shown with a \"crossed\" background.
Note that to be able to redefine the initial transition of B
it is necessary to give it a name (so that it can be referenced as redefined from D
). This is yet another reason why it's good practise to give names to transitions, even if it's not mandated. But, of course, if you want to prevent anyone from creating a derived capsule with a state machine that redefines a certain transition, you can accomplish that by not giving a name to that transition. In effect, an unnamed transition is final, i.e. cannot be overridden or excluded.
The rule that a capsule state machine must have exactly one initial transition also applies to a derived capsule. Therefore, when you introduce inheritance between two existing capsules, you typically first get an error saying that the derived capsule has two initial transitions (one inherited, and one locally defined). You then need to decide if you want to either remove the initial transition in the derived capsule, or (like in the above example) instead redefine the initial transition.
Example
You can find sample applications where capsule state machines are inherited here:
Capsule inheritance also has a third dimension, which relates to its structure. Parts and ports defined in the base capsule are inherited by the derived capsule. Just like for states and transitions, it's possible to redefine or exclude a part or a port. A redefining port can change the type (i.e. protocol), multiplicity and the notification property of the redefined port. A redefining part can change the type, multiplicity and kind (fixed, optional or plugin) of the redefined part.
Below is an example of a capsule DPPI
that inherits from another capsule BPPI
. The port port1
and the part part1
is redefined, while the port port2
and part part2
are excluded.
capsule BPPI { \n service port port1 : PR1; \n behavior port port2 : PR1; \n part part1 : Cap1;\n part part2 : Cap1;\n statemachine {\n state State;\n initial -> State;\n };\n};\n\ncapsule DPPI : BPPI {\n service notify port redefine port1 : PR2[10];\n optional part redefine part1 : Cap2[0..20];\n part exclude part2;\n behavior port exclude port2;\n statemachine {\n state State2; \n };\n};\n
Redefined and excluded elements are also shown in class diagrams. Below is the class diagram for the capsules in the above example.
Example
You can find a sample application where parts are inherited here.
"},{"location":"art-lang/#calling-code-from-an-inherited-transition","title":"Calling Code from an Inherited Transition","text":"Guard and effect code for a transition that redefines an inherited transition can call the guard and effect code snippet from that inherited transition. For this purpose two C++ macros are available:
rtdata
and rtport
arguments.rtdata
and rtport
.That is, CALLSUPER
is equivalent to SUPERMETHOD(rtdata, rtport)
.
Example
You can find a sample application where these macros are used here.
Note that these macros are just a convenience and you can accomplish the same thing if you place the code of the transition code snippet in a virtual capsule member function, which then can be overridden in the sub capsule.
"},{"location":"art-lang/#abstract-capsule","title":"Abstract Capsule","text":"Some capsules are not intended to be instantiated, and just provide a base implementation which other capsules can reuse and specialize by means of inheritance. Such capsules should be declared as abstract. By doing so, you can leave the state machine of the abstract capsule incomplete, with only a partial implementation. Validation rules that check the correctness of capsule state machines will not report any problems for state machines of abstract capsules.
Below is an example of an abstract capsule Base
which provides a partial implementation of a state machine. The capsule C
inherits from Base
and extends the inherited state machine to make it complete.
abstract capsule Base { \n statemachine {\n state State;\n initial -> State;\n choice x; // Would normally report error ART_0006, but not for abstract capsule \n };\n};\n\ncapsule C : Base {\n statemachine {\n state A, B;\n x -> A when `expr()`;\n x -> B when `else`;\n };\n};\n
The Base
capsule state machine has a choice x
without any outgoing transitions. Normally a problem would be reported for that (ART_0006
), but because Base
is an abstract capsule, no error is reported. The capsule C
, which is non-abstract, can inherit Base
but must then define the missing outgoing transitions.
Note
A capsule that contains one or many pure virtual functions (either locally defined or inherited) is effectively also abstract in the sense that it cannot be instantiated. However, to use a partial state machine in a capsule you need to declare it with the abstract
keyword. Of course, you can still declare pure virtual functions for an abstract capsule if needed.
Example
You can find a sample application with an abstract capsule here. An example with an abstract capsule that has a pure virtual function can be found here.
"},{"location":"art-lang/#class-inheritance","title":"Class Inheritance","text":"A class with state machine can inherit from other classes with state machines, or from C++ classes (or structs). Multiple inheritance is supported.
Contrary to capsule inheritance, class inheritance does not imply inheritance between the state machines in the derived and base classes. This means it's not possible to redefine or exclude states and transitions in an inherited class state machine. Nor is it possible to redefine trigger operations. In fact, the derived class will have two state machines (its own, plus the one inherited from the base class) and these two state machines will execute independently of each other. That is, class inheritance is more a way of aggregating state machines rather than reusing and redefining them. Because of this, it's rather unusual to let two classes with state machines inherit each other. It's more useful to let a class with state machine inherit from other C++ classes.
Below is an example of a class with state machine that inherits from two C++ classes DataContainer<CData>
and IDisposable
.
class DataClass : `DataContainer<CData>`, `IDisposable` {\n [[rt::decl]]\n `\n void dispose() override; // From IDisposable\n `\n [[rt::impl]]\n `\n void DataClass:dispose() {\n // impl\n }\n `\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"art-lang/#abstract-class","title":"Abstract Class","text":"Just like for abstract capsules, you can declare a class with the abstract
keyword. However, since class state machines are not inherited, there is no formal meaning in doing so. It can still sometimes be useful to declare a class as abstract, to tell users of the class that it should not be directly instantiated. For example, if a class has a pure virtual function it is effectively abstract, and you can then use the abstract
keyword to further emphasize this.
abstract class AClass {\n [[rt::decl]]\n `\n virtual void implementMe() = 0;\n `\n statemachine {\n state State;\n initial -> State;\n };\n};\n
"},{"location":"art-lang/#protocol-inheritance","title":"Protocol Inheritance","text":"A protocol may inherit events from another protocol. Only one base protocol is allowed; multiple inheritance is not supported for protocols. Inherited events can be redefined, but not excluded. A redefining event in a derived protocol can change the type of the event parameter as defined in the base protocol.
In the example below, the protocol ExtendedMachineEvents
adds one more in-event stop
to the inherited MachineEvents
protocol. It also redefines the startDeferred
event to change its parameter type.
protocol MachineEvents {\n in start();\n in startDeferred(`unsigned long` /* milliseconds */);\n\n out success();\n out error(`std::string` /* error message */);\n};\n\nprotocol ExtendedMachineEvents : MachineEvents {\n in stop();\n in redefine startDeferred(`unsigned long long`);\n};\n
Example
You can find sample applications using protocol inheritance here:
A template is a type that is parameterized by means of template parameters to make it more generic. When a template is used (a.k.a. instantiated), actual template parameters must be provided that match the formal template parameters defined in the template. Currently only classes can have template parameters. Just like in C++, two kinds of template parameters are supported:
Replaced with a type when the template is instantiated.
Replaced with a non-type, for example a constant value, when the template is instantiated.
Template parameters may have defaults that will be used if a matching actual template parameter is not provided when instantiating the template.
Below is an example of a class with template parameters, some of which have defaults specified. The keywords typename
and class
can both be used for defining a type template parameter. A non-type template parameter is defined by specifying its type as a C++ code snippet.
template <typename T = `int`, class U, `int` p1 = `5`>\nclass TemplateClass : `Base<T,U,p1>` {\n [[rt::decl]]\n `\n void func(T arg1) {\n // impl\n }\n `\n\n statemachine {\n state State;\n initial -> State;\n };\n};\n
Template parameters can only be used from C++ code snippets, and above you see some examples of how they can be used. It's not possible to instantiate a template in Art itself. For example, even if class Base
above was defined as an Art class, a C++ code snippet has to be used since it has template parameters.
Example
You can find a sample application using templates here.
"},{"location":"art-lang/#property","title":"Property","text":"Properties are name-value pairs that provide a generic mechanism for augmenting Art elements with extra data. Such data can be utilized by tools that operate on a parsed Art file, such as the code generator and semantic checker. Most Art elements can have properties and the syntax for specifying properties is the same regardless of the kind of element. However, different kinds of Art elements can have different properties.
For Art elements that have a name, properties are specified right after the name. For elements without name, properties are specified before the element itself. In both cases the syntax looks like this:
[[rt::properties(\n<property name>=<property value>,\n<property name>=<property value>,\n...\n<property name>=<property value>\n)]]\n
All properties have a default value, so you only need to specify a property if you want to set it to something else. The default values have been chosen so that you in most cases don't need to specify any properties at all.
A property has a type, and its value must conform to that type. The following property types are supported:
Boolean properties have a value that is either true
or false
. If you want to set a boolean property to true
you can use a shorthand syntax where you just specify the property name. For example:
capsule CapProp \n[[rt::properties(\ngenerate_file_impl=false,\ngenerate_file_header\n)]]\n{\n // ...\n};\n
Writing generate_file_header
is equivalent to writing generate_file_header=true
. However, this particular property has the default value true
and hence doesn't need to be set at all.
Integer properties have a numeric value (>= 0). Here is an example:
protocol XProtocol [[rt::properties(\n version=1\n)]]{\n // ...\n};\n
String properties have a string value, enclosed in double quotes. Here is an example:
class MC [[rt::properties(\n rule_config=\"E0022\"\n)]]{\n // ...\n};\n
A property of enumeration type has a value that references a literal of the enumeration. There are different enumerations used for different properties. The best way to learn about what enumeration literals that are available for a certain property is to use the Content Assist feature in the Art file editor. Place the cursor after the equal sign, and press Ctrl+Space. Here is an example of defining a property of enumeration type:
class MC [[rt::properties(\n kind=struct\n)]]{\n // ...\n};\n
Note that in some cases the name of an enumeration literal starts with underscore (_
) to prevent it from clashing with the set of Art keywords.
Below is a table that lists all properties that can be used on different kinds of Art elements. Each property is described in a section of its own below the table.
Art Elements Property Type Default Capsule generate_file_header Boolean true Capsule generate_file_impl Boolean true Capsule, Protocol, Port, Initial transition, Triggered transition Trigger rule_config String \"\" Protocol version Integer 0 Port registration Enumeration (automatic, automatic_locked, application) automatic Port registration_name String \"\" Initial transition, Triggered transition const_rtdata Boolean true Transition, State, Choice, Junction, Entry Point, Exit Point, Port, Part, Capsule, Class color String \"\""},{"location":"art-lang/#generate_file_header","title":"generate_file_header","text":"By default a capsule is translated to one header file (.h
) and one implementation file (.cpp
). Set this property to false
to prevent generation of the header file, for example if you prefer to write it manually.
By default a capsule is translated to one header file (.h
) and one implementation file (.cpp
). Set this property to false
to prevent generation of the implementation file, for example if you prefer to write it manually.
This property is used for configuring validation rules for an Art element. Read more about this here.
"},{"location":"art-lang/#version","title":"version","text":"Specifies the version of an Art element. You can use this to keep track of updates to types used in APIs (increase the version when the element changes).
"},{"location":"art-lang/#registration","title":"registration","text":"This property specifies how to register an unwired port at runtime. The default is automatic
which means the port will be registered automatically when the container capsule instance is initialized. The value automatic_locked
has the same meaning but the registration will be \"locked\" so that any future attempt to deregister it, or register it under a different name, will fail. Set the property to application
to programmatically register the port using the functions registerSPP()
and registerSAP()
respectively.
This property specifies the name to use when registering an unwired port at runtime. By default the port name is used, but it can be overridden using this property.
"},{"location":"art-lang/#const_rtdata","title":"const_rtdata","text":"This property can be set on transitions where you need to modify the data it receives when it's triggered. If the property is set to false
the rtdata
parameter in the transition function will be non-const. It can then be modified, which for example can avoid copying received message data and instead move it using its move constructor or move assignment operator.
[[rt::properties(const_rtdata=false)]] CurrentState -> NextState\n`\n someAttr = std::move(*rtdata); // Avoid copying the message data object\n`;\n\nMyTransition: [[rt::properties(const_rtdata=false)]] OtherState -> NextState\n`\n pC = std::move(*((MyClass*) rtdata)); // Avoid copying the message data object\n`;\n
Note that the const_rtdata
property appears in the Art syntax right after the transition name. If the transition has no name, it appears in the beginning of the transition declaration.
Specifies which color to use for an Art element in a diagram. Colors should be specified as RGB values using 6 hexadecimal digits. For example, \"#ff00ff\". The Art text editor will help you set an appropriate color by means of a color picker.
Note that you can also set the color directly from the diagram. Select a symbol or line and then set the color property using the Properties view (under \"Appearance\").
"},{"location":"art-lang/cpp-extensions/","title":"C++ Extensions","text":"An Art file may contain code snippets at various places, where the C++ code within these snippets takes the form of expressions (e.g., guard conditions), statements (e.g., transition effects), or declarations (e.g., types, functions, variables, etc.). While most code snippets are copied directly to the generated C++ files during the translation process from an Art file to C+, certain snippets containing declarations (specifically, those marked as rt::decl
and rt::impl
) undergo parsing and analysis by the code generator. The code generator identifies and processes certain C+ extensions, such as attributes applied to the declarations in these snippets, translating them into additional C++ code. This capability enables the code generator to enhance the C++ code you write by incorporating additional boilerplate code. This chapter provides details on the applicable C++ extensions and the corresponding generated code for each.
A type descriptor is meta data about a C++ type. The TargetRTS uses the type descriptor to know how to initialize, copy, move, destroy, encode and decode an instance of the type. Type descriptors for all primitive C++ types are included in the TargetRTS, but for other types you need to ensure type descriptors are available if you plan to use them in a way where the TargetRTS needs it.
Note
Most types in your application may not need a type descriptor, and for some types it will be enough to provide a partial implementation of the type descriptor. Only implement what is necessary for how the type actually will be used. For example, if you send an instance of a type in an event between two capsules, and the instance is copied rather than moved, then the type descriptor needs a copy function but doesn't need a move function. And if you don't encode instances to strings (or decode them from strings) then the type descriptor doesn't need encode and decode functions. Always think about how a type will be used before deciding if it needs a type descriptor or not.
"},{"location":"art-lang/cpp-extensions/#c-implementation","title":"C++ Implementation","text":"The C++ implementation of a type descriptor consists of four parts. In the code shown below we assume the type descriptor describes a type called \"MyType\".
1) A type descriptor object
This is a variable typed by RTObject_class with a name that has the prefix \"RTType_\". It will be declared in the header file:
extern const RTObject_class RTType_MyType;\n
and implemented in the implementation file:
const RTObject_class RTType_MyType =\n{\n // Initialization of RTObject_class corresponding to properties of MyType\n};\n
Member variables of RTObject_class store all information about the type, such as its name and byte size. Some of the member variables store pointers to type descriptor functions which the TargetRTS will call when it needs to do something with an instance of the type, for example copy or encode it.
2) Type descriptor functions
These are functions with a certain prototype, each of which performs a specific action on an instance of the type. The type descriptor object stores pointers to these functions. For a partial type descriptor, some of these pointers will be nullptr
and then the corresponding type descriptor function does not exist. Below is the list of type descriptor functions that can be part of a type descriptor:
void rtg_MyType_init(const RTObject_class* type, MyType* target)
Initializes an instance of the type copy void rtg_MyType_copy(const RTObject_class* type, MyType* target, const MyType* source)
Copies one instance of the type (source) to another (target) move void rtg_MyType_move(const RTObject_class* type, MyType* target, MyType* source)
Moves one instance of the type (source) to another (target) destroy void rtg_MyType_destroy(const RTObject_class* type, MyType* target)
Destroys an instance of the type encode void rtg_MyType_encode(const RTObject_class* type, const MyType* source, RTEncoding* coding)
Encodes an instance of the type decode void rtg_MyType_decode(const RTObject_class* type, MyType* target, RTDecoding* coding)
Decodes an instance of the type The encode function usually encodes the instance into a string representation, and the decode function usually parses the same string representation and creates an instance of the type from it. However, the functions use interface classes RTEncoding
and RTDecoding
from the TargetRTS which can be implemented in many different ways. Note also that you can globally disable the support for encoding and/or decoding by unsetting the macros OBJECT_ENCODE
and OBJECT_DECODE
respectively. Learn more about encoding and decoding in this chapter.
3) A type installer object
Decoding functions typically need to look up a type descriptor from the name of the type. For example, if it finds the type name \"MyType\" in the string that it parses, it needs to find the type descriptor object for \"MyType\" so it can allocate memory for an instance of \"MyType\" and then initialize it. To facilitate this lookup the TargetRTS keeps a type descriptor registry. The purpose of the type installer object is to add the type descriptor to this registry. The C++ code looks like this:
#if OBJECT_DECODE\nRTTypeInstaller rtg_MyType_installer( RTType_MyType );\n#endif\n
4) A \"typed value\" struct
This is a struct which encapsulates an untyped instance of the type and its type descriptor. Its name has the prefix \"RTTypedValue_\". The struct has constructors that enable construction from a reference to an instance of the type. The typed value struct is the glue between generated code and TargetRTS code. TargetRTS functions get an untyped instance together with the type descriptor, and the type descriptor provides all information it needs to know about the type.
The typed value struct will be declared and implemented in the header file:
struct RTTypedValue_MyType\n{\n const void * data; // Untyped pointer to an instance of MyType\n const RTObject_class * type; // Type descriptor for MyType\n const bool rValueRef = false; // Was the typed value struct created from an rvalue or lvalue reference to MyType?\n inline RTTypedValue_MyType( const MyType & rtg_value )\n : data( &rtg_value )\n , type( &RTType_MyType )\n {\n }\n inline RTTypedValue_MyType( const MyType && rtg_value )\n : data( &rtg_value )\n , type( &RTType_Colors )\n , rValueRef( true )\n {\n }\n inline RTTypedValue_MyType( const MyType & rtg_value, const RTObject_class * rtg_type )\n : data( &rtg_value )\n , type( rtg_type )\n {\n }\n inline RTTypedValue_MyType( const MyType && rtg_value, const RTObject_class * rtg_type )\n : data( &rtg_value )\n , type( rtg_type )\n , rValueRef( true )\n {\n }\n inline ~RTTypedValue_MyType( void )\n {\n }\n};\n
"},{"location":"art-lang/cpp-extensions/#automatically-generated","title":"Automatically Generated","text":"The code generator will automatically generate a type descriptor for a type if you mark it with the rt::auto_descriptor attribute. The generated type descriptor functions will get a default implementation that depends on the kind of type. If the default implementation is not appropriate for your type you can simply provide your own implementation of one or many type descriptor functions in an rt::impl
code snippet.
Note
If you write your own type descriptor function it's important that its prototype exactly matches what is listed in this table. It's recommended to use Content Assist within the rt::impl
code snippet to avoid typos and reduce typing effort.
Here are some examples of using the rt::auto_descriptor
attribute on different kinds of types:
enum [[rt::auto_descriptor]] Status {\n Error, OK\n};\n\nenum class [[rt::auto_descriptor]] Colors { \n Red, Green, Yellow \n};\n\nstruct [[rt::auto_descriptor]] Point {\n int x;\n int y;\n};\n\n#include <string>\ntypedef std::string [[rt::auto_descriptor]] MyString;\n\n#include <vector>\nusing MyVector [[rt::auto_descriptor]] = std::vector<int>; \n
An automatically generated type descriptor behaves like this:
init
function for an enum will initialize it to the value of its first literal. For other types it will initialize the instance by invoking the parameterless constructor.copy
function for an enum just assigns the source
parameter to the target
parameter. For other types it will invoke the copy constructor.move
function is not available for an enum. For other types it will invoke the move constructor.destroy
function for an enum does nothing. For other types it will invoke the destructor.encode
function for an enum encodes it into an integer representation (0 for the first literal, 1 for the second, etc). For a typedef or type alias there is no default encode implementation. A structured type (struct or class) is encoded by calling put_struct
on the RTEncoding
. The TargetRTS provides encoding implementations that produce either JSON or a compact ASCII format.encode
implementation, the decode
implementation will read the encoded representation and create a corresponding instance of the type. For a typedef or type alias there is no default decode implementation.Here is an example of how to override the default implementation of a type descriptor (for an enum), and to provide a missing type descriptor function (for a typedef):
[[rt::decl]]\n`\n enum class [[rt::auto_descriptor]] Colors { \n Red, Green, Yellow \n }; \n\n #include <string>\n typedef std::string [[rt::auto_descriptor]] MyString;\n`\n\n[[rt::impl]]\n`\n static void rtg_Colors_init( const RTObject_class * type, Colors * target )\n {\n *target = Colors::Green; // Make Green the default color instead of Red\n }\n\n #if OBJECT_ENCODE\n static int rtg_MyString_encode(const RTObject_class* type, const MyString* source, RTEncoding* coding )\n {\n return coding->put_string(source->c_str()); // Encode as string literal\n }\n #endif\n`\n
Note that the default implementation of a type descriptor for a typedef or type alias makes the assumption that the typedefed or aliased type is a structured type which has both a parameterless constructor, a copy constructor and a move constructor. If this assumption is not correct, you need to write your own type descriptor functions, or even implement the whole type descriptor manually.
Example
You can find sample applications that use automatically generated type descriptors here:
If a type needs a type descriptor but the default implementation is not appropriate, and it's also not enough to simply override one or a few of the type descriptor functions with custom implementations, you can choose to implement the type descriptor manually. To do so you need to mark the type with the rt::manual_descriptor attribute. The code generator will then skip generation of the following parts of the type descriptor:
As an example assume we have a type alias for the Colors enum from the previous example. We only plan to use this type for standard enum encoding/decoding, but we want the Yellow color to be the default. Since the default type descriptor implementation for a type alias assumes the aliased type to be a structured type, we choose to do a manual implementation of the type descriptor instead of overriding several type descriptor functions (most of which we anyway won't need).
[[rt::decl]]\n`\n using MyColors [rt::manual_descriptor] = Colors;\n`\n\n[[rt::impl]]\n`\nstatic void rtg_MyColors_init( const RTObject_class * type, MyColors * target )\n{\n *target = Colors::Yellow; // Default color\n}\n\n// Manual type descriptor implementation\nconst RTObject_class RTType_MyColors =\n{\n nullptr\n , nullptr\n , \"MyColors\"\n , 0 /* RTVersionId */\n , sizeof( MyColors )\n , reinterpret_cast< RTInitFunction > ( rtg_MyColors_init )\n , nullptr\n#if RT_VERSION_NUMBER >= 7105\n , nullptr\n#endif\n#if OBJECT_DECODE\n , RTenum_decode\n#endif\n#if OBJECT_ENCODE\n , RTenum_encode\n#endif\n , RTnop_destroy\n , 0\n , nullptr\n};\n`\n
Use the Content Assist template \"New Type Descriptor Object\" to avoid typing all code manually.
Example
You can find a sample application that uses a manually implemented type descriptor here.
"},{"location":"art-lang/cpp-extensions/#versioning","title":"Versioning","text":"Type descriptors support versioning by means of the field RTObject_class::_version
. By default the version field is set to 0 which denotes the first version of the type described by the type descriptor. For types that are part of an API for which you need to maintain backwards compatibility, you can raise the version in case you need to modify the type in a way that is not backwards compatible. Use of versioning requires a manually implemented type descriptor, and the version information is for example used when you perform encoding/decoding using the RTVAsciiEncoding
and RTVAsciiDecoding
classes.
A type descriptor for a structured type (class or struct) contains information about the member variables (a.k.a fields) of the type. This information is stored in a field descriptor object typed by the TargetRTS class RTFieldDescriptor
.
If the structured type only contains public member variables, the code generator can generate the field descriptor as a global object in the implementation file. However, if at least one member variable is either private or protected, you need to declare the type descriptor inside the type, as a public member variable. The reason is that the byte offset of each member variable is stored in the field descriptor, and computing this offset requires access to the member variable from the field descriptor.
Below is an example of a struct and a class. The struct only contains public member variables and hence it's not necessary to declare the field descriptor manually. However, since the class contains a private member variable, we need to declare a field descriptor for it.
[[rt::decl]]\n`\n struct [[rt::auto_descriptor]] Point {\n int x;\n int y;\n };\n\n class [[rt::auto_descriptor]] DataPoint : public Point {\n private:\n long data;\n\n public: \n static const RTFieldDescriptor rtg_DataPoint_fields[];\n };\n`\n
A field descriptor member variable must have the name rtg_<type>_fields
, where <type>
is the name of the structured type.
Use Content Assist to create a class or struct that contains a field descriptor.
Example
You can find a sample application where a field descriptor is declared here.
"},{"location":"art-lang/cpp-extensions/#inheritance","title":"Inheritance","text":"If a class or struct inherits from another class or struct, as in the example above, then the type descriptor of the derived type will have a reference to the type descriptor of the base type. In this case both types need to have a type descriptor. A type descriptor can at most reference one base type descriptor (a.k.a. a super descriptor) which means that only single inheritance is supported for automatically generated type descriptors. If you use multiple inheritance you have to write a manual type descriptor for the derived type.
Example
You can find a sample application that uses an automatically implemented type descriptor for a class that inherits from another class here.
"},{"location":"building/","title":"Building","text":"Art and C++ files can be built into applications or libraries. The build process consists of three steps:
Generate source files In this step Art files are translated to C++ source files.
Generate a make file A make file for building generated C++ code (and possibly also other non-generated C++ source files) is generated.
Run make to generate binaries A make tool is invoked for building an executable or library from the generated make file.
All these steps require information which is stored in a transformation configuration (TC for short). It is a text file which contains various properties needed for translating Art elements to C++ code, for generating the make file, and finally for launching the make tool. You can have more than one TC in your workspace, but at most one TC in each workspace folder can be active. Set a TC as active by right-clicking on it and perform the command Set as Active. An active TC is marked with a checkmark.
Once there is an active TC in a workspace folder, the first and second steps (generation of C++ source files and make file) will happen automatically for all Art files contained in that workspace folder. The files that get generated are placed in its own workspace folder as specified by the targetFolder
property of the TC. The C++ code in this target workspace folder is then incrementally and automatically updated as soon as any of these Art files are changed. Also the make file (which by default is placed in a subfolder called default
in the target workspace folder) gets updated when needed. Below is an example of a simple target workspace folder.
To perform the third step (running make to generate binaries) you can simply go to the target folder in the Terminal and invoke the command make
. Alternatively you can use the TC context menu command Build (see below).
The context menu of a TC provides a few useful commands that automate some of the steps mentioned above:
Build This command first generates C++ code and a make file for the TC, and then runs the make tool on the generated make file. Note, however, that this command does not set the TC as active. If you plan to change code snippets in generated code you must set the TC as active yourself.
Run First builds the TC, and then attempts to launch the executable that is produced. The executable is launched in a non-debug mode by specifying the launch argument -URTS_DEBUG=quit
. If you instead want to launch the executable for debugging it you can go to the Terminal and manually launch it from there without any extra arguments. Note that if your TC creates a library rather than an executable, then this command will still build the TC, but will then give an error message since there is no executable to run.
Note
For a more flexible way of launching a built executable, consider using a launch configuration.
make clean
to clean using the make file.If you build a TC and at least one error exists in the TC itself, in prerequisites TCs or in any of the Art files that will be built, then a dialog will ask if you want to cancel the build (recommended) or proceed anyway (only do this if you are confident the errors are safe to ignore).
Use the setting code-rt.build.cancelOnError
to suppress this dialog.
In some cases of rapid prototyping and testing you may want to quickly build and run a capsule without first having to create a TC or a launch configuration. Then you can click the \"Run\" link that appears just before any capsule in the Art text editor.
This command will create a temporary TC file and use a temporary target folder. All other TC properties will have their default values.
"},{"location":"building/#build-messages","title":"Build Messages","text":"When you use the Build or Run commands on a TC, messages will be printed in two places depending on what kind of message it is:
You can navigate from an element in an Art file to the corresponding element in the C++ file that gets generated from that Art file. Use the context menu that appears when you right-click on an element in an Art file and invoke the command Open Generated Code. If C++ code has not yet been generated for the Art file, for example because no active TC has been set, navigation will fail with an error message.
When navigating to generated C++ code an attempt is made to put the cursor as close as possible to the relevant C++ element. However, when there is no C++ element that directly corresponds to the selected Art element, the cursor may instead be placed on a container C++ element. If there are more than one C++ element generated from a single Art element, you will be prompted for where to navigate. For example:
For C++ code snippets you can as an alternative perform the navigation using a tooltip that appears when you hover over the code snippet:
If the cursor is within the C++ code snippet when navigating, the cursor will be set at the same place in the generated C++ code. This is convenient if you start to edit a code snippet in an Art file but later realize that you want to edit it in the generated C++ code instead.
You can also navigate in the other direction, i.e. from generated C++ code to the source Art file. This is described below in Making Changes in Generated C++.
"},{"location":"building/#making-changes-in-generated-c","title":"Making Changes in Generated C++","text":"C++ code snippets that are embedded in the Art file will be enclosed by special comments in the generated C++ file. You can edit such code snippets in a generated C++ file. When you save the file your changes will be automatically propagated back to the Art file. Here is an example of what a code snippet may look like in the generated C++ code:
//{{{USR file:///c:/code-realtime/workspaces/demoWorkspace/HelloWorld.art#::HelloWorld::<TopStateMachine>::<TriggeredTransition_5>::<Effect>\n std::cout << \"Hello World!\" << std::endl;\n context()->abort();\n//}}}USR\n
The comment contains information about the source Art file and the Art element in that file that contains the code snippet.
Warning
Only make edits on the lines within the special code snippet comments. If you edit outside the comment those edits will be lost the next time the file gets regenerated. And if you change the comment itself, the propagation of changes back to the Art file will no longer work correctly.
One very common scenario where it's useful to change a code snippet in a generated file is when there is a compilation error reported in the code snippet. Navigating from that compilation error will take you to the code snippet in the generated file, and it's convenient to directly fix the problem there.
Another scenario is when you write new code in such a code snippet and want to take advantage of the editing support for C++ that is provided by your IDE, and/or need to see the full C++ context of the edited code snippet. You can navigate from the code snippet in the Art file to the code snippet in the generated file as described above.
Sometimes you may need to navigate in the other direction, i.e. from a code snippet in the generated C++ code to the source Art file. For example, when editing the effect code of a transition it can be useful to look at that transition in its state machine (either in the Art file or in a state diagram). You can Ctrl-click the hyperlink of the USR-comment to perform this navigation as shown in the picture below.
You can make edits in multiple code snippets in a generated file. When the file is saved all edited code snippets will be automatically propagated back to the Art file.
Warning
Code snippets in Art files can only be updated when there is an active TC set. Changes made in generated code snippets will be lost the next time they are generated, unless you have set the TC as active. To prevent this, always make sure the TC is set as active before you make any changes in generated files.
Pay attention to the status bar in the bottom left corner when you save a generated file. If you know at least one code snippet was modified, but still get the message shown below:
then you can know the changes failed to propagate to the Art file. If the update was successful you should instead get a message that tells how many code snippets that were updated. For example:
"},{"location":"building/#building-from-the-command-line","title":"Building from the Command Line","text":"You can build a TC from the command line by using the Art Compiler.
"},{"location":"building/art-compiler/","title":"Art Compiler","text":"The Art Compiler is a stand-alone tool which can be used for building a TC from the command-line. Using this tool makes it possible to integrate the translation of Art files into C++, and compilation of that C++ code, into your automated build process.
"},{"location":"building/art-compiler/#location-and-launching","title":"Location and Launching","text":"The Art Compiler is located in the bin
folder of the Code RealTime installation. It's a JAR file with the name artcompiler.jar
.
Tip
The folder where the Art Compiler is located can be seen from the message that is printed in the Art Server output channel when the Code RealTime extension is activated. It's located in the same folder as the Art Language Server JAR file, called artserver.jar
.
To launch the Art Compiler you need a Java Virtual Machine (JVM). You should use the same JVM as is used for running the Art Language Server (see Setup Java). Launch the Art Compiler with the java
command like this:
java <JVM options> -jar <extension-path>/bin/artcompiler.jar <Art Compiler options>\n
Often you don't need to use any JVM option, but if the application is huge you may need to increase the memory of the JVM. Refer to the documentation of your JVM for a list of available JVM options. You may want to use the same JVM options as are used when launching the Art Language Server (see the setting code-rt.languageServer.jvmArgs
), but it's not required to do so.
To test that the Art Compiler can be successfully launched you can try to invoke it without any arguments. You should see an output similar to the below:
C:\\openjdk-17\\bin\\java -jar c:\\Users\\MATTIAS.MOHLIN\\testarea\\install\\VSCode\\data\\extensions\\secure-dev-ops.code-realtime-ce-1.0.0\\bin\\artcompiler.jar\n10:24:53 : INFO : Art Compiler 1.0.0-20231212_1212\n10:24:53 : INFO : Copyright (C) HCL Technologies Ltd. 2022, 2023.\n10:24:54 : INFO : Arguments:\nUsage: java -jar artcompiler.jar <options>\nOptions:\n LIST OF OPTIONS\n\nAll options with argument can be used in format <option> <argument> or <option>=<argument>.\n
"},{"location":"building/art-compiler/#art-compiler-options","title":"Art Compiler Options","text":"The Art Compiler accepts options in the form of command-line arguments to artcompiler.jar
that start with single or double dash (-
or --
). Many options can take an argument which then needs to be of the correct type (Boolean, Path etc). You can specify the argument for an option either like this
<option> <argument>\n
or like this
<option>=<argument>\n
Below is a table that lists all options that are available for the Art Compiler. Each option is described in a section of its own below the table.
Option Argument Type buildConfig String buildVariants Path cwd Path generate N/A help N/A out Path ruleConfig String tc Path version N/A ws Path"},{"location":"building/art-compiler/#buildconfig","title":"buildConfig","text":"A build configuration is useful when you want to build a TC that uses build variants. It provides values for build variant settings and hence specifies a certain variant of the application to be built. Read more about build configurations here.
"},{"location":"building/art-compiler/#buildvariants","title":"buildVariants","text":"Specifies a Build Variants script to use for the build. Read more about build variants here.
"},{"location":"building/art-compiler/#cwd","title":"cwd","text":"Set the current working directory. By default this is the location from which you launch the Art Compiler. If you use a relative path in options that take a path as argument, such as --out or --tc, the path will be resolved against the current working directory.
"},{"location":"building/art-compiler/#generate","title":"generate","text":"By default the Art Compiler will generate C++ files and a make file, and then build the C++ code by invoking make on the make file. If you set this option then only the files will be generated, but make will not be invoked. Usually the running of make is what takes most time when building a TC, so if you for example only is interested in getting the generated files you can save time by setting this option.
"},{"location":"building/art-compiler/#help","title":"help","text":"Use this option to print information about the version and all available options. This is the same information as is printed if launching the Art Compiler without any options. If this option is passed, all other options are ignored.
"},{"location":"building/art-compiler/#out","title":"out","text":"Set the output folder which controls where generated files will be placed. By default it is set to the folder that contains the folder containing the built TC. It hence by default corresponds to the workspace folder used when building from the UI. If you want to place generated files in a different location when building from the command-line you can set this option to another folder. Relative paths specified as targetFolder in TCs will be resolved against the specified --out
folder.
Specifies which validation rules that should be enabled, and what severity the problems they find should have. Rules are configured using the same syntax as is used for the rule_config
property in an Art file. For example:
--ruleConfig \"W0009,X7001\"\n
Read more about how to configure validation rules here.
"},{"location":"building/art-compiler/#tc","title":"tc","text":"Specifies the TC to build. This option is mandatory, unless you only pass the help or version options.
"},{"location":"building/art-compiler/#version","title":"version","text":"Use this option to print the version of the Art Compiler. This version is the same as is used for the Code RealTime extension and can also be seen in the file CHANGELOG.md
in the Code RealTime installation folder. If this option is passed, all other options are ignored.
Specifies a workspace file (.code-workspace
) which will be used for resolving paths that are relative to the workspace. It's optional to use this option and it only needs to be set if any of the built TCs contain workspace-relative paths.
--ws C:/art-comp-test/validation.code-workspace\n
"},{"location":"building/art-compiler/#art-compiler-steps-and-messages","title":"Art Compiler Steps and Messages","text":"The Art Compiler performs its work using several sequential steps. During each step messages can be printed with a severity that is either INFO, WARNING or ERROR. Messages are printed to stdout
with a time stamp. If at least one error is reported when performing one of the steps, the Art Compiler stops and doesn't proceed with the next step.
The following steps are performed:
RTPredefined.art
from the Code RealTime installationNote that the last step is skipped if the generate option is set.
"},{"location":"building/art-compiler/#process-return-value","title":"Process Return Value","text":"The Art Compiler exits with a zero return value if no errors occur when building the TC. The value will be non-zero in case an error occured and in that case there will also be a printout explaining why the build failed. You can for example use the Art Compiler process return value if you launch it from a script that needs to know if the build was successful or not.
"},{"location":"building/build-cpp-files/","title":"C++ Source Files","text":"You can include regular, non-generated, C++ files in a build. This can be useful whenever you need to include some C++ code in your application that doesn't naturally belong to any particular Art element. For example, you can write utility functions or types in regular C++ files and then use these functions and types from any Art element in the application.
All files with the file extension .cpp
that are present in a workspace folder, or in a subfolder of the workspace folder, are assumed to be C++ source files and will be compiled together with generated C++ files. They will be compiled with the same compiler flags as are used for generated C++ files, and resulting object files will be placed in the target folder (in the same subfolder hierarchy as in the workspace folder).
All files with the file extension .h
that are present in a workspace folder, or in a subfolder of the workspace folder, are assumed to be C++ header files. The folders where they are located will be added automatically to the inclusionPaths property so they will be found by the preprocessor.
As an example, let's assume we have a workspace folder with the following structure:
The file utils.h
contains declarations of some utility functions, and utils.cpp
contains their implementations. To use one of these utility functions from main.art
you just need to include the header file. For example:
capsule Main {\n [[rt::impl_preface]]\n `\n #include <iostream>\n #include \"utils.h\" // Automatically added to inclusion paths\n `\n\n statemachine {\n state S1;\n initial -> S1\n `\n std::string str(\"Hello \");\n concat_string(str, \"World!\"); // Utility function defined in utils.cpp\n std::cout << str << std::endl;\n `;\n };\n};\n
It's recommended to place C++ source files in subfolders as in the example above. If you place them directly in the workspace folder you need to ensure they don't have the same name as any of the Art elements that are built. This is to ensure there are no name clashes between the object files that are produced when building.
"},{"location":"building/build-cpp-files/#excluding-source-files","title":"Excluding Source Files","text":"By default all .cpp
and .h
files that are found in a workspace folder, or in a folder that is directly or indirectly contained in the workspace folder, will be included in the build. If you want to exclude some of these files from the build you can use the sources
TC property, just as for Art files. All .cpp
and .h
files that match at least one pattern in the sources
property, and doesn't match an anti-pattern (starting with the character !
) will be included into the build.
Note
If you set the sources
property to explicitly include some specific .cpp
and .h
files you also need to specify which .art
files to include. That is, make sure the specified patterns match all files that should be included in the build (both .cpp
, .h
and .art
files). If you only specify anti-patterns (i.e. files to exclude from the build) you don't need to do this, since by default all files are included except the ones that are excluded.
Here are some examples:
tc.sources = [\"**/*.cpp\", \"**/*.h\", \"*.art\"]; // Include all .cpp and .h files from the workspace folder and all its subfolders, and all .art files from the workspace folder. This is the default behavior if the \"sources\" property is not set.\ntc.sources = [\"src/utils.cpp\", \"include/utils.h\", \"*.art\"]; // Include a specific .cpp and .h file into the build\ntc.sources = [\"!src/utils.cpp\", \"!include/utils.h\"]; // Include all .cpp and .h files except two specific ones (all .art files will be built)\n
If you have set-up the TC to place generated C++ files in a subfolder of the workspace folder, you don't have to exclude them by means of the sources
TC property. Generated files will always be included in the build exactly once, no matter where they are located.
The C++ extension that you use with Code RealTime makes use of JSON files for knowing how to analyze C++ source code. Such JSON files are automatically generated by the C++ code generator based on information provided in the TC:
c_cpp_properties.json
is usedcompile_commands.json
is usedWhen you add C++ source files to a workspace folder that you want to build together with the Art files it contains, you should also create such a JSON file to enable the C++ extension to also analyze these files. Without doing so, features such as content assist, navigation and hover tooltips will not work when you edit those files. Rather than creating these files manually it's often easiest to copy the ones that are generated by the C++ code generator from the target folder, and then modify them as required. The most important thing to get right is the inclusion paths to make sure the C++ extension is able to find all include files required by a certain C++ source file.
"},{"location":"building/build-variants/","title":"Build Variants","text":"There is often a need to build several variants of the same application. For example:
A variant of an application may often only use slightly different build settings (for example, setting a compiler flag to include debug symbols), but in some cases the application logic may also be somewhat different (for example, use of some code that is specific to a certain target platform). Code RealTime provides a powerful mechanism, based on scripting, that allows you to build several variants of an application with minimal effort.
"},{"location":"building/build-variants/#dynamic-transformation-configurations","title":"Dynamic Transformation Configurations","text":"A TC is defined using JavaScript which is interpreted when it is built. This opens up for dynamic TC properties where the value of a property is computed at build-time. For simple cases it may be enough to replace static values for TC properties with JavaScript expressions to be able to build several variants of an application. As an example, assume that you want to either build a release or a debug version of an application. The debug version is obtained by compiling the code with the $(DEBUG_TAG)
flag. The TC can then for example look like this:
let tc = TCF.define(TCF.CPP_TRANSFORM);\ntc.topCapsule = 'Top';\nlet system = Java.type('java.lang.System');\nlet isDebug = system.getenv('DEBUG_BUILD');\ntc.compileArguments = isDebug ? '$(DEBUG_TAG)' : '';\n
TCs are evaluated using Nashorn which is a JavaScript engine running on the Java Virtual Machine. This is why we can access a Java class such as java.lang.System
to read the value of an environment variable. With this TC, and the use of an environment variable DEBUG_BUILD
, we can now build either a release or a debug version of our application depending on if the environment variable is set or not.
However, defining variants of an application like this can become messy for more complex examples. Setting up several environment variables in a consistent fashion will require effort for everyone that needs to build the application, and perhaps you even need to write a script to manage it. But the biggest problem is that the JavaScript that defines the build variants is embedded into the TC file itself. This makes it impossible to reuse a build variant implementation for multiple TCs.
"},{"location":"building/build-variants/#build-variants-script","title":"Build Variants Script","text":"A Build Variants script allows to define build variants outside the TC itself. It defines a number of high-level settings, we call them build variant settings, each of which is implemented by a separate JavaScript file. There are two kinds of build variant settings that can be defined:
A Build Variants script must have a global function called initBuildVariants
which defines the build variant settings. Here is an example where one boolean and one enumerated build variant setting are defined:
// Boolean setting\nlet isDebug = {\n name: 'Debug',\n script: 'debug.js',\n defaultValue: false,\n description: 'If set, a debug version of the application will be built'\n};\n\n// Enumerated setting\nlet optimization = {\n name: 'Optimization',\n alternatives: [\n { name: 'HIGH', script: 'opt.js', args: ['HIGH'], description: 'Apply all optimizations', defaultValue: true },\n { name: 'MEDIUM', script: 'opt.js', args: ['MEDIUM'], description: 'Apply some optimizations'},\n { name: 'OFF', script: 'opt.js', args: ['OFF'], description: 'Turn off all optimizations' }\n ]\n};\n\n// This function defines which build variant settings that are applicable for a certain TC\nfunction initBuildVariants(tc) {\n BVF.add(isDebug);\n BVF.add(optimization);\n}\n
Note the following:
name
property specifies a user-friendly name of the build variant setting.description
property to document what the build variant setting means and how it works.defaultValue
property set. This alternative will be used in case no value is provided for that build variant setting at build-time. For a boolean setting, defaultValue
should be set to either true
or false
depending on if you want the build setting to be turned on or off by default.script
property specifies the JavaScript file that implements the build variant setting. The path is relative to the location of the Build Variants script (usually they are all placed in the same folder). For a boolean setting the script is only invoked if the setting is set to true
. For an enumerated setting the script is always invoked, and the value of the enumerated setting is passed as an argument to the script using the args
property. It's therefore possible (and common) to implement all alternatives of an enumerated setting with the same script.The initBuildVariants
function gets the built TC as an argument. You can use it for defining different build variant settings for different kinds of TCs. Here is an example where a build variant setting only is defined for an executable TC:
function initBuildVariants(tc) {\n if (tc.topCapsule) {\n // Executable TC\n BVF.add(linkOptimization);\n }\n else {\n // Library TC\n }\n}\n
"},{"location":"building/build-variants/#build-variant-setting-script","title":"Build Variant Setting Script","text":"A script that implements a build variant setting is invoked twice when a TC is built. The first time a function preProcess
gets called, and the second time a function postProcess
gets called. You can define either one or both of these functions depending on your needs.
If a preProcess
function exists it will be called before the built TC is evaluated. Therefore you cannot access the TC in this function. The only input to the function is the script arguments, as defined by the args
property for an enumerated setting. The main reason for implementing a preProcess
function is to compute some data based on a build variant setting. Such data can be stored globally and later be accessed when postProcess
gets called or in a TC file when setting the value of a TC property. Here is an example:
function preProcess( targetPlatform ) {\n MSG.formatInfo(\"Building for target platform %s\", targetPlatform);\n TCF.globals().targetPlatform = targetPlatform;\n if (targetPlatform == 'Win64_MSVS') {\n TCF.globals().targetCompiler = 'MSVS';\n MSG.formatInfo(\"Building with Microsoft Visual Studio Compiler\"); \n } else {\n TCF.globals().targetCompiler = 'GCC'; \n MSG.formatInfo(\"Building with GNU Compiler\");\n }\n}\n
Here we use the MSG
object for printing messages to the build log and we use the TCF
object for storing globally some data that we have computed based on the build variant setting. Remember that the TCF
object also is available in a TC file, which means that TC properties may access the stored global data.
If a postProcess
function exists it will be called after the built TC has been evaluated. The function gets the built TC as an argument, as well as all its prerequisite TCs. For an enumerated setting it also gets the script arguments as defined by the args
property. The function can directly modify properties of both the built TC and all its prerequisites. The property values that the TCs have when the function returns are the ones that will be used in the build. Hence this function gives you full freedom to customize all TC properties so that they have values suitable for the build variant setting. Here is an example that uses the global data computed in the preProcess
function above:
function postProcess(topTC, allTCs, targetPlatform) { \n for (i = 0; i < allTCs.length; ++i) {\n if (TCF.globals().targetCompiler == 'MSVS') {\n allTCs[i].compileCommand = 'cl';\n }\n else if (TCF.globals().targetCompiler == 'GCC') {\n allTCs[i].compileCommand = 'gcc';\n } \n }\n if (targetPlatform == 'MacOS') {\n MSG.formatWarning(\"MacOS builds are not fully supported yet\"); \n }\n}\n
Also in this function we can use the TCF
and MSG
objects to access global data and to print messages to the build log. But most importantly, we can directly write the properties of the topTC
(the TC that is built) and/or allTCs
(the TC that is built followed by all its prerequisite TCs).
By modifying the compileArguments TC property the build variant setting script can set preprocessor macros in order to customize the code that gets compiled. Hence we can both customize how the application is built, and also what it will do at run-time. This makes Build Variants a very powerful feature for building variants of an application, controlled by a few well-defined high-level build variant settings.
Example
You can find a sample application that uses build variants here.
"},{"location":"building/build-variants/#build-configuration","title":"Build Configuration","text":"When building a TC that uses build variants you need to provide values for all build variant settings, except those for which you want to use their default values. These values are referred to as a build configuration. You can only specify a build configuration when building with the Art Compiler. When building from within the IDE, all build variant settings will get their default values.
Specify the build configuration by means of the --buildConfig option for the Art Compiler. A boolean build variant setting is set by simply mentioning the name of the setting in the build configuration. To set an enumerated build variant setting use the syntax setting=value
. Separate different build variant settings by semicolons. For the sample build variants script above, with one boolean and one enumerated build variant setting, a build configuration can look like this:
--buildConfig=\"Debug;Optimization=MEDIUM\"
Build variant scripts are implemented with JavaScript and run on a Java Virtual Machine (JVM) by means of an engine called Nashorn. It supports all of ECMAScript 5.1 and many things from ECMAScript 6. Since it runs on the JVM you can access Java classes and methods. See the Nashorn documentation to learn about these possibilities.
In addition to standard JavaScript and Java functionality, a build variant script can also use an API provided by Code RealTime. This API consists of a few JavaScript objects and functions. Note that there are three different contexts in which JavaScript executes in Code RealTime and not all parts of the API are available or meaningful in all contexts.
Evaluation of a TC: TCs are evaluated when they are built, but also in order to perform validation of TC properties, for example while editing the TC. JavaScript in a TC file has access to the TCF object. Typically on the first line in a TC it's used like this: let tc = TCF.define(TCF.CPP_TRANSFORM);
. Since TCs are evaluated frequently all JavaScript it contains should only compute what it necessary for setting the values of TC properties. It should not have any side-effects, and should not print any messages.
Build Variants script: A build variants script is evaluated when building a TC with the Art Compiler. This evaluation happens early with the purpose of deciding which build variant settings that are applicable for the build. You can use the BVF object in a build variants script.
Build Variant Setting script: A build variant setting script is evaluated when building a TC with the Art Compiler. It's evaluated twice as explained above. You can use the MSG and TCF objects in a build variant setting script.
This object provides a \"Build Variant Framework\" with functions that are useful when implementing a Build Variants script. The object is only available in that kind of script.
"},{"location":"building/build-variants/#bvfadd","title":"BVF.add","text":"add(...buildVariantSettings)
Adds one or several build variant settings to be available for the current build. Each build variant setting is represented by a JavaScript object that either describes a boolean or enumerated setting as explained above.
"},{"location":"building/build-variants/#bvfaddcommonutils","title":"BVF.addCommonUtils","text":"addCommonUtils(...commonUtils)
If you implement utility functions that you want to use from several scripts you can make them globally available by means of this function. For example:
let myUtils = {\n name: 'My utils', // Any name\n script: 'myUtils.js' // Script that contains global utility functions\n}\nfunction initBuildVariants(tc) {\n BVF.addCommonUtils(myUtils);\n // ...\n}\n
All functions defined in \"myUtils.js\" will now be available to be used by build variant setting scripts.
"},{"location":"building/build-variants/#bvfformatinfo","title":"BVF.formatInfo","text":"formatInfo(msg,...args)
Prints an information message to the build log. This function works the same as MSG.formatInfo.
"},{"location":"building/build-variants/#bvfformatwarning","title":"BVF.formatWarning","text":"formatWarning(msg,...args)
Prints a warning message to the build log. This function works the same as MSG.formatWarning.
"},{"location":"building/build-variants/#bvfformaterror","title":"BVF.formatError","text":"formatError(msg,...args)
Prints an error message to the build log. This function works the same as MSG.formatError. Note that the Art Compiler will stop the build if an error is reported.
"},{"location":"building/build-variants/#msg-object","title":"MSG Object","text":"This object provides functions for writing messages to the build log. Each function takes a message and optionally also additional arguments. The message may contain placeholders, such as %s, that will be replaced with the arguments. You must make sure the number of arguments provided match the number of placeholders in the message, and that the type of each argument matches the type of placeholder (e.g. %s for string).
The MSG object is only available in a build variant setting script.
"},{"location":"building/build-variants/#msgformatinfo","title":"MSG.formatInfo","text":"formatInfo(msg,...args)
Prints an information message to the build log. Example:
MSG.formatInfo(\"Building for target platform %s\", targetPlatform);\n
"},{"location":"building/build-variants/#msgformatwarning","title":"MSG.formatWarning","text":"formatWarning(msg,...args)
Prints a warning message to the build log.
"},{"location":"building/build-variants/#msgformaterror","title":"MSG.formatError","text":"formatError(msg,...args)
Prints an error message to the build log. Note that the Art Compiler will stop the build if an error is reported.
"},{"location":"building/build-variants/#tcf-object","title":"TCF Object","text":"This object provides a \"Transformation Configuration Framework\". It is available in a build variant setting script and also in a TC file.
"},{"location":"building/build-variants/#buildvariantsfolder","title":"buildVariantsFolder","text":"buildVariantsFolder() -> String
Returns the full path to the folder where the build variants script is located.
"},{"location":"building/build-variants/#buildvariantsscript","title":"buildVariantsScript","text":"buildVariantsScript() -> String
Returns the full path to the build variants script.
"},{"location":"building/build-variants/#define","title":"define","text":"define(descriptorId) -> {TCObject}
Creates a new TC object. This function is typically called in the beginning of a TC file to get the TC object whose properties are then set.
"},{"location":"building/build-variants/#gettoptc","title":"getTopTC","text":"getTopTC() -> {TCObject}
Returns the top TC, i.e. the TC that is built. You can use this from a prerequisite TC to access properties set on the top TC. For example, it allows a library TC to set some of its properties to have the same values as are used for the executable TC. This can ensure that a library is built with the same settings that are used for the executable that links with the library. Here is an example of how a library TC can be defined to ensure that it will use the same target configuration as the executable that links with it:
let tc = TCF.define(TCF.CPP_TRANSFORM);\nlet topTC = TCF.getTopTC().eval; // eval returns an evaluated TC object, where all properties have ready-to-read values (even for properties with default values)\ntc.targetConfiguration = topTC.targetConfiguration;\n
"},{"location":"building/build-variants/#globals","title":"globals","text":"globals() -> {object}
Returns an object that can store global data needed across evaluations of different JavaScript files. For an example, see above.
"},{"location":"building/build-variants/#orderedgraph","title":"orderedGraph","text":"orderedGraph(topTC) -> [{TCObject}]
Traverses all prerequisites of a TC (topTC
) and returns an array that contains them in a depth-first order. The last element of the array is the top TC itself. The function also ensures that all prerequisite TCs are loaded.
var prereqs = TCF.orderedGraph(tc);\nfor (i = 0; i < prereqs.length; ++i) {\n var arguments = prereqs[i].compileArguments;\n // ...\n}\n
"},{"location":"building/launch-configurations/","title":"Launch Configurations","text":"As described above you can launch a built executable using the Run command in the TC context menu. This is a quick and easy way to run an executable, which is sufficient for simple applications. However, the simplicity comes with several limitations:
-URTS_DEBUG=quit
which means it will run in non-debug mode.A more flexible way to launch an executable is to use a launch configuration. This is a JSON file that contains several attributes that control how to launch the executable. Using a launch configuration also gives additional benefits:
To create a launch configuration open the \"Run and Debug\" view from the activity bar and then click the create a launch.json file hyperlink.
You can choose to store the launch configuration either in a workspace folder or in the workspace file. In most cases it makes sense to store it in the same workspace folder that contains the TC that will be used for building the application to launch. However, if you want to share the same launch configuration for multiple applications you can choose to store it in the workspace file instead.
When you choose to store the launch configuration in a workspace folder it will be placed in the .vscode
folder and get the name launch.json
:
The created launch configuration looks like this:
{\n \"type\": \"art\",\n \"request\": \"launch\",\n \"name\": \"runTC\",\n \"tc\": \"${workspaceFolder}/${command:AskForTC}\"\n}\n
You should change the name
attribute to give the launch configuration a more meaningful name.
You can have multiple launch configurations in the same launch.json
file. Create new ones either by copy/paste of another one, or by pressing the Add Configuration... button. You can also create new launch configurations using the drop down menu in the \"Run and Debug\" view:
The name of a launch configuration appears in the launch configuration drop down menu:
Launch the launch configuration by pressing the green arrow button that appears to the left of this drop down menu.
You can also perform the launch from the Run
menu, using the command Run without Debugging (Ctrl+F5). In that case the launch configuration that is selected in the \"Run and Debug\" view drop down menu will be used.
Below is a table that lists all attributes that can be used in a launch configuration. Each attribute is described in a section of its own below the table.
Attribute Description Mandatory type The type of launch configuration. Always \"art\". Yes request What the launch configuration will do. Always \"launch\". Yes name The name of the launch configuration Yes tc The TC file to use for building and launching the application. Yes args Command line arguments to pass to the launched application. No environment Environment variables to set for the launched application. No cwd Current working directory for the launched application No"},{"location":"building/launch-configurations/#type","title":"type","text":"This attribute specifies the type of launch configuration. It is mandatory and is always the string \"art\".
"},{"location":"building/launch-configurations/#request","title":"request","text":"This attribute specifies what the launch configuration will do. It is mandatory and is always the string \"launch\".
"},{"location":"building/launch-configurations/#name","title":"name","text":"Specifies the name of the launch configuration. You should give a meaningful and unique name to each launch configuration that describes what it does. The chosen name appears in the drop down menu in the \"Run and Debug\" view. When you first create a launch configuration it gets the name \"runTC\". Make sure to change this default name.
"},{"location":"building/launch-configurations/#tc","title":"tc","text":"This attribute specifies which TC file to use for building and launching. The specified TC must build an executable. You must use an absolute path to the TC file, but it can contain the ${workspaceFolder}
variable which expands to the location of the workspace folder. By default the tc
attribute is set to ${workspaceFolder}/${command:AskForTC}
which means you will be prompted for choosing which TC to use when the launch configuration is launched.
For a list of other variables that can be used in this attribute see this page.
"},{"location":"building/launch-configurations/#args","title":"args","text":"Specifies the command-line arguments for the launched executable. This is a list of strings, and by default it is set to [\"-URTS_DEBUG=quit\"]
which means that the executable will run in non-debug mode. You may add custom command-line arguments for your application as necessary. For example:
{\n \"type\": \"art\",\n \"request\": \"launch\",\n \"name\": \"Let my exe listen to a port\",\n \"tc\": \"${workspaceFolder}/app.tcjs\",\n \"args\": [\"-URTS_DEBUG=quit\", \"--port=12345\"]\n}\n
"},{"location":"building/launch-configurations/#environment","title":"environment","text":"Specifies environment variables to be set for the launched executable. This is a list of objects where each object has a property that specifies the name of an environment variable. The environment variable will be set to the value of that property. In the example below the environment variable LD_LIBRARY_PATH
will be set to /libs/mylibs
to tell a Linux system where to load shared libraries needed by the application.
{\n \"type\": \"art\",\n \"request\": \"launch\",\n \"name\": \"Launch and load shared libraries\",\n \"tc\": \"${workspaceFolder}/app.tcjs\",\n \"environment\": [{\"LD_LIBRARY_PATH\" : \"/libs/mylibs\"}]\n}\n
"},{"location":"building/launch-configurations/#cwd","title":"cwd","text":"By default the launched application runs in the same folder as where the executable is located. By setting this attribute you can change the current working directory to something else. The value of this attribute must be an absolute path, but certain variables can be used. See this page for more information.
"},{"location":"building/transformation-configurations/","title":"Transformation Configurations","text":"A transformation configuration (or TC for short) contains all properties needed for transforming Art files into C++ code and for building the generated code into an application or a library. It is a text file in JavaScript format with the file extension .tcjs. Using JavaScript for defining build properties has many advantages. For example, it allows for dynamic properties where the value is not a static value but computed dynamically by JavaScript code when the TC is built.
Code RealTime provides a dedicated language server for TCs to make them just as easy to work with as Art files. A form-based editor is also provided as an alternative.
"},{"location":"building/transformation-configurations/#creating-transformation-configurations","title":"Creating Transformation Configurations","text":"To create a new TC select a file in the workspace folder that contains the Art files you want to transform to C++. Then invoke the command File - New File - Transformation Configuration. In the popup that appears specify the name of the TC or keep the suggested default name.
A .tcjs file will be created with the minimal contents. Specify the mandatory topCapsule property (if you are building an executable) and any other properties needed.
"},{"location":"building/transformation-configurations/#setting-a-transformation-configuration-as-active","title":"Setting a Transformation Configuration as Active","text":"You can have more than one TC in your workspace, and also multiple TCs in the same workspace folder, but at most one TC in each workspace folder can be active. Code RealTime uses the active TC in several ways:
--tc
option for the Art Compiler.Note
If you don't set a TC as active none of the above will work (or will work incorrectly). It's therefore strongly recommended to create a TC and set it as active as early as possible when you start to work in a new Art workspace folder.
Set a TC as active by right-clicking on it and perform the command Set as Active. An active TC is marked with a checkmark.
If the active TC has prerequisites, those too will be set as active. This ensures that the results you get when working with Art files in the IDE will be the same as when you will build the TC using the Art Compiler.
You can deactivate an individual TC by removing the file art_build_settings.json
in the .vscode
folder. To deactivate all TCs in the workspace use the Deactivate All command in the Art Build view.
You can edit a TC directly as a JavaScript file in the text editor. Features such as content assist, navigation and hover tooltips work very similar to how they work for an Art file:
tc.
to get the list of all available TC properties that can be set. You can also use content assist in many places to get suggestions for valid TC property values, for example the list of available top capsules.As an alternative to editing a TC as a JavaScript file Code RealTime also provides a form-based editor which may be easier to use, especially until you are familiar with all TC properties that exist and what they mean.
To open the form-based TC editor, right-click on a TC file and invoke the context menu command Edit Properties (UI).
Each available TC property has its own widget for viewing and editing the value. The type of widget depends on the type of TC property. For example, an enumerated property like \"C++ Code Standard\" uses a drop down menu.
Click the info button to view documentation about a certain TC property. Click the button again to hide the documentation.
Certain TC properties have default values. Such values are not stored in the TC file, but the TC editor still shows them so you can know what value will actually be used unless you set a custom value for such a property.
You can tell which TC properties that have a custom (i.e. non-default) value set by looking at the color of the property name. Properties with custom values set have names shown in blue which are hyperlinks that will navigate to the value in the TC file. Such properties also have a \"Delete\" button which can be used for deleting the property value (i.e. to restore the property to use its default value).
You can freely choose if you want to edit TC files as text files or using the form-based TC editor, and you can even use both at the same time. The form-based TC editor is automatically updated as soon as you edit the TC file, and the TC file is automatically updated when a widget with a modified value loses focus.
"},{"location":"building/transformation-configurations/#transformation-configuration-prerequisites","title":"Transformation Configuration Prerequisites","text":"A TC can either build a library or an executable. This is controlled by the topCapsule property. If this property is set the TC will build an executable, otherwise it will build a library. To ensure that a library gets built before an executable that links with it, you can set the prerequisites property of the executable TC to reference the library TC. Doing so will also cause the executable to link with the library automatically (i.e. you then don't need to manually set-up necessary preprocessor include paths or linker paths using other TC properties).
If you change the prerequisites of a TC you should again set it as active so that the prerequisite TCs also become active.
"},{"location":"building/transformation-configurations/#accessing-the-top-tc-from-a-prerequisite-tc","title":"Accessing the Top TC from a Prerequisite TC","text":"A library TC that is built as a prerequisite of an executable TC should typically use the same values for many TC properties as are used by the executable TC. For example, both TCs should be compiled with the same compiler and use the same TargetRTS configuration, otherwise build inconsistencies could occur. To avoid the need of duplicating such common properties both in the executable TC and in all its prerequisite library TCs, it's possible to access properties of the executable TC in the library TC. Here is an example:
tc.compileArguments = TCF.getTopTC().eval.compileArguments;\n
The function TCF.getTopTC()
will at build-time return a reference to the TC that is at the \"top\" of the prerequisite hierarchy, i.e. the TC on which the build command is performed. It's typically an executable TC, but could also be a library TC (in case you build a library that depends on other libraries).
Since we construct a TC property value based on the value of another TC property (here coming from the top TC) we should use the eval
property to ensure all default values get expanded into real values.
Code RealTime provides a view called Art Build which makes several workflows related to TCs easier. The view shows all TCs that are present in the workspace so you don't have to find them in the Explorer view under each workspace folder. For each TC its prerequisites are shown below in a tree structure. This allows to quickly see which TCs a certain TC depends on through its prerequisites without having to open the TC editor.
The smaller gray text to the right of the TC name tells in which workspace folder the TC is located. This helps since it's common to have TCs with the same name in a workspace.
You can edit a TC by double-clicking on it. This will open the TC in a text editor.
When a TC is selected in the Art Build view you can use the toolbar buttons for building, cleaning and running it.
Tip
It's common to build the same TC many times when developing an Art application. By keeping that TC selected in the Art Build view you can quickly build it by just pressing its Build toolbar button. Building the TC from the Explorer view requires you to first find it which can be cumbersome since the Explorer view selection changes frequently.
There are also a few useful commands in the Art Build view toolbar:
Refresh In most cases the Art Build view refreshes automatically when TCs are modified. However, if needed you can force a refresh by pressing this button.
Clean All Cleans all TCs by removing all target folders in the workspace. Everything contained in the target folder will be deleted (generated code, makefiles, built binaries, etc). A message will be printed in the Art Server channel in case all target folders could be successfully removed, or if not, which ones that could not be deleted.
Collapse All Collapses all TCs to not show any prerequisites.
Deactivate All Makes all TCs non-active. This makes it easier to switch from building one set of active TCs to another.
Below is a table that lists all properties that can be used in a TC. Note that many TC properties have default values and you only need to specify a value for a TC property if its different from the default value. Each property is described in a section of its own below the table.
Property Type Default Value capsuleFactory String N/A commonPreface String N/A compileArguments String N/A compileCommand String \"$(CC)
\" copyrightText String N/A cppCodeStandard Enum string \"C++ 17\" eval TC object N/A executableName String \"$(TOP_CAPSULE)$(EXEC_EXT)
\" inclusionPaths List of strings [] libraryName String \"$(LIB_PFX)$(TCONFIG_NAME)$(LIB_EXT)
\" linkArguments String N/A linkCommand String \"$(LD)
\" makeArguments String N/A makeCommand String \"$defaultMakeCommand
\" prerequisites List of strings [] sources List of strings [\"*.art\"] targetConfiguration String Depends on current operating system targetConfigurationName String \"default\" targetFolder String Name of TC with \"_target\" appended targetRTSLocation String \"${code_rt_home}
/TargetRTS\" threads List of Thread objects List of two Thread objects (MainThread and TimerThread) topCapsule String N/A unitName String \"UnitName\" userLibraries List of strings [] userObjectFiles List of strings []"},{"location":"building/transformation-configurations/#capsulefactory","title":"capsuleFactory","text":"This property can be used for specifying a global capsule factory that can control how all capsule instances in the application are created and destroyed. One scenario where this is useful is when implementing dependency injection for capsule creation. See Capsule Factory and Dependency Injection for more information.
The value of this property should be a C++ expression of type RTActorFactoryInterface*
. If the expression contains the variable $(CAPSULE_CLASS)
it will be replaced with the name of the C++ class for the capsule. This can be useful for implementing a generic capsule factory which takes the capsule class as a template parameter.
tc.capsuleFactory = '&CapsuleFactory::factory';\n
Note that you can override the use of the global capsule factory by providing a local capsule factory for a specific part.
"},{"location":"building/transformation-configurations/#commonpreface","title":"commonPreface","text":"This property allows you to write some code that will be inserted verbatimly into the header unit file (by default called UnitName.h
). Since the header unit file is included by all files that are generated from the TC, you can use the common preface to define or include definitions that should be available everywhere in generated code.
tc.commonPreface = `\n#include <iostream>\n`;\n
"},{"location":"building/transformation-configurations/#compilearguments","title":"compileArguments","text":"Specifies the arguments for the C++ compiler used for compiling generated C++ code. Note that some compiler arguments may already be specified in the TargetRTS configuration that is used, and the value of this property will be appended to those standard compiler arguments.
tc.compileArguments = '$(DEBUG_TAG)'; // Compile code for debugging\n
"},{"location":"building/transformation-configurations/#compilecommand","title":"compileCommand","text":"Specifies which C++ compiler to use for compiling generated C++ code. The default value for this property is $(CC)
which is a variable that gets its value from the TargetRTS configuration that is used.
This property may be used to insert a common comment block in the beginning of each generated file, typically a copyright text.
tc.copyrightText = \n`\nCopyright \u00a9 2024 \nAll rights reserved!\n`;\n
You can use a multi-line text with empty lines in the beginning and end as shown above, to make the TC more readable. Such empty lines will be ignored by the code generator.
"},{"location":"building/transformation-configurations/#cppcodestandard","title":"cppCodeStandard","text":"Defines the C++ language standard to which generated code will conform. The default value for this property is C++ 17
. Other valid values are C++ 98
, C++ 11
, C++ 14
and C++ 20
. Note that the latest version of the TargetRTS requires at least C++ 11, so if you use an older code standard you have to set TargetRTSLocation to an older version of the TargetRTS that doesn't contain any C++ 11 constructs. If you need to compile generated code with a very old compiler that doesn't even support C++ 98 you can set this preference to Older than C++ 98
.
This is a special property that returns a copy of the TC where all properties with implicit default values have been expanded into real values. It's useful when you want to define a TC property value based on the value of another TC property which has a default value.
As an example consider this TC file which builds an application where the copyright statement is placed in the unit header file:
let tc = TCF.define(TCF.CPP_TRANSFORM);\ntc.commonPreface = `\n/** Copyright by me */\n`;\ntc.copyrightText = \"See copyright in \" + tc.eval.unitName + \".h\";\n
This TC doesn't set the value of the unitName property, but it has a default value (\"UnitName\") which can be obtained by means of the eval
property. If you would directly access tc.unitName
in the above example, the string \"undefined\" would be returned when the unitName property has not been explicitly set.
The eval
property is often used together with the TCF.getTopTC()
function as described here.
Specifies the name of the executable that is built by the TC. This property is only applicable for TCs that build executables.
The default value of this property is \"$(TOP_CAPSULE)$(EXEC_EXT)
\" where $(TOP_CAPSULE)
is the name of the topCapsule
and $(EXEC_EXT)
is a variable for the file extension which gets its value from the TargetRTS configuration that is used.
Specifies additional include paths for the C++ preprocessor in addition to \"standard\" ones such as the location of TargetRTS include files. If your application links with a user library or user object file you need to add the location of the header file(s) that belong to the library or object file.
tc.inclusionPaths = [\"/libs/myLib1/includes\", \"/libs/myLib2/includes\"];\n
Note that you don't need to add inclusion paths for target folders of prerequisite TCs. They are added automatically by the make file generator.
"},{"location":"building/transformation-configurations/#libraryname","title":"libraryName","text":"Specifies the name of the library that is built by the TC. This property is only applicable for TCs that build libraries.
The default value of this property is \"$(LIB_PFX)$(TCONFIG_NAME)$(LIB_EXT)
\" where $(LIB_PFX)
is a prefix and $(LIB_EXT)
is a file extension which get their values from the TargetRTS configuration that is used. $(TCONFIG_NAME)
expands to the name of the TC file (only the base name, not including the file extension).
Specifies the arguments for the C++ linker used for linking object files and libraries into an executable. This property is only applicable for TCs that build executables.
tc.linkArguments = '/DEBUG'; // Build executable for debugging with Visual Studio\n
"},{"location":"building/transformation-configurations/#linkcommand","title":"linkCommand","text":"Specifies which C++ linker to use for linking object files and libraries into an executable. The default value for this property is $(LD)
which is a variable that gets its value from the TargetRTS configuration that is used. This property is only applicable for TCs that build executables.
Specifies the arguments for the make command to be used.
tc.makeArguments = '-s'; // Silent make (do not print build commands to the terminal)\n
"},{"location":"building/transformation-configurations/#makecommand","title":"makeCommand","text":"Specifies which make command to use for processing the generated make file. By default the make command is $defaultMakeCommand
which gets its value from which TargetRTS configuration that is used.
This property is a list of references to other TCs that need to be built before the current TC. It's typically used to express that a library TC is a prerequisite of an executable TC, which means the library TC needs to be built before the executable TC. Below is an example where an executable TC has a library TC as a prerequisite:
tc.prerequisites = [\"../MyLibrary/lib.tcjs\"]; \n
Prerequisite TCs can either be specified using absolute or relative paths. Relative paths are resolved against the location of the TC that has the property set.
You can also use the predefined variable ${workspaceFolder}
in prerequisite paths. This is often useful as it makes it possible to reference a prerequisite TC based on another workspace folder, regardless of where in the file system it's located. For example:
tc.prerequisites = [\"${workspaceFolder:MyLibrary}/lib.tcjs\"]; \n
Note that use of workspace-relative paths requires setting the -ws option for the Art Compiler.
For more information about this property see Transformation Configuration Prerequisites.
"},{"location":"building/transformation-configurations/#sources","title":"sources","text":"By default all Art files that are located in the same folder as the TC will be transformed to C++. Sometimes you may want to exclude some Art files, for example because they are built with another TC, or they contain something you want to temporarily exclude from your application without having to delete the files or comment out their contents. In these cases you can set the sources
property to specify exactly which Art files that should be built by the TC. The value of the property is a list of strings that specify glob-style patterns and anti-patterns. An Art file will be transformed if it matches at least one pattern and doesn't match any anti-pattern. Anti-patterns start with the !
character. In both patterns and anti-patterns you can use the wildcards *
(matches any sequence of characters) and ?
(matches a single character). Below are a few examples:
tc.sources = [\"*.art\"]; // Transform all Art files in the folder that contains the TC. This is the default behavior if the \"sources\" property is not set.\ntc.sources = [\"cap1.art\", \"cap2.art\"]; // Only transform two specific Art files\ntc.sources = [\"*.art\", \"!code_rt_gen.art\"]; // Transform all Art files except one\ntc.sources = [\"!code_rt_gen.art\"]; // Same as above (i.e. the pattern \"*.art\" is optional)\ntc.sources = [\"source??.art\", \"!*_gen.art\"]; // Transform all Art files with names starting with \"source\" and followed by two arbitrary characters. Art files with a name that ends with \"_gen\" are excluded.\n
Example
You can find a sample application that has a TC with the \"sources\" property set here.
The sources
property can also be used to specify which regular (i.e. non-generated) C++ files that should be included in the build. See this chapter for more information.
Specifies which TargetRTS configuration to use. The TargetRTS location specified in the targetRTSLocation property defines valid values for this property. If this property is not specified, and the default TargetRTS location from the Code RealTime installation is used, then it will get a default value according to the operating system that is used. For Windows a MinGw-based configuration will be used, while for Linux a GCC-based configuration will be used.
tc.targetConfiguration = \"WinT.x64-VisualC++-17.0\";\n
"},{"location":"building/transformation-configurations/#targetconfigurationname","title":"targetConfigurationName","text":"This property maps to a subfolder of the target folder where all generated files that are not source code will be placed. This includes for example makefiles and the files that are produced by these makefiles (typically binaries). The default value of this property is default
.
Specifies where files generated from the TC will be placed. This property is usually just set to a name, and then a target folder with that name will be created next to the folder that contains the TC. When the build runs from the UI, that target folder is then added as a workspace folder, which will cause you to be prompted about if you trust the authors of the files in that folder. It's safe to answer yes since all files in that folder are automatically generated by Code RealTime.
You can specify an absolute path to a target folder if you prefer generated files to be placed in a certain location that is accessible to everyone in the team, for example a shared network drive. If you instead use a relative path it will get resolved relative to a desired output folder. For a UI build the output folder is set using the setting code-rt.build.outputFolder
. When building with the Art Compiler the output folder is instead set using the --out
option. If you haven't set the desired output folder, relative paths will instead be resolved against the folder that contains the workspace folder that contains the TC where the targetFolder
property is set.
Use forward slashes as path separator in this property.
If this property is not specified it defaults to the name of the TC, with _target
appended. For example, if the TC is called app.tcjs
, the target folder will default to app_default
.
Specifies the location of the TargetRTS to use. If no value is set for this property the TargetRTS from the Code RealTime installation will be used. If you want to use another TargetRTS specify the full path to the TargetRTS
folder (including that folder itself). Use forward slashes as path separator. For example:
tc.targetRTSLocation = \"C:/git/rsarte-target-rts/rsa_rt/C++/TargetRTS\";\n
"},{"location":"building/transformation-configurations/#threads","title":"threads","text":"Specifies the threads used by the application. If no value is set for this property the application will have two default threads:
MainThread Runs all capsule instances in the application. It's implemented in the TargetRTS by the RTPeerController class.
TimerThread Runs all timers in the application. It's implemented in the TargetRTS by the RTTimerController class.
Both these threads will have a stack size of 20kB and run at a normal priority.
If your application needs a different thread configuration, or threads with different properties, you need to set the threads
property. Note that you cannot just specify threads in addition to the default ones mentioned above, but must always specify all threads. Here is an example where the default threads are present, plus one additional user-defined thread:
tc.threads = [\n{\n name: 'MainThread',\n implClass: 'RTPeerController',\n stackSize: '20000',\n priority: 'DEFAULT_MAIN_PRIORITY'\n},\n{\n name: 'TimerThread',\n implClass: 'RTTimerController',\n stackSize: '20000',\n priority: 'DEFAULT_TIMER_PRIORITY'\n},\n{\n name: 'MyThread',\n implClass: 'RTPeerController',\n stackSize: '20000',\n priority: 'DEFAULT_MAIN_PRIORITY',\n logical: [\n 'MyLogicalThread'\n ]\n}\n];\n
Note that for user-defined threads, like MyThread
above, you need to specify one or many logical threads that are mapped to it. These are references to threads that your application use instead of refering directly to a physical thread. This indirection makes it possible to change how capsule instances of your application are run by threads by only modifying the threads
property in the TC, without the need to change any application code.
Only executable TCs can define physical threads. A library TC can, however, define logical threads. An executable TC that has such a library TC as its prerequisite must map those logical threads to physical threads. Here is an example of a library TC that defines a logical thread.
tc.threads = [ `LibraryThread` ];\n
Note that in this case the threads
property contains a list of strings rather than a list of objects as is the case for an executable TC.
Read more about threads here.
"},{"location":"building/transformation-configurations/#topcapsule","title":"topCapsule","text":"Specifies the capsule that should be automatically incarnated when the executable starts to run. Hence this property is only applicable for TCs that build executables, and for those TCs it's a mandatory property. The top capsule is the entry point of the realtime application.
If you don't specify a value for this property, the TC will build a library instead of an executable.
"},{"location":"building/transformation-configurations/#unitname","title":"unitName","text":"Specifies the base name of the so called unit header and implementation files that are generated from the TC. By default the value of this property is UnitName
which means that these unit files will be called UnitName.cpp
and UnitName.h
. The unit files contain certain information that applies to the whole unit of code that is generated from a TC. The header unit file is included by all files that are generated from the TC.
This property is a list of user libraries that should be linked with the application. The property is only applicable for TCs that build executables.
tc.userLibraries = [\"../../libs/libMyLib.a\"];\n
Each library should be specified with a full or relative path so the linker can find it. If no path is provided you may need to provide a link argument to specify the location(s) where the linker should look for the user libraries.
"},{"location":"building/transformation-configurations/#userobjectfiles","title":"userObjectFiles","text":"This property is a list of user object files that should be linked with the application. The property is only applicable for TCs that build executables.
tc.userObjectFiles = [\"../../objs/extra.obj\"];\n
Each object file should be specified with a full or relative path so the linker can find it. If no path is provided you may need to provide a link argument to specify the location(s) where the linker should look for the object files.
"},{"location":"releases/","title":"Releases","text":"All releases of Code RealTime are available on the Visual Studio Marketplace and the Open VSX Registry.
Release notes can be found here.
"},{"location":"releases/CHANGELOG/","title":"1.0.5 (2024-08-06 13:21)","text":"RTPredefined.art
and all files from the TargetRTS. Having these files read-only helps to avoid accidental modifications, but if you anyway want to edit them you can do so by updating the setting files.readonlyInclude
.MacT.AArch64-Clang-15.x
.RTJsonResult::keys_begin()
and RTJsonResult::keys_end()
which make it possible to iterate over the keys of a parsed JSON string. This makes the JSON parser useful also for scenarios where the expected keys are not statically known._Actor
when referencing a capsule from C++ code, for example when defining a capsule member function. This makes the code more readable and less typing is required. Note, however, that this change is not backwards compatible; existing references must be updated to remove the _Actor
suffix. Note also that the type descriptor object, which previously had the same name as the capsule, now has the prefix RTType_
. This prefix was already used for non-capsule types. If you have existing references to the type descriptor object of a capsule, for example in calls to RTFrame::incarnate()
, you therefore need to add the RTType_
prefix in those places.abstract
for marking capsules and classes as being abstract. This is an alternative to adding a pure virtual function to the capsule/class, and is better for scenarios where no pure virtual functions are needed, but the capsule/class should still be abstract (i.e. not possible to instantiate). Another benefit is that for an abstract capsule certain validation rules that check the correctness of state machines will not report a problem even if the capsule state machine is not correct. This makes it possible to create an abstract base capsule with an incomplete state machine which derived capsules can inherit from, reuse and specialize.rt::decl
code snippets of a capsule, looking for pure virtual functions. If at least one such function is found, without a matching overridden function, the capsule is implicitly abstract, and treated by the code generator as if it was declared with the abstract
keyword.rt::decl
code snippets of a capsule, looking for a user-defined destructor. If a user-defined destructor is present, the code generator doesn't generate the capsule destructor it otherwise generates. For an example, see this sample.RTActor
base class (such as _predestroy()
).notify
, publish
and subscribe
. Another improvement is that checkboxes for properties that are not applicable for a port with a predefined protocol (such as Timing
or Frame
) will now be hidden.Timing
or Frame
) is declared with properties that are not applicable for such a port (for example notify
or unwired
).$(CAPSULE_CLASS)
in the value for the capsuleFactory
TC property. It will be replaced with the name of the C++ class that is generated for the capsule. This can be useful for implementing a generic capsule factory which takes the capsule class as a template parameter.TCF.getTopTC()
function and the special eval
property. This is useful in order to allow the same library to be built as a prerequisite from multiple executables. For more information, see the documentation.executableName
and libraryName
.inclusionPath
property. The sources
TC property can be used to precisely control which C++ source files that should be included into the build. For more information, see the documentation.SUPERMETHOD
and CALLSUPER
for redefined transitions. They make it possible to call the effect or guard code of an inherited transition from a redefined transition, without having to place such code in a capsule member function. For an example, see this sample.rt::decl
code snippets of a capsule, looking for user-defined constructors. If at least one user-defined constructor is present, it doesn't generate the default capsule constructor it otherwise generates. Other parts of generated code were updated accordingly to no longer assume that a capsule has a default constructor. For an example, see this sample.sources
and prerequisites
TC properties by means of hyperlinks in the TC editor. When specified paths can be resolved to valid Art or TC files, they will become underlined, and can be opened by Ctrl+click.${workspaceFolder}
can now be used when specifying the prerequisites of a TC. This is often a better alternative than using a relative path since it allows the prerequisite TC to be located anywhere in the file system and still be found when resolving the prerequisite. As an example you can write ${workspaceFolder:MyLibrary}/lib.tcjs
where MyLibrary
is the name of the workspace folder where the prerequisite TC is located.-ws
which can be used for specifying the location of the workspace (in the form of a .code-workspace
file). It's optional to use this option, but it must be specified if built TCs contain workspace-relative paths.capsuleFactory
. This allows to globally control how capsule instances in an application are created and destroyed. One scenario where this is useful is when using the RTInjector
class from the TargetRTS to implement dependency injection for capsule creation. For an example, see this sample. Read more about these topics in new documentation chapters Capsule Factory and Dependency Injection.art_diagram_settings.json
.sources
property, is modified in the form-based TC editor, the TC file is automatically updated accordingly. Now the formatting of such TC properties is improved by placing each list element on a line of its own, and to add a trailing comma after the last element. This makes it easier to merge the TC file, without getting merge conflicts, in case more people are editing it in parallel.art_diagram_settings.json
) now uses an improved JSON format. A benefit with this new format is that it's now possible to move a workspace folder in the file system without invalidating the diagram settings. Another benefit is that each capsule part in a structure diagram can now be independently expanded, even for the case when there are multiple capsule parts typed by the same capsule.threads
property in a TC. For example, an attempt to map the same logical thread to different physical threads are reported as a problem. Previously, such problems were only detected when compiling or linking the application, or even at run-time.history*
). For an example, see this sample.const_rtdata
property. By setting this property to false
on a transition, the rtdata
parameter of the transition function will be non-const. This, for example, allows it to be moved instead of copied, which can be more efficient. For an example, see this sample.Delete
key or using the Delete command in the popup menu that appears when pressing Ctrl+Space.rt::create
and rt::destroy
code snippets. For an example, see this sample.long long
.rt::decl
code snippet you can tag it with a C++ attribute to tell the code generator how it should generate the type descriptor. Currently two attributes are supported: rt::auto_descriptor
(for fully automatic type descriptor generation) and rt::manual_descriptor
(when you prefer to write the type descriptor manually). Read more about this feature in the documentation.--buildConfig
and --buildVariants
. For an example, see this sample.rtdata
parameter in transition functions generated for non-triggered transitions. Previously this only worked for triggered transitions, and rtdata
in a non-triggered transition would become untyped (const void *). Now the code generator analyzes the whole transition chain to use the more specific type also for functions generated for non-triggered transitions. For an example, see this sample.rtistic.build.cancelOnError
can be used to control this behavior.rtistic.build.outputFolder
is now available. It can be used for setting a common output folder for generated code, and TCs where the targetFolder
specifies a relative path will now be resolved against that output folder. This new setting hence plays the same role for a UI build as the --out option does when building with the Art Compiler from the command-line.c_cpp_properties.json
file. This prevents references to definitions in prerequisite libraries to appear as broken in the UI when looking at generated code, and it also enables features such as Content Assist, hover tooltips etc for those definitions.*.art
) and doesn't match any anti-pattern (e.g. !exclude.art
). This property is not yet supported by the form-based TC editor, but can be set in the .tcjs file in order to exclude one or several Art files in the workspace folder where the TC is located.Delete
key. More than one such element can be deleted at the same time by using Ctrl+click for selecting multiple symbols or lines before pressing the Delete
key.topCapsule
property set an executable will be built. Otherwise a library will be built.rtistic.diagram.showDiagnostics
) controls if they should be shown or not.=
character. If there is not a fixed list of valid values, the expected value type (e.g. string) will be shown.Art Server
is now available and can be seen in the Output view. It's used by the Art language server for printing diagnostic messages (usually internal errors), and can be useful for troubleshooting problems.Art Build
is now available and can be seen in the Output view. It's used when building a TC. For example, messages are printed when a build starts and finishes.color
property. Currently this is supported for initial and triggered transitions.makeCommand
and makeArguments
.targetProject
and targetServicesLibrary
were renamed to targetFolder
and targetRTSLocation
respectively.The Target RunTime System (or TargetRTS for short) is a C++ library that is used by the code that is generated by Code RealTime. When building the realtime application from the generated code, it links with a TargetRTS library that has been prebuilt for the platform (hardware, operating system etc) on which the application will run.
The TargetRTS provides C++ implementations for the concepts of the Art language. The APIs of these implementations are used by the generated code, but also by the embedded C++ code that you write inside the Art files. This documentation serves the following purposes:
A realtime application runs in a target environment, which is comprised of all things that \"surround\" the application at run-time. Examples include the operating system (if any) and the target hardware. When building the TargetRTS, it's built for a particular target environment, and the resulting TargetRTS libraries become specific for that environment. The Code RealTime installation includes several TargetRTS libraries that have been prebuilt for common target environments that can be used right away. They have been created for popular combinations of C++ compilers for operating systems such as Windows, Linux, macOS and VxWorks. If none of those prebuilt TargetRTS libraries are sufficient, you can build your own.
Each way of building the TargetRTS for a specific target environment is described by means of a target configuration. It provides fixed values for parameters of the target environment, such as
These values can be put together to form a string which uniquely describes the target configuration in a compact way. Here is an example of such a string:
The first part of the name is the target which identifies the operating system name, version (if significant) and threading model. In the example above the operating system name is Windows. The version is not specified which means that the target configuration can work for more than one version of Windows. The letter 'T' shows that the application will be multi-threaded (for a single-threaded application the letter 'S' is used instead).
The second part of the name, which follows after the dot, is the libset which identifies the processor architecture and the compiler. In the example the processor architecture is x64 and the compiler is Microsoft Visual C++ version 17.0.
When you build your realtime application you need to specify the target configuration to use by means of the TC property targetConfiguration
. Valid values for this property is determined by the folder specified in the TC property targetRTSLocation
. If this property is not set, it defaults to the TargetRTS
folder inside the Code RealTime installation.
TargetRTS files are organized into subfolders under the TargetRTS
folder in the Code RealTime installation. If you set the targetRTSLocation
TC property it must point to a folder which follows the same structure.
In addition to the subfolders listed below, you may see folders with names build-<target_configuration>
. Those folders are output folders produced when building the TargetRTS. See this chapter for more information.
This folder contains various utilities in the form of Perl scripts and make file fragments. Not all of these are used in the latest version of Code RealTime but are provided for historic and backwards compatibility reasons.
"},{"location":"target-rts/#config","title":"config","text":"This folder contains a subfolder for each available target configuration. The contents of this folder defines valid values for the TC property targetConfiguration
.
The following files are present in each target configuration subfolder:
config.mk
This file is often empty, but can contain overrides of variables defined in the target or libset of the target configuration. See target and libset for the list of available variables that can be overridden.setup.pl
This file can set or override Perl variables used by the Perl script Build.pl
that is used when building the TargetRTS. Some of the variables have defaults set in Build.pl
which you only need to override if required. Others have no values set in Build.pl
and must be set in setup.pl
.This folder contains those TargetRTS header files that is used by generated code and are independent of a specific target. Target specific header files are present in the target folder.
This folder contains a file RTConfig.h
which defines a large set of C++ macros that can be used for configuring the TargetRTS. This file is included by all source files of the TargetRTS. For more information see Configure or Change the TargetRTS.
This folder contains the libraries that result from building the TargetRTS. There is one subfolder for each target configuration that has been built. Each subfolder contains two libraries (with prefixes and file extensions according to target-specific conventions):
ObjecTimeTypes
Contains the parts of the TargetRTS related to predefined types, encoding and decoding for those types, type descriptors etc. Also contains commonly used utilities such as synchronization primitives, code for TCP communication etc.ObjecTime
Contains everything else, for example implementations of the Art language concepts.In addition they also contain the main
object file which contains the implementation of the main function of the application (only used when building an executable).
This folder contains files related to the libset part of a target configuration, i.e. the processor architecture and the compiler. There is one subfolder for each libset. Each such subfolder have a file libset.mk
which defines make file variables that have a libset specific value. To avoid repeating variables that are often the same in different libsets, there is a file default.mk
which defines default values for many of these variables.
For example, the DEBUG_TAG
variable has the default value -g
, but is overridden to /Zi
for Microsoft Visual C++ libsets.
A libset subfolder may also contain a file RTLibSet.h
which can set libset specific C++ macros. It gets included from the main configuration file RTConfig.h
(see include).
This folder contains the TargetRTS source code (i.e. implementation files). It's only present in the Code RealTime Commercial Edition. The folder also contains some files needed when building the TargetRTS from its sources, such as Build.pl
.
The file MANIFEST.cpp
defines which source files that should be built. All TargetRTS source files are listed there under groups that decide which library each one should be placed in (see lib). Preprocessor conditions can be used for including or excluding some of the files depending on the target configuration that is built. For example, if a file uses a C++ 11 construct a condition must be specified that will exclude it when building a target configuration that uses a C++ 98 compiler.
The include
subfolder contains header files that are only used internally within the TargetRTS. Header files that are used by generated code are instead found in include.
The target
subfolder contains TargetRTS source code that is specific for a certain target. When you need to build the TargetRTS for a new target (e.g. a new operating system) you need to provide implementations for certain primitives which the TargetRTS uses from the operating system (e.g. to read the system clock, or create a new thread). The target/sample
subfolder contains template files that can help when doing this. For more information, see Creating a New Target Configuration.
This folder contains files related to the target part of a target configuration, i.e. the operating system, version (if significant) and threading model. There is one subfolder for each target. Each such subfolder have (at least) the following files:
target.mk
This file gets included in make files used both when compiling the TargetRTS and a realtime application that uses the TargetRTS. It may define or redefine any make file variable as required, but the following ones are typically target-specific: TARGETCCFLAGS
(compile options), TARGETLDFLAGS
(link options), TARGETLIBS
(link libraries). The values for these variables can use variables from libset.mk
(see libset).RTTarget.h
Defines target specific preprocessor macros, for example USE_THREADS
which is set to 1 for multi-threaded targets, and 0 for single-threaded targets. It gets included from the main configuration file RTConfig.h
(see include).This folder contains various utility scripts, some of which are used when building the TargetRTS.
"},{"location":"target-rts/build/","title":"Building, Debugging and Customizing","text":"Note
The information in this chapter is only applicable for the Commercial Edition of Code RealTime since it includes the source code for the TargetRTS. With the Community Edition comes only precompiled versions of the TargetRTS for a limited number of commonly used target configurations.
"},{"location":"target-rts/build/#build","title":"Build","text":"To build the TargetRTS you need to have Perl installed. On Linux and macOS Perl is usually already available, while on Windows you have to download and install it yourself. One way to get Perl on Windows is to use GitBash. See the Perl web page for other options.
Hint
As a user of Code RealTime Commercial Edition you also have access to Model RealTime which includes a version of Perl, called rtperl
. It can be found inside the plugin com.ibm.xtools.umldt.rt.core.tools
in the Model RealTime installation. If you want to use this version of Perl, locate the version under tools
that matches your operating system and add its folder to your PATH variable.
Follow these steps to build the TargetRTS from its sources:
extension/TargetRTS
into a folder. src
subfolder of the unzipped folder and invoke a command similar to the below:perl Build.pl WinT.x64-MinGw-12.2.0 make all\n
The Perl script Build.pl
drives the build process of the TargetRTS, but uses a make tool for the bulk of the work. The first argument to the script is the name of the target configuration to use. This is the same name as is specified with the TC property targetConfiguration
. The second argument to the script is the make tool to use. It corresponds to the TC property makeCommand
. The final argument is a make target defined in the file main.mk
. To build everything use the target all
.
The object files produced during the build will be placed in an output folder inside the TargetRTS
folder. The name of this folder is build-<target_configuration>
where <target_configuration>
is the name of the target configuration used. When all object files have been built, library files will be created from them and placed in a lib/<target_configuration>
sub folder. Any existing libraries in that folder, such as the precompiled versions of the TargetRTS, will be overwritten.
The Build.pl
script accepts an optional flag -flat
which, if used, should be the first argument. This flag causes the script to concatenate all source files that belong to the same class into a single file (placed in the output folder), and then build those concatenated file. This significantly reduces the number of source files to compile and therefore often speeds up the build. But beware that when debugging the TargetRTS, you will debug these concatenated files, rather than the original source code. Do not change the concatenated files as those changes will be lost the next time you build the TargetRTS with the -flat
flag.
To be able to debug the TargetRTS, you need to build it with debug symbols included. Follow these steps (either in an existing target configuration, or in a new one you have created):
libset.mk
in a text editor. This file is located in a subfolder under the libset
folder depending on what target configuration you are using. For example TargetRTS/libset/x64-MinGw-12.2.0/libset.mk
.LIBSETCCEXTRA
to include the flag $(DEBUG_TAG)
. This variable expands to the debug compilation flag of the compiler. You might also want to remove any specified optimization flags since they can make debugging harder.Build.pl
Perl script as mentioned above.You also need to build the generated code with debug symbols included. To do this set the compileArguments
property in the TC to include the $(DEBUG_TAG)
tag (and remove any optimization flags, if present).
Note
The Visual Studio compiler also requires a link argument /DEBUG
to be set to include debug symbols in an executable. Use the linkArguments
TC property to set it.
If you updated an existing target configuration and TC that were already built without debug symbols previously, make sure to do a clean build. The easiest way to do a clean build of the TargetRTS is to simply remove the entire output folder where the object files are placed (the output folder is named build-<target_configuration>
). To do a clean build of your application, perform the Clean command in the TC context menu, followed by Build.
There are many opportunities to customize the TargetRTS, and several good reasons to do so. Two common reasons are:
Examples when you need to create a new target configuration include:
Rather than creating a new target configuration from scratch, it's easier to start by copying an existing one. Pick a target configuration that resembles the one you like to create, and copy its subfolder under config
.
For example, if you want to create a new target configuration for using the latest version of the MinGw compiler on Windows, a good starting point is to copy config/WinT.x64-MinGw-12.2.0
since most things will be the same as in that target configuration. Also copy the libset folder libset/x64-MinGw-12.2.0
. Rename the copied folders for the new MinGw version and update any settings as required. For this scenario you don't need to create a new target since the existing target/WinT
can be used also for this new target configuration.
If your new target configuration is for a different operating system you also need to create a target for it. Create a subfolder under target
according to the naming conventions (see target configurations). The name of this subfolder must be used as the first part of your target configuration name (the text before the dot). Also create a new subfolder under src/target
where you can place source code that is target specific. The files in src/target/sample
can be used as a template for what code you need to write to integrate with the new operating system. Finally set the variable $target_base
in the file setup.pl
in your copied target configuration subfolder to the name of your new subfolder under src/target
. After this you can build your new target configuration.
Most configuration of the TargetRTS is done at the source code level using preprocessor macros. Each macro implements a specific configuration setting and can get its value from two files:
target/<target>/RTTarget.h
Settings specific for the target (e.g. operating system specific settings).libset/<libset>/RTLibSet.h
Settings specific for the libset (e.g. compiler specific settings).A setting can have a default value in include/RTConfig.h
which a setting in the above files can override. If needed, you can add your own macros for new configuration settings you need, but in most cases it's enough to change the value of existing settings. Below is a list of the most commonly used configuration settings. Those settings that control if a certain feature should be included in the TargetRTS have the value 1 when the feature is included, and 0 when it's not included.
Controls if the TargetRTS should use threads. Set to 0 for building a single-threaded version of the TargetRTS or 1 for building a multi-threaded version of it.
Default value: none (must be set for a target configuration, usually in target/<target>/RTTarget.h
)
Controls if the TargetRTS should keep track of statistics, such as the number of messages sent, the number of created capsules instances, etc. Collected statistics is saved per thread in the controller object, and can be printed by calling RTController::printStats()
. See RTCounts for what data that gets collected when this feature is enabled.
Default value: 0 (do not collect statistics)
"},{"location":"target-rts/build/#defer_in_actor","title":"DEFER_IN_ACTOR","text":"When a message is deferred it gets stored in a queue from where it later can be recalled. There can either be one such defer queue per capsule instance or only one defer queue per thread (i.e. stored in the controller object). Separate queues for each capsule instance will use more memory but can on the other hand result in better performance.
Default value: 0 (use one defer queue per thread). If your application doesn't use message deferral you should keep this default value.
"},{"location":"target-rts/build/#integer_postfix","title":"INTEGER_POSTFIX","text":"This is a deprecated setting that controls if the RTInteger class should support the increment (++) and decrement (--) operators. The setting is deprecated since use of RTDataObject subclasses for representing primitive types is deprecated. Use a primitive C++ type instead, such as int
.
Default value: 1 (set to 0 only if you use RTInteger and a very old C++ compiler)
"},{"location":"target-rts/build/#log_message","title":"LOG_MESSAGE","text":"By default a capsule has a function logMsg()
which gets called when a received message gets dispatched to a capsule instance, just before the message is handled by the capsule's state machine. This is a virtual function that you can override in your capsule to perform any general action needed when a message is dispatched (logging is a common, but not the only example). The default implementation is used by the debugger to log the dispatched message.
Default value: 1 (set to 0 if you don't need this feature and want to slightly improve the performance)
"},{"location":"target-rts/build/#object_decode-and-object_encode","title":"OBJECT_DECODE and OBJECT_ENCODE","text":"The TargetRTS contains features that can convert an object to a string representation (encoding) and create an object from a string representation (decoding). It can for example be used when persisting objects from memory to a file or database, when building distributed applications where data needs to be sent between processes or machines, or when using APIs of web services (often JSON).
Default value: 1 (set to 0 if you don't need this feature)
Note that encoding/decoding is controlled by two separate settings since it's possible that you need one but not the other.
"},{"location":"target-rts/build/#otrtsdebug","title":"OTRTSDEBUG","text":"This setting controls the level of debugging support that the TargetRTS will provide. There are 3 possible values:
Default value: DEBUG_VERBOSE
Note that regardless how you set this setting you can of course always build the TargetRTS with debug symbols to debug it with a C++ debugger.
"},{"location":"target-rts/build/#rtreal_included","title":"RTREAL_INCLUDED","text":"Controls if the RTReal class should be included or not. If your target environment doesn't support floating point data types, or your application doesn't use them, you can disable this feature.
Default value: 1
"},{"location":"target-rts/build/#purify","title":"PURIFY","text":"If you use tools for tracking run-time problems such as memory leaks or access violations, for example Purify, you can enable this feature. It will remove some optimizations in the TargetRTS which otherwise can hide some memory allocation and deallocation events from such tools.
Default value: 0
"},{"location":"target-rts/build/#rts_inlines","title":"RTS_INLINES","text":"Controls if the TargetRTS uses any inline functions. If enabled, inline functions of classes will be compiled with the class header file. If disabled, they will instead be compiled from a special file inline.cc
which most classes have.
Default value: 1 (set to 0 if you use an old compiler without proper support for inline functions)
"},{"location":"target-rts/build/#rts_compatible","title":"RTS_COMPATIBLE","text":"This setting is used for controlling backwards compatibility in the TargetRTS. It corresponds to the version number RT_VERSION_NUMBER
which is defined in the file RTVersion.h
.
Default value: 520. This is a very old version of the TargetRTS, but it means that by default certain deprecated code gets included to make the TargetRTS compatible for old applications that use it. If you set it to RT_VERSION_NUMBER
, then such deprecated code will not be included which will slightly reduce the size of the executable.
Controls if the TargetRTS can use TCP/IP. This is required for certain features such as debugging.
Default value: 1 (set to 0 if your target environment doesn't provide TCP/IP support)
"},{"location":"target-rts/build/#inline_methods","title":"INLINE_METHODS","text":"The member functions in a generated capsule class that contain user-defined code, such as transition or guard code, will be declared with this macro. You can set it to inline
if you want those function to be declared as inline functions. This may (or may not, depending on compiler) improve the performance, but may also lead to a larger executable.
Default value: none
"},{"location":"target-rts/build/#rtframe_checking","title":"RTFRAME_CHECKING","text":"This setting is used when you call functions on a Frame port, for example to destroy a capsule instance in a part. The recommendation is to declare Frame ports as non-service ports, so they only can be used internally by the owning capsule itself. However, if you somehow make a Frame port accessible for other capsules, they must at least be run by the same thread as the capsule that owns the Frame port.
The following values are possible for this setting:
Perform a run-time check that a Frame port is only used by the capsule that owns it.
Perform a run-time check that a Frame port is only used by code that runs in the same thread as the capsule that owns it.
Do not perform a run-time check. It improves the application performance slightly, and can be safely set if all Frame ports are only used by the capsule that owns them.
"},{"location":"target-rts/build/#rtframe_thread_safe","title":"RTFRAME_THREAD_SAFE","text":"This setting is used when you call functions on a Frame port, for example to create or destroy a capsule instance in a part. By default these functions are thread-safe, which is required when a created or destroyed capsule instance runs in a different thread than the code that calls the functions. However, in certain cases it's possible to optimize the performance by instead using function implementations that are not thread-safe. For example, if you know that you only use Frame ports to operate on capsule instances that run in the same thread as the capsules that owns the ports, then you can disable this setting to improve the application performance.
Default value: 1 (set to 0 if you know it's safe to not use thread-safe implementations of Frame functions)
"},{"location":"target-rts/build/#rtmessage_payload_size","title":"RTMESSAGE_PAYLOAD_SIZE","text":"This setting controls the size of the data area in each message. This data area is used for message data that is small enough, such as integers, booleans and short strings. If the data that is sent with a message is bigger than the specified RTMESSAGE_PAYLOAD_SIZE, then dynamically allocated memory is used for storing the message data outside of the message itself. See Message Data Area for more information about the message data area.
Default value: 100 (byte size of the message data area)
Increasing the value will make each message bigger, which makes the application consume more memory, but on the other hand it may become faster since fewer messages will require dynamic memory to be allocated for storing the message data. On the other hand, if your application mostly send very small data objects, you may benefit from decreasing the RTMESSAGE_PAYLOAD_SIZE. You need to fine-tune the value of this setting to find the optimal trade-off between speed and memory consumption for your application.
"},{"location":"target-rts/build/#observable","title":"OBSERVABLE","text":"This setting controls if the application will be \"observable\" at run-time. Target observability includes different kinds of features such as debugging, tracing etc. Disabling this setting will improve application performance and decrease memory consumption, but you will then not be able to use any of the target observability features.
Default value: 1 (set to 0 to disable all target observability features)
"},{"location":"target-rts/capsule-factory/","title":"Capsule Factory","text":"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 in a capsule part. These rules are:
new
operator and destroyed using the delete
operator.When you incarnate a capsule into an optional part using the incarnate() functions of a Frame port, you can customize rules 3, 4 and 5 above by using different overloads of incarnate()
. However, if you want to customize rules 1 and/or 2, or incarnate a fixed part, you need to provide your own capsule factory. This can be done in a few different ways.
You can provide a local capsule factory for a part by implementing the rt::create
and/or rt::destroy
code snippets. For an example see Part with Capsule Factory.
You can also provide a local capsule factory in a more dynamic way when you incarnate an optional part by calling incarnateCustom()
instead of incarnate()
. For an example see Capsule Constructor. However, note that in this case it's only possible to provide the create
implementation of the capsule factory, and capsule instances created that way will always be deleted using the delete
operator.
Scenarios where a local capsule factory could be useful include:
Example
You can find a sample application that uses a local capsule factory here.
"},{"location":"target-rts/capsule-factory/#global-capsule-factory","title":"Global Capsule Factory","text":"A global capsule factory will be used for creating and destroying capsule instances in all capsule parts in the application, except those for which a local capsule factory has been provided. Implement a global capsule factory by means of a class that inherits RTActorFactoryInterface
. You need to implement the create()
and destroy()
functions. Then define an object of this class and set the capsuleFactory
TC property to the address of that object.
Here is an example of how a global capsule factory can be implemented in an Art file called CapsuleFactory.art
:
[[rt::decl]]\n`\nclass CapsuleFactory : public RTActorFactoryInterface {\npublic: \n RTActor *create(RTController *rts, RTActorRef *ref, int index) override {\n\n // Create capsule instance here\n }\n\n void destroy(RTActor* actor) override {\n // Delete capsule instance here\n }\n\n static CapsuleFactory factory;\n};\n`\n\n[[rt::impl]]\n`\nCapsuleFactory CapsuleFactory::factory;\n`\n
In the TC, specify the capsule factory object and make sure the capsule factory header file gets included everywhere:
tc.capsuleFactory = \"&CapsuleFactory::factory\";\ntc.commonPreface = `\n#include \"CapsuleFactory.art.h\"\n`;\n
If the expression specified in the capsuleFactory
property contains the variable $(CAPSULE_CLASS)
it will be replaced with the name of the C++ class that is generated for the capsule. This can be useful for implementing a generic capsule factory which takes the capsule class as a template parameter.
Scenarios where a global capsule factory could be useful include:
Example
You can find a sample application that uses a global capsule factory here.
"},{"location":"target-rts/dependency-injection/","title":"Dependency Injection","text":"An object in an application has several \"dependencies\", i.e. various things that affect how it behaves at run-time. For a capsule part that gets incarnated with capsule instances at run-time, examples of such dependencies include which capsule to create an instance of, what data to pass to the capsule constructor, and which thread that should run the created capsule instance. But the behavior of a capsule instance also depends a lot on which other capsule instances it communicates with at run-time, so those are also examples of dependencies.
Dependency injection is a technique where run-time dependencies of objects are managed by a central injector object, instead of being hardcoded across the application. The injector is configured so it provides the desired dependencies for objects when they are needed at run-time. One benefit with using dependency injection is that objects in your application become more loosly coupled and it becomes much easier to configure and customize the behavior of your application.
There are many dependency injection frameworks for C++ which you can use for the passive C++ classes of your application. To use dependency injection for capsules, the TargetRTS provides a class RTInjector
. You can use this class for registering create-functions for capsule parts at application start-up. When the TargetRTS needs to incarnate a capsule part, it will check if a create-function is registered for it. If so, that create-function will be called for creating the capsule instance. Otherwise, the capsule instance will be created by the TargetRTS itself, as usual.
To use dependency injection in your realtime application you need to implement a global capsule factory and specify it in your TC. The create()
function of the global capsule factory delegates all calls to the RTInjector
singleton object.
You also must configure the injector by registering create-functions for all capsule parts where you want to customize how a capsule instance should be created. You need to do this early, typically at application start-up. At least it must be done before the TargetRTS attempts to create a capsule instance in a capsule part which you want to customize with dependency injection. A good place can be to do it in the constructor of the top capsule, in the main function of your application, or in the constructor of a static object (such as the global capsule factory object itself).
A create-function is registered by calling registerCreateFunction()
on RTInjector
. The second argument is the create-function, and the first argument is a string that specifies the path to the capsule part in the composite structure of the application. Such paths always start with a /
denoting the top capsule, and then follows names of capsules parts separated by /
. You can use a :
to specify the index of a capsule instance in a part with multiplicity. For example, if the application has this composite structure
then the path string /logSystem:0/logger
refers to the capsule part logger
that is contained in the LogSystem
capsule instance which is the first (index 0) capsule instance in the capsule part logSystem
of the top capsule Top
.
A call to registerCreateFunction()
to customize the incarnation of capsule instances in the logger
capsule part could then look like this:
RTInjector::getInstance().registerCreateFunction(\"/logSystem:0/logger\",\n [this](RTController * c, RTActorRef * a, int index) { \n return new TimestampLogger(c, a);\n }\n);\n
Example
You can find a sample application that uses dependency injection here.
In most cases your application will only register create-functions once at start-up. However, RTInjector
allows to do it at any time, and you can also remove or replace an already registered create-function. This makes it possible to implement very dynamic dependency injection scenarios. For example, you can change which capsule that gets instantiated depending on how much memory is currently available.
Dependency injection can for example be useful when implementing capsule unit testing in order to \"mock out\" capsules which the capsule-under-test depends on. In this case you could for example let the registration of create-functions be controlled by a configuration file that is part of the test case.
Another possibility is to combine dependency injection with Build Variants to build multiple variants of an application by means of high-level build settings (so called \"build variants\"). The build variant script can set compilation macros that control how injected create-functions behave.
"},{"location":"target-rts/encoding-decoding/","title":"Encoding and Decoding","text":"Encoding is the process of serializing data from memory into a string representation. Decoding is the opposite, i.e. deserializing a string representation of data into memory. There are many situations when it's useful to encode and/or decode:
The TargetRTS provides support for encoding and decoding data by means of the encode
and decode
functions of a type descriptor. This means that if you want to use encoding and/or decoding for a data type, you just need to make sure that it has a type descriptor with one or both of these functions implemented. The TargetRTS has a default implementation for encoding and decoding many types, including all predefined C++ types, structured types and enums. You can choose between three string formats:
If your data type is too complex for the default encode/decode implementation in the TargetRTS, or if you want to use some other format than the above two, you can write your own encode/decode functions. If you let your implementation inherit from the RTEncoding
and RTDecoding
interface classes, then the TargetRTS can seamlessly manage encoding and decoding also for your custom implementation.
If you don't need support for encoding and/or decoding in your application you should unset the macros OBJECT_ENCODE
and/or OBJECT_DECODE
both when building the TargetRTS and your application. This will reduce the application footprint.
Assume you have a data object stored in a variable data
of type T
, and the type descriptor of T
has support for encoding and decoding. The sample below will encode the object to ASCII and print it to stdout.
T data = new T(); // Object to encode\n\nchar buf[1000];\nRTMemoryOutBuffer buffer( buf, 1000 );\nRTAsciiEncoding coding( &buffer );\nRTType_T._encode_func(&RTType_T, &data, &coding);\nbuffer.write(\"\", 1); // IMPORTANT: Terminate the buffer string before printing it!\nstd::cout << \"ASCII encoding: \" << buf << std::endl << std::flush; \n
Here we call the static encode function of the type descriptor (_encode_func
) and provide the type descriptor object (RTType_T
), the object to encode (data
) and the ASCII coding object (coding
). If you instead prefer to use the JSON encoding, just change the type of the coding object from RTAsciiEncoding
to RTJsonEncoding
(see the example below).
To avoid the risk of overflowing a fixed-sized buffer, and get a slightly more compact code, you can use the RTDynamicStringOutBuffer
utility class. You can encode by calling put()
on the coding object:
RTDynamicStringOutBuffer buffer;\nRTJsonEncoding coding(&buffer);\ncoding.put(&data, &RTType_T);\nstd::cout << \"JSON encoding: \" << buffer.getString() << std::endl << std::flush; \n
RTEncoding::put()
produces a string that is prefixed with the type name. For JSON encoding it may look like this:
{T}{\"a\" : 5,\"b\" : true}\n
The {T}
prefix is needed if you later want to decode this string back to an object, but it has to be stripped off to get a string with valid JSON syntax. If you only are interested in getting the JSON encoding, without the type prefix, you can instead call RTEncoding::put_struct()
(assuming T
is a structured type).
Let's continue the above example and decode the string stored in buffer
back to an object of type T
:
RTMemoryInBuffer inBuffer(buffer.getString(), RTMemoryUtil::strlen(buffer.getString()));\nRTJsonDecoding decoding(&inBuffer);\nvoid* decodedObj;\nconst RTObject_class* type;\ndecoding.get(&decodedObj, &type);\nT* decodedData = reinterpret_cast<T*>(decodedObj);\n
Here we call RTJsonDecoding::get()
to perform the decoding. This function expects a string that is prefixed with the type name, and it will look-up the type descriptor object based on it (type
in the example). The decoded object is assigned to decodedObj
which is untyped (void*
). You can cast this pointer to the expected type (T
).
If your JSON string is not prefixed with the type name, you can instead call RTJsonDecoding::get_struct
which takes the type descriptor object as an argument.
decoding.get_struct(&decodedObj, &RTType_T);\nT* decodedData = reinterpret_cast<T*>(decodedObj);\n
Note that decoding will allocate and initialize a new object in memory. It's your responsibility to delete this object when you no longer need it. If you prefer to work with the decoded object as an untyped pointer, you can delete the object by calling the destroy function on the type descriptor like this:
type->destroy(decodedObj, RTObject_class::DestroyAndDeallocate);\n
"},{"location":"target-rts/encoding-decoding/#encoding-a-message","title":"Encoding a Message","text":"The JSON encoder has a special function put_msg
which can be used for encoding a received RTMessage
to JSON. It can for example be useful as a way to trace received messages in a standard format which other tools can read and use. Here is an example of how it can be used in a code snippet within a capsule (e.g. a transition):
RTDynamicStringOutBuffer buf;\nRTJsonEncoding coding(&buf);\ncoding.put_msg(msg);\ncout << \"Received msg: \" << buf.getString() << endl << flush;\n
The encoding includes the name of the message's event, its argument data type (if any) and the data object itself (if any). Here is an example of what it may look like:
{\n \"event\" : \"event_with_class\",\n \"type\" : \"MyClass\",\n \"data\" : {\"a\" : 8, \"b\" : false}\n}\n
"},{"location":"target-rts/encoding-decoding/#default-encodingdecoding-rules","title":"Default Encoding/Decoding Rules","text":"The default encoding/decoding in the TargetRTS follows these rules:
If you wish to change any of these rules, you can customize encoding/decoding.
"},{"location":"target-rts/encoding-decoding/#custom-encodingdecoding","title":"Custom Encoding/Decoding","text":"If you want to customize how a certain type gets encoded/decoded, you can write a custom encode and/or decode function for its type descriptor. Note that you can also define a typedef or type alias of an existing type, if you only want to change the encoding/decoding for some objects typed by that type (i.e. then change the type of those objects to your typedef or type alias instead). See this chapter for more information and examples.
If you want to encode/decode using a different format, such as another textual format or even a binary format, you need to write your own encoder and/or decoder. If possible you should let your implementation inherit from the RTEncoding
and RTDecoding
classes. That allows the TargetRTS to work seamlessly with your implementation from encode/decode functions of a type descriptor.
You can also let your encoder and/or decoder class inherit from the classes that implement ASCII and JSON encoding/decoding by overriding some of their virtual functions. This can be useful if you just want to slightly customize the ASCII or JSON encoding/decoding. For example, assume you want to change the JSON encoding to encode boolean data as strings. Then you can define your custom encoder class like below:
#include <RTJsonEncoding.h>\n\nclass CustomJsonEncoding : public RTJsonEncoding {\n public:\n CustomJsonEncoding(RTOBuffer * buffer)\n : RTJsonEncoding(buffer) {}\n\n virtual int put_bool(bool value) {\n if (output->write(\"\\\"\", 1 ) != 1)\n return 0;\n int res = RTJsonEncoding::put_bool(value);\n if (output->write(\"\\\"\", 1 ) != 1)\n return 0;\n return res;\n }\n};\n
"},{"location":"target-rts/encoding-decoding/#json-parser","title":"JSON Parser","text":"The JSON Decoder has to parse a JSON string before it can create an object representation of it in memory. However, it only needs to support parsing a subset of JSON, namely the subset of JSON which can be produced by the JSON Encoder. Because of this it doesn't need to use a general-purpose JSON parser.
There are, however, scenarios where you may need to parse JSON, not for the purpose of decoding it, but for some other reason. For example, you may get JSON as the result of making an API call, and then need to parse the JSON to more easily extract the relevant information from it. To support this scenario the TargetRTS includes a general-purpose JSON parser implemented in RTJsonParser
.
You parse a JSON string by calling RTJsonParser::parseJsonString()
. The parser result is represented by an object of RTJsonResult
. On this object you can call functions to
get_type()
)operator[const std::string&]
)operator[size_t]
)Values are also represented by RTJsonResult
and you can check their type (either null
, JSON object, array, string, number or boolean). For values with types that correspond to C++ primitive types you can call \"get_\" functions (e.g. get_bool()
to get a C++ bool from a JSON boolean value). Do not forget to first check the type of the value, because if you try to convert to the wrong kind of value, the result may be unexpected. Often it's more convenient to use one of the operator==
functions to directly compare a JSON value with the corresponding C++ value.
Here is an example of how to parse a JSON string and check the result:
RTJsonParser parser;\nRTJsonResult result;\nbool ok = parser.parseJsonString(result, \"{\\\"field1\\\" : \\\"string\\\", \\\"arr\\\" : [1,true,3.14]}\");\n\nstd::cout << result[\"field1\"].get_string() << std::endl; // \"string\"\nif (result[\"arr\"].ok()) { // Check if the \"arr\" key is present\n std::cout << result[\"arr\"].get_size() << std::endl; // 3\n if (result[\"arr\"][2] == 3.14) {} // will be true\n if (result[\"arr\"][1].get_type() == RTJsonResult::RTJSON_BOOL) {} // will be true\n}\n
Example
You can find a sample application that uses the JSON parser here.
"},{"location":"target-rts/integrate-with-external-code/","title":"Integration with External Code","text":"In most cases a realtime application contains at least some code that is not generated by Code RealTime. The application can use libraries, both 3rd party libraries and libraries you have created yourself. It can also contain other C++ code which is built together with generated code, either from within Code RealTime or from another IDE or build system. In this chapter we'll look at some techniques and best practises for integrating the code generated by Code RealTime with other code, which we here refer to as external code.
"},{"location":"target-rts/integrate-with-external-code/#external-port","title":"External Port","text":"External code can send events to a capsule in a thread-safe way by means of an external port defined on the capsule. Such a port is typed by the predefined External
protocol.
External ports can for example be useful in scenarios when external code has to wait for some event to occur or some data to become available, and then notify a capsule about it. Such external code has to run in its own thread (an external thread), to avoid blocking the application while it's waiting. In some cases it's enough to just notify the capsule about what has happened, while in other cases it's necessary to also transfer some data from the external code to the capsule. Here are examples for three typical scenarios:
float
as event data.All these types of scenarios are supported by means of external ports.
Example
You can find a sample application that uses an external port here.
"},{"location":"target-rts/integrate-with-external-code/#general-usage","title":"General Usage","text":"The capsule that owns an external port has full control over when it's ready to accept an event on the external port. This is important to prevent an external thread from \"starving\" the thread that runs the capsule. The following rules apply for how an external port must be used:
enable()
on the port. It's important that this call happens from the same thread that runs the capsule. If the port is already enabled, nothing happens and it stays enabled.raise()
on the port, and it's important that this call happens from the external thread (i.e. the capsule itself should not call it). Once an event has been raised on the external port, it automatically becomes disabled and has to be enabled by the capsule again before another event can be raised on it.disable()
on the port. If the port is already disabled, nothing happens and the port stays disabled.The state machine below shows these rules graphically:
The external code can use the return value of raise()
to know if the event was successfully raised on the port or not. It should never assume that raising the event will succeed, because it cannot know if the capsule has enabled the external port or not. If raising the event fails, the external code can choose to try again a little later, or skip it altogether. How to best handle this situation depends on the application and how important it is for the capsule to be notified about the event.
Here is code which can be called from the external thread to attempt to raise an event on an external port extPort
:
if (extPort.raise() == 0){\n // failure (possibly try again a little later)\n}\nelse {\n // success\n}\n
Note
To allow the external code to call raise()
on the external port it's necessary to somehow provide the external code with a way to access it. A good way to do this is to let the capsule implement an interface class which provides a function for raising the event on the external port. The external code can then be given a reference to the capsule through that interface class. This prevents the need to expose the external port itself to the external code, and it effectively ensures that the external code cannot do anything with the capsule except calling the provided function. See this sample for an example of how to let a capsule implement an interface class.
The capsule with the external port handles the raised event like any other event it receives (it has the name event
). Here is an example of a PushButton capsule that uses an external port for getting notified when a button is pushed:
capsule PushButton {\n\n behavior port external : External; \n\n statemachine {\n state WaitForPush {\n entry\n `\n // Enable the external port so we can receive *one* event on it\n external.enable();\n `;\n };\n initial -> WaitForPush;\n\n onButtonPush: WaitForPush -> WaitForPush on external.event\n `\n // The button was pushed\n `;\n };\n};\n
"},{"location":"target-rts/integrate-with-external-code/#passing-data","title":"Passing Data","text":"The external code can choose to pass a data argument with the event that it raises on an external port. The same rules apply for such data as for all other event data, except that it will always be copied (i.e. it's not possible to move it). Just like when data is associated with a timeout event, it's necessary to provide both the data and its type descriptor when raising an event with data. Here is an example where the raised event carries a string as data:
char* str = \"external data\";\nextPort.raise(&str, &RTType_RTpchar);\n
As with all other events, the capsule receives the passed data through the rtdata
argument in the transition that is triggered on the event called event
of the External
protocol. For example, to receive the string data used above:
RTpchar d = *((RTpchar*) rtdata);\nstd::cout << \"Received data on external port: \" << d << std::endl;\n
Sometimes data may become available in the external thread at a higher pace than what the capsule can (or want to) handle. In that case it\u2019s not convenient to pass the data to the capsule thread in the call of raise()
. Instead, the external code can call dataPushBack()
to push the data into a data area on the external port itself. The external code can push any number of data objects on the external port. The capsule can access the data, when it's ready to do so, by calling either dataPopFront()
or dataPopBack
depending on if it wants to handle the data in a \"first-in-first-out\" (FIFO) or \"last-in-first-out\" (LIFO) manner. The external code can call raise()
to notify the capsule that data is available for it to fetch. It can either do this as soon as it has pushed any data on the external port, or wait until a certain number of data objects have been pushed.
Here is an example of external code for pushing a data object consisting of a string and an integer on the external port:
std::pair<std::string,int>* data = new std::pair<std::string,int>(\"external data\", 15);\nextPort.dataPushBack(data);\n
And here is how the capsule can choose to fetch the data in a FIFO manner:
unsigned int remaining;\ndo {\n std::pair<std::string,int>* data;\n remaining = extPort.dataPopFront((void**) &data);\n if (data == 0)\n break;\n // Handle received external data here...\n delete data;\n}\nwhile (remaining > 0);\n
Note that the external code is responsible for allocating the data pushed on the external port, while the capsule is responsible for deleting the data once it has fetched it. The capsule can choose whether it wants to fetch all available data at once (as in the above example), or only some of it. However, it's important to design the application so that external port data doesn't just keep growing as that eventually would cause the application to run out of memory.
It's of course possible to implement another scheme for passing data from external code to a capsule, and the data area of an external port should just be seen as a convenience. Any data structure can be shared between the external code and the capsule, but it's important that it is thread-safe. You can for example use a mutex (see RTMutex
in the TargetRTS) for protecting data that is shared by the external thread and the capsule's thread.
The TargetRTS contains a main()
function implementation which is used when you build an executable. However, its implementation simply calls RTMain::entryPoint()
which is provided by the generated code in the unit file (by default called UnitName.cpp
). If you want to integrate the generated code with external code that already contains a main()
function you can either
The generated implementation of RTMain::entryPoint()
performs certain set-up activities such as defining the RTSystemDescriptor
which among other things contains a reference to the type descriptor of the top capsule. It then calls RTMain::execute()
which will start-up the TargetRTS and create and run an instance of the top capsule. This means that when you build an executable, the top capsule instance will always be run by the main thread. But if you instead provide the main()
function in the external code, you can create a different thread and call RTMain::entryPoint()
from that thread, and then the top capsule instance will be executed by that thread.
One example where you typically cannot let the main thread run the top capsule instance, is when the application has a user interface. This is because the main thread then already is busy running the event loop that manages the user interface. In this case you can create a separate thread responsible for running the realtime part of your application (i.e. the top capsule instance).
Example
You can find a sample application where a user interface is integrated with a realtime application here.
"},{"location":"target-rts/message-communication/","title":"Message Communication","text":"Applications developed with Code RealTime consist of objects with state machines that communicate by means of messages. This chapter describes the details of how this message-based communication is implemented in the TargetRTS.
"},{"location":"target-rts/message-communication/#controllers-and-message-queues","title":"Controllers and Message Queues","text":"As explained in Threads, an application consists of controllers each of which is run by a physical thread and is managing a group of capsule instances. The main responsibility of a controller is to facilitate the exchange of messages from one capsule instance to another. There are two kinds of message exchange:
From the application\u2019s perspective, there is no difference between sending a message within a thread and sending a message across threads; the code to send and receive the message is still the same. There is, however, a difference in performance, and message sending across threads is approximately 10-20 times slower than message sending within a thread. You should therefore assign capsule instances to controllers in a way so that those that communicate frequently with each other should be run by the same controller.
Each physical thread in the TC of an application specifies an implementation class that inherits from RTController. The default implementation is provided by the RTPeerController class, and it implements a simple event loop that in each iteration delivers the most prioritized message to the capsule instance that should handle it. Let's explore what happens internally in the TargetRTS when a capsule sends an event on a port:
myPort.myEvent().send();\n
The message is now delivered to the controller that runs the receiver capsule instance. This is done by calling RTController::receive()
. The controller has two message queues where it stores received messages, the internal queue and the incoming queue. The received message is put in one of these:
Note that both the internal and incoming message queue is actually an array of queues, one for each message priority level. The received message is inserted in the end of the queue that matches the priority of the message as specified by the sender. This ensures that messages are handled in priority order, and, within each level of priority, in a FIFO (\"first-in-first-out\") manner.
If the message was placed in the incoming queue, it gets transferred to the internal queue in the beginning of the RTController::dispatch()
function which is called once in each iteration of the controller's event loop. This happens in the function RTController::acceptIncoming()
.
RTController::dispatch()
function checks the contents of the incoming queue, starting with the queue at the highest priority level (Synchronous
), proceeding with queues at lower priority levels, until the queue at the lowest priority level (Background
). As soon as it encounters a non-empty queue it dispatches the first message of that queue. Dispatching a message is done by calling RTMessage::deliver()
, which eventually leads to a call of the RTActor::rtsBehavior()
function which implements the capsule state machine in the generated code.
Note
The control is not returned to the TargetRTS until the transition which is triggered by the received message has run to completion. This includes the triggered transition itself, and also any number of non-triggered transitions that may follow it. It also includes entry and exit actions for states. It may also involve the call of one or several guard functions that are required for determining which transition that should be triggered. Hence, the dispatching of a single message may lead to execution of several code snippets. Before they have all executed it's not possible for the controller to dispatch another message, and it's therefore important that the code that runs operates as efficiently as possible. A long-running operation should be performed by a capsule instance that runs in a different controller, to avoid blocking the execution of other capsule instances.
Finally, when the message has been dispatched, it's freed (see Message Memory Management).
From a code snippet of a capsule, such as a transition effect or guard code, you can get the message that was most recently dispatched to the capsule by accessing RTActor::msg
. You should treat this message object, and all data it contains, as read-only. Since it will be freed when control returns to the TargetRTS after the message has been dispatched, it's not safe to store any pointers to the message or the data it contains and access these at a later time. All data from the message object that you need to keep should be copied. However, if the data is big it's possible to instead move it by setting the property const_rtdata on a transition.
The picture below illustrates a controller and how messages arriving from capsule instances are placed in the incoming or the internal queue, depending on if those capsule instances run in the same or a different controller. It also shows how these queues actually are arrays of queues organized according to message priority.
"},{"location":"target-rts/message-communication/#message-priority","title":"Message Priority","text":"The sender of a message can choose between the following priority levels:
In addition to these five priority levels, there are two system-level priorities which are higher than all the above; System and Synchronous. These are used internally by the TargetRTS and cannot be used when sending user-defined messages.
As explained above, each priority level has its own message queue in the controller, and the controller looks for messages to dispatch starting from the queue with the highest priority. As soon as a message is found, it gets dispatched, and no more messages are dispatched in that iteration of the event loop. This means that if a large number of high priority messages are continously sent, they will prevent (or at least delay) dispatching of low priority events in the same controller. It's therefore best to stick to the default message priority for most messages, and only use higher and lower priority messages when really needed.
"},{"location":"target-rts/message-communication/#message-representation","title":"Message Representation","text":"A message is an instance of a protocol event and is represented by an object of the RTMessage class. It stores the following information:
signal
). This is a numerical id that uniquely identifies the event for which the message was created within its protocol. void*
) but can safely be casted to a pointer typed by the parameter of the protocol event. In most cases such casts happen automatically in generated code, so that you can access correctly typed data in a transition function. However, if you get the data from the message object by calling RTMessage::getData()
you need to cast it yourself. See Message Data Area for more information about how the message data is stored.RTMessage::getType()
.RTMessage::getPriority()
.RTMessage::sap()
.RTMessage::receiver()
.As mentioned above you should treat a message object, and all data it contains, as read-only and owned by the TargetRTS. The data that is passed with the message object is either a copy of the data provided by the sender or was moved from it. This avoids the risk that both the sender and receiver, which may run in different threads, access the same data object simulatenously.
Important
When you develop an application don't make assumptions about how many times the data object will be copied. In many cases it will only be copied once, but there are situations when multiple copies will need to be created. It is therefore important that any event parameter has a type descriptor where the copy function (e.g. a copy constructor) is able to copy the data object multiple times. Note that copying of the message object may happen even after it has been dispatched. The receiver must therefore not change it in a way that will prevent it from later being copied correctly by the TargetRTS.
"},{"location":"target-rts/message-communication/#message-data-area","title":"Message Data Area","text":"Messages that carry data can either store that data inside the RTMessage object, in the _data_area
member variable, or on the system heap. A TargetRTS configuration macro RTMESSAGE_PAYLOAD_SIZE
defines the byte limit which decides where a data object will be stored. Data that is smaller than this limit is stored inside the RTMessage object, while bigger data is stored on the system heap. The picture below shows two message objects, one where the data object is small enough to fit in the _data_area
and another where it's too big to fit there and therefore is instead allocated on the system heap.
As a user you don't need to think about where the message data is placed, because it's still accessed in the same way. However, to obtain optimal performance for the application it's necessary to fine-tune RTMESSAGE_PAYLOAD_SIZE
to find the best trade-off between application memory usage and speed.
When a capsule instance wants to send a message, its controller first needs to get a new RTMessage object. The controller gets it by calling RTController::newMsg()
. The obtained message object will then be given to the controller which manages the receiver capsule instance (which in case of intra-thread communication will be the same controller). That controller inserts the message object into an event queue according to the message priority. When the message has been dispatched to the receiver, the message object is no longer needed and the controller then frees it by calling RTController::freeMsg()
.
Dynamically allocating and deallocating memory for each individual message object would impact negatively on application performance. The newMsg()
and freeMsg()
functions therefore use a free list which is a pool of message objects that are \"free\" to use (meaning that they are currently not involved in any communication). newMsg()
obtains the first message of the free list, and freeMsg()
returns that message to the beginning of the free list.
There is a single free list in the application and it's implemented by the RTResourceMgr class. At application start-up, when the first message of the application needs to be sent, a block of messages (see RTMessageBlock) is allocated from the heap and added to the free list. Subsequent requests to get a message object can therefore be processed quickly, without the need to allocate memory.
If the application needs to send a large number of messages at a faster pace than they can be dispatched, it can happen that the free list becomes empty. In that case another block of messages get allocated.
The size and behavior of the free list is controlled by a few constants in the TargetRTS implementation:
RTController::freeMsg()
. If the size of the free list exceeds maxFreeListSize a number of messages are freed to reduce the size of the free list to minFreeListSize. Hence, the size of the free list is always kept in the range defined by these two constants.Note that freeing a message doesn't deallocate its memory. Instead it is reset by calling RTMessage::clear()
so it becomes ready to be used again. This means that the memory occupied by the free list will grow until a certain limit when it's big enough to always contain a free message when the application needs one. That limit is different for different applications, and if you want to avoid dynamic allocation of additional message blocks after application start-up, you may need to adjust the RTMessageBlock::Size
constant.
Hint
You can compile the TargetRTS with the RTS_COUNT
flag set to collect run-time statistics for your application. Among other things it counts the peek number of messages allocated by each controller. This information can help you configure the free list to have a size that is appropriate for your application.
Example
You can find a sample application that shows how to collect and print statistics here
"},{"location":"target-rts/threads/","title":"Threads","text":"An Art application consists of capsule instances that each manage a state machine and communicates with other capsule instances by sending and receiving events. Conceptually we can think about each capsule instance as run by its own thread.
However, in practise it's often necessary to let each thread run more than one capsule instance. The number of capsule instances in an application can be higher than the maximum number of threads the operating system allows per process. And even if that is not the case, having too many threads can consume too much memory and lead to unwanted overhead.
When creating a new Art application it's recommended to start with a minimal number of threads, perhaps only the main thread initially. During the design work you will then add new threads when you identify capsules that need to perform long-running tasks. Such a capsule should not run in the main thread since during the long-running task all other capsules run by that thread will be unresponsive (i.e. cannot respond to incoming events).
Another input to which threads to use is how capsule instances communicate with each other. Those capsule instances that communicate frequently with each other benefit from being run by the same thread since sending an event within the same thread is faster than sending it across threads.
Example
You can find a sample application that uses threads here.
"},{"location":"target-rts/threads/#physical-and-logical-threads","title":"Physical and Logical Threads","text":"Code RealTime makes a difference between physical and logical threads. Physical threads are the real threads that exist in the application at run-time. A logical thread is a conceptual thread which application code uses when it needs to refer to a thread. Hence, it is an indirection which prevents hard-coding the application against certain physical threads.
Logical and physical threads are defined in the transformation configuration (TC) using the threads property. Each logical thread is mapped to a physical thread. Having all information about threads in the TC has several benefits:
To ensure that each logical thread is mapped to a physical thread, the logical threads are defined implicitly when they are mapped to a physical thread. Here is an example where there are two physical threads MainThread
and PT1
, and three logical threads L1
, L2
and L3
. The logical threads L1
and L2
are both mapped to the MainThread
while L3
is mapped to PT1
.
tc.threads = [\n{\n name: 'MainThread',\n implClass: 'RTPeerController',\n stackSize: '20000',\n priority: 'DEFAULT_MAIN_PRIORITY',\n logical: [\n 'L1', 'L2'\n ]\n},\n{\n name: 'PT1',\n implClass: 'RTPeerController',\n stackSize: '20000',\n priority: 'DEFAULT_MAIN_PRIORITY',\n logical: [\n 'L3'\n ]\n}\n];\n
Take care to map a logical thread to exactly one physical thread.
"},{"location":"target-rts/threads/#library-threads","title":"Library Threads","text":"Physical threads can only be defined in executable TCs. A library TC can, however, define logical threads. An executable TC that has such a library TC as its prerequisite must map those logical threads to physical threads. Here is an example of a library TC that defines a logical thread. Note that in this case the threads
property contains a list of strings rather than a list of objects as is the case for an executable TC.
tc.threads = [ 'LibraryThread' ];\n
If you anyway define physical threads for a library TC they will be ignored by the C++ code generator, and only the logical threads will be considered.
"},{"location":"target-rts/threads/#running-a-capsule-instance-in-a-custom-thread","title":"Running a Capsule Instance in a Custom Thread","text":"Capsule instances are connected in a tree structure where the top capsule instance is the root. A capsule instance always lives inside a part of another (container) capsule. The top capsule instance is always run by the main thread, but for all other capsule instances you can choose which thread that should run it.
When a new capsule instance is created it will by default be run by the same thread that runs the container capsule instance. This means that by default all capsule instances in the application will be run by the main thread.
The picture below outlines the capsule instances of an Art application. C1
is the top capsule. For simplicity we have assumed that all capsule parts are fixed with multiplicity 1 so they only can contain one capsule instance.
The capsule instances contained in cp1
and fp1
are run by the logical thread Logical1
while the capsule instances contained in dp1
, cp2
and ep2
are run by the logical thread Logical2
. Other capsule instances are run by the main thread. Note that to accomplish that we need to explicitly reference the MainThread
when incarnating ep1
since by default it would be run by the thread that runs its container capsule, i.e. Logical2
. In fact we need to explicitly mention a logical thread for all capsule instances in this example except ep2
since it runs in the same logical thread as its container capsule instance cp2
.
If you don't want a capsule instance to be run by the same thread that runs its container capsule you can specify another thread when creating the capsule instance. When incarnating a capsule instance into an optional part this can be done in a call to incarnate()
on a Frame port. Here is an example:
frame.incarnate(myPart, nullptr /* data */, nullptr /* type */, LogicalThread, -1);\n
Here LogicalThread
refers to a logical thread that must exist in the TC. The physical thread to which it is mapped will run the created capsule instance.
If the part is fixed you need to use a capsule factory for specifying the thread that should run a capsule instance that is incarnated into the part. For example:
fixed part server : Server [[rt::create]]\n`\n return new Server(LogicalThread, rtg_ref);\n`;\n
"},{"location":"target-rts/threads/#targetrts-implementation","title":"TargetRTS Implementation","text":"The implClass
property of a physical thread that is defined in a TC refers to the class in the TargetRTS that implements the thread. This class must inherit from RTController. A default implementation is provided by the RTPeerController. It implements a simple event loop that in each iteration delivers the most prioritized message to the capsule instance that should handle it.
You can implement your own controller class by creating another subclass of RTController. As an example, look at RTCustomController.
See Message Communication for more details about how controllers work.
If the application uses timers it needs a timer thread for implementing the timeouts. The TargetRTS provides a default implementation RTTimerController which implements basic support for processing timeout events and timer cancellation.
"},{"location":"target-rts/threads/#default-threads-and-thread-properties","title":"Default Threads and Thread Properties","text":"If no threads are specified in the TC the application will use two threads; one main thread that runs all capsule instances and one timer thread that implements support for timers as explained in the documentation of the threads property. If your application is single-threaded and doesn't use timers, it's unnecessary to have a timer thread and you can then remove it by only defining the MainThread in the threads property:
tc.threads = [\n{\n name: 'MainThread',\n implClass: 'RTPeerController',\n stackSize: '20000',\n priority: 'DEFAULT_MAIN_PRIORITY'\n}\n];\n
A thread object defines a physical thread by means of the following properties:
name The name of the thread. It's recommended to choose a name that describes what the thread is doing. Many C++ debuggers can show the thread name while debugging, and you can also access it programmatically by calling the RTController::name() function. Note that names of physical threads in the application must be unique.
implClass This is the name of the TargetRTS class that implements the thread. See TargetRTS Implementation. If omitted it will default to RTPeerController
.
stackSize The thread stack size in bytes. This value is interpreted by the target environment, and some operating systems may have special values (such as 0) that can be used to avoid hard-coding a certain stack size. If omitted it will default to 20000
.
priority The thread priority. By default it's DEFAULT_MAIN_PRIORITY
(or DEFAULT_TIMER_PRIORITY
for a timer thread). These are macros with values that are interpreted by the target environment.
logical A list of names of logical threads that are mapped to the physical thread. Logical threads must have unique names and each logical thread must only be mapped to one physical thread. Except for the main thread and timer threads this property should not be empty, since it's through the logical threads that the application code can use the physical thread.
Thread information specified in the TC is generated into the unit files (by default called UnitName.h
and UnitName.cpp
). You will find there functions _rtg_createThreads()
and rtg_deleteThreads()
which contain the code for creating and deleting the physical threads that you have added in addition to the default MainThread and TimerThread. There is also a function _rtg_mapLogicalThreads()
where the logical threads are mapped to physical threads.
Some target environments only support one thread. In this case the macro USE_THREADS
will be unset when compiling generated C++ code and the TargetRTS, and it will remove all code related to threads.
Capsules can use timers to get notified when some time has passed. A timer is implemented by means of a port typed by the predefined Timing
protocol.
Note
A timer port should always be a non-service behavior port. This is checked by the validation rule ART_0035.
Example
You can find sample applications that use timers here:
When you set a timer you specify the time when it should timeout. At that time the capsule will receive the event timeout
on the timer port, and it can trigger a transition in the capsule state machine that handles the timeout.
There are three ways to set a timer:
informAt
you set a one-shot timer that will timeout once, at a specific point in time (absolute time).informIn
you set a one-shot timer that will timeout once, when a certain time has passed (relative time). informEvery
you set a periodic timer that will timeout repeatedly at certain intervals (relative time).The same timer port can be set in any of these ways, and you can \"reuse\" the timer by setting it again when it has timed out. If you set the same timer multiple times, before it has timed out, you will get multiple timeout events (one for each time the timer was set).
Note
While it's possible to implement a periodic timer by re-setting a one-shot timer each time it times out, it's not recommended to do so. You will get a higher precision by using a proper periodic timer. This is because it takes some time to set the timer which may add to some drift in the timeouts.
The time specified for a one-shot timer, or interval for a periodic timer, can be specified in three ways:
std::chrono::duration
(for relative time) or std::chrono::time_point
(for absolute time). You need to include the <chrono>
header file and use a C++ 11 compiler.<chrono>
header file and use a C++ 14 compiler.All functions that set a timer return an RTTimerNode*
, and in case the timer could not be set nullptr
is returned. It's good practise to always check this return value, to ensure the timer was successfully set. If you later need to operate on the timer (for example to cancel it) you should construct an RTTimerId
object from the RTTimerNode*
. You can then call isValid()
on that object to make sure the timer was successfully set.
The example below shows some different ways to set timers and to handle the timeouts:
capsule Timers {\n behavior port timer1 : Timing, timer2 : Timing, timer3 : Timing; \n\n statemachine {\n state S {\n timeout : on timer1.timeout, timer2.timeout, timer3.timeout\n `\n // TODO: Handle timeouts here\n `;\n };\n\n initial -> S\n `\n RTTimerId tid1 = timer1.informIn(RTTimespec(2, 0)); // one-shot timer to time out in 2 s\n if (!tid1.isValid()) {\n // timer1 could not be set\n }\n\n std::chrono::system_clock::time_point t = std::chrono::system_clock::now() + std::chrono::milliseconds(50);\n RTTimerNode* t2 = timer2.informAt(t); // one-shot timer to timeout in 50 ms from now\n if (!t2) {\n // timer2 could not be set\n }\n\n RTTimerId tid3 = timer3.informEvery(800ms); // periodic timer to timeout every 800 ms\n if (!tid3.isValid()) {\n // timer3 could not be set\n }\n `;\n }\n}\n
If you set a timer with an absolute time that has already passed, or a relative time of 0, the timeout will happen almost immediately. Note the word \"almost\", because in practise it always takes a little time for the timeout event to be placed in the controller's event queue, and from there be dispatched to the capsule.
"},{"location":"target-rts/timers/#timer-priority","title":"Timer Priority","text":"If you want a timeout event to be processed as quickly as possible you can use a higher than default priority when setting the timer. The last parameter of informIn()
, informAt()
and informEvery()
specifies the priority of the timeout event (by default it's General
which is the normal priority of an event). In the same way you can lower the priority, if you want the timeout event to be handled at a lower priority.
The timer set in the example below will timeout immediately and the timeout event will be processed with a higher than normal priority.
timer.informIn(0s, High); \n
"},{"location":"target-rts/timers/#cancel-a-timer","title":"Cancel a Timer","text":"To cancel a timer you need the RTTimerId
object that you constructed when the timer was set. Call cancelTimer()
on the timer port, with the RTTimerId
as argument, to cancel the timer. Here is an example where a timer is set, and then immediately cancelled.
RTTimerId tid = timer.informIn(RTTimespec(10, 0)); // 10 s\nif (!tid.isValid()) {\n // error when setting timer\n}\nelse {\n timer.cancelTimer(tid); \n // now tid.isValid() will return false\n}\n
If you need to cancel a timer from a different code snippet from where it was set, you need to store the RTTimerId
object in a member variable of the capsule.
Important
When you create an RTTimerId
object from an RTTimerNode*
, the RTTimerNode
will store a pointer to internal data of the RTTimerId
object. The TargetRTS keeps track of RTTimerNode
s for active timer requests, and may access that internal data of your RTTimerId
object. It's therefore important to make sure that the address of an RTTimerId
object doesn't change, as that would make the RTTimerNode
reference invalid memory. For example, you should not insert RTTimerId
objects into an std::vector
as that could change their addresses, and hence invalidate such pointers, when the vector is modified. Either use a collection that doesn't do this (e.g. std::list
) or store pointers to RTTimerId
objects in the collection instead of the objects themselves.
Cancelling a timer guarantees that its timeout event will not be received by the capsule. This is true even if, at the time of cancellation, the timeout period has already lapsed, and the timeout event is waiting in the controller's event queue to be dispatched to the capsule. In this case cancelling the timer will remove the timeout event from the queue so that it doesn't get dispatched to the capsule.
However, when the timeout event already has been dispatched, it's too late to cancel the timer. If you still do it you will receive an error. In the same way, it's an error to cancel the same timer more than once. You can call RTTimerId.isValid()
to check if the RTTimerId
is still valid (meaning that its timeout event has not been dispatched) before cancelling the timer.
Just like other events, the timeout event that is sent when a timer has timed out, can have data. At most one data object can be passed, and if you need more you can use a struct or class as data type.
Contrary to data of user-defined events, timer data is untyped (void*
). You therefore need to provide the type descriptor of the data as an extra argument when setting the timer. The TargetRTS will copy the provided data into the timeout event, so the type descriptor must provide a copy function.
Here are examples of setting timers with timer data:
// Pass a boolean as timer data\nbool b = true;\ntimer1.informIn(RTTimespec(5, 0), &b, &RTType_bool);\n\n// Pass the current time as timer data\nRTTimespec now;\nRTTimespec::getclock(now);\ntimer2.informIn(1s, &now, &RTTimespec::classData);\n
Note
Since timer data is untyped, any timeout event can carry any kind of data. While this is flexible, it requires caution since in the timeout transition you need to explicitly cast rtdata
from void*
to a pointer to the timer data. You must therefore be sure what type of data each timeout event carries. It's recommended to not use different types of data for the same timer.
Here is an example of how to access the data of timer2 from the above example:
timeout: on timer2.timeout\n`\n const RTTimespec then = *(static_cast<const RTTimespec*>(rtdata));\n`;\n
The data pointed at by rtdata
for a timeout event is owned by the TargetRTS. It is allocated to a copy of the data that is provided when setting the timer, and deallocated if the timer is cancelled. For a one-shot timer it's also deallocated after the timeout event has been dispatched and handled by the capsule, while for a periodic timer the same data object will be used for each timeout event that is produced.
Sometimes you may need to adjust the clock of your realtime application. For example, distributed applications that run on different machines in a network may use the Network Time Protocol (NTP) to synchronize the system time over the network. While any timer port can be used for adjusting the system time, it's recommended to only do it through one specific timer port on one specific capsule within the application.
Adjusting the clock is a three-step process:
adjustTimeBegin()
on the timer port. This suspends the timing service so no timeouts can happen, and no new timers can be set.adjustTimeEnd()
on the timer port, and provide the time adjustment as argument. The TargetRTS will recompute new timeout time points for all active timers that have been set with a relative time, for example periodic timers. After that the timing service is resumed.Note that adjustTimeEnd()
takes a relative time as argument (positive to move the clock forwards, and negative to move it backwards). However, operating system functions for setting the system clock usually take an absolute time. Here is an example of a capsule member function for setting the system clock to a new absolute time. Replace sys_setclock()
with the actual function for setting the system clock in your operating system.
void AdjustTimeCapsule_Actor::setClock(const RTTimespec& new_time)\n{\n RTTimespec old_time, delta;\n\n timer.adjustTimeBegin();\n\n RTTimespec::getclock(old_time); // Read system clock\n if (sys_setclock(new_time)) { // Set system clock with OS function\n delta = new_time;\n delta -= old_time;\n }\n\n timer.adjustTimeEnd(delta);\n}\n
Error handling is important in this function; if the function for setting the system clock fails (for example because the application doesn't have enough privileges to change the clock), it must call adjustTimeEnd()
with a zero time argument, to restart the timing service without changing the clock. adjustTimeEnd()
works by simply adding a time offset to account for the changed system time, and if the system time was not modified that offset must be zero.
Example
You can find a sample application that changes the system clock here. The sample is for Windows but can easily be modified for other operating systems.
"},{"location":"target-rts/versions/","title":"Versions","text":"Note
Some of the information in this chapter is only applicable for the Commercial Edition of Code RealTime since it includes the source code for the TargetRTS. With the Community Edition comes only precompiled versions of the TargetRTS for a limited number of commonly used target configurations.
It's common to extend and modify the TargetRTS with your own utilities and customizations. In this case you will build your application against a copy of the TargetRTS that contains these changes. However, when a new version of the TargetRTS is released, you then must incorporate the changes in that new version into your own copy of the TargetRTS. This document helps with this process by documenting all changes made in the TargetRTS. It also describes some strategies for more easily managing multiple versions of the TargetRTS.
Note
The version of the TargetRTS is defined in the file RTVersion.h
by means of the macro RT_VERSION_NUMBER
.
To simplify the process of adopting changes from a new version of the TargetRTS, so called patch files are provided in the folder TargetRTS_changelog
(located next to the TargetRTS
folder). The patch files have names <from-version>_<to-version>.patch
and contain all changes made from one version to another. You can use the command-line tool patch
to automatically apply the changes of a patch file to your own copy of the TargetRTS.
For example, to apply the changes from version 8000 to version 8002, go to the folder that contains the TargetRTS
and TargetRTS_changelog
folders and run this command:
patch -p3 < TargetRTS_changelog/8000_8002.patch\n
You can also downgrade the version of the TargetRTS by running the same command but with the -R
flag.
Note
The patch
command is included in Linux and Unix-like operating systems, but on Windows you have to download it separately. You can for example get it through the Git for Windows set of tools.
The patch files in the TargetRTS_changelog
folder have been created by a Bash script TargetRTS/tools/createPatch.sh
. You can use this script if you have your version of the TargetRTS in a Git repo and want to produce a patch file for the changes you have made to the TargetRTS. You can then later use that patch file to apply your changes to a newer version of the TargetRTS.
Whether it's best to adopt changes in a standard TargetRTS into your version of the TargetRTS, or to do the opposite, i.e. adopt your changes into a standard TargetRTS, may depend on how big changes you have made. If your changes are small and limited the latter may be easiest, while if you have made substantial changes the former may be the better option.
"},{"location":"target-rts/versions/#change-log","title":"Change Log","text":"Below is a table that lists all changes made in the TargetRTS since version 8000 (which were delivered with Code RealTime 1.0.0). For changes in older versions of the TargetRTS, which were done for Model RealTime, see this document.
TargetRTS Version Included Changes 8001 JSON Decoding 8002 Building without rtperl JSON parser Script for creating TargetRTS patch files Pointers in JSON encoding/decoding 8003 Align terminology in comments Configurable max TCP Connections 8004 Improved implementation of JSON parser JSON encoding/decoding for RTByteBlock New target configuration for MacOS on AArch64"},{"location":"target-rts/versions/#json-decoder","title":"JSON decoder","text":"A new decoder class RTJsonDecoding
is now available for decoding messages and data from JSON. JSON produced from data by the JSON Encoder (RTJsonEncoding
) can be decoded back to (a copy of) the original data.
New macros were added in makefiles to support building generated applications without using rtperl
.
A new class RTJsonParser
can be used for parsing arbitrary JSON strings. It has a more general use than RTJsonDecoding
which is specifically for decoding JSON that has been produced by RTJsonEncoding
. See this chapter for more information.
A Bash script createPatch.sh
is now available in the tools
folder of the TargetRTS. It can be used for producing patch files describing the differences between two versions of the TargetRTS. See Patch Files for more information.
Data of pointer type is now encoded to a string by the JSON encoder (RTJsonEncoding
) and can be decoded back to a memory address by the JSON decoder (RTJsonDecoding
).
Several comments were updated to align the terminology used in Code and Model RealTime. This was done so that the Doxygen documentation that is generated from the TargetRTS header files will be easy to understand for users of both products.
"},{"location":"target-rts/versions/#configurable-max-tcp-connections","title":"Configurable max TCP connections","text":"The RTTcpSocket
class has a new function setMaxPendingConnections()
which can be used for setting the maximum number of clients that can connect to the TCP socket. Previously this limit was always 5, and this is still the default in case you don't call this function to change it.
The RTJsonParser
now has an improved recursive implementation that uses a map instead of a vector for storing keys and values in the RTJsonResult
object. The new implementation provides new functions RTJsonResult::keys_begin()
and RTJsonResult::keys_end()
which allows to iterate over the keys in the parse result without knowing their names.
The RTJsonEncoding
and RTJsonDecoding
now support JSON encoding/decoding for objects of the RTByteBlock
class.
A new target configuration for the Clang 15 compiler for MacOs with ARM processor is now available. It has the name MacT.AArch64-Clang-15.x
.
An Art file is primarily edited using its textual syntax in the text editor. However, many parts of the Art language also has a graphical syntax which can be shown (and to some extent also edited) from graphical diagrams. Visual Studio Code and Eclipse Theia also provide a few views that provide value for Art, such as the Outline view and the References view. These views do not support any editing, but provide useful overview and navigation possibilities.
"},{"location":"working-with-art/art-editor/","title":"Text Editor","text":"You can use any text editor for editing Art files, but it's highly recommended to edit them in Code RealTime. Thereby you will have access to features such as syntax coloring, content assist and semantic validation.
"},{"location":"working-with-art/art-editor/#syntax-coloring","title":"Syntax Coloring","text":"Code RealTime provides color themes that have been specifically designed for being used for editing Art files. Activate one of these color themes from File - Preferences - Color Theme.
This feature, which also is known as IntelliSense or Code Completion, helps you when editing an Art file by proposing commonly used Art constructs that are valid at the current cursor position. Invoke Content Assist by pressing Ctrl+Space. Depending on where the cursor is placed you will get different proposals to choose from. There are four kinds of proposals as shown in the picture below:
State
variable for the capsule code template which occurs both in the state definition and as a state reference in the initial transition. All occurrances of a variable are updated simultaneously when you replace the variable with a string.Note that code templates are also available in some C++ code snippets (e.g. rt::decl
and rt::impl
) and can help you insert pieces of C++ code that are commonly used in Art applications.
name
item in the proposals list tells you that you can use an arbitrary identifier as the name of an Art element at that position. For example, in the proposals list shown in the picture above name
appears since a triggered transition may have an optional name before its declaration. The code template for the triggered transition will not insert a name, since many transitions don't have names, but you can manually add it afterwards:MyTransition: State -> X on timer.timeout\n
:
or .
where applicable. For example, after you have typed the name for the triggered transition shown above you can use Content Assist to learn that it may be followed by either a ->
or :
token:If you use Content Assist within a C++ code snippet, Code RealTime will delegate the request to the C++ language server extension that is installed. It works by computing valid completions from the cursor position based on the generated C++ file that contains the code from the code snippet.
Note that the \"Microsoft C++\" and \"clangd\" language servers work slightly differently in this regard. It can happen that you in some cases need to invoke Content Assist twice, before the correct results appear. This has to do with how the language servers keep cached information from C++ files, and will hopefully improve in future versions.
Hint
The \"clangd\" language server supports an argument --completion-parse=always
which you can add in its settings. It's recommended to set this argument, since it will force the generated C++ file to be parsed each time Content Assist is invoked, without relying on cached information.
To rename an Art element place the cursor on the element's name and press F2 (or invoke the command Rename Symbol from the context menu). This performs a \"rename refactoring\" that updates all references to the renamed element too.
Note
Avoid renaming an element by simply editing its name. For Code RealTime to understand that you want to rename an element, rather than replacing it with another element, you need to use the approach described above.
"},{"location":"working-with-art/diagrams/","title":"Diagrams","text":"Art is a textual language but there is also a graphical notation for many parts of the language. You can therefore visualize (and in many cases also edit) some of the Art elements using graphical diagrams. The following diagrams can be used:
The picture below shows an example of what these diagrams may look like:
"},{"location":"working-with-art/diagrams/#opening-diagrams","title":"Opening Diagrams","text":"To open a diagram from an Art file place the cursor inside an Art element. Bring up the context menu and invoke a command for opening a diagram for the Art element: Open State Diagram, Open Structure Diagram or Open Class Diagram. Note that all these three commands are always available, but if the selected Art element cannot be shown in the selected kind of diagram, you will get an error and no diagram will open.
If the cursor is placed on an Art element that has a graphic representation in the form of a symbol or line in the diagram, for example a state in a state diagram, the symbol or line will be highlighted in the opened diagram by selecting it. You can use this feature as a way to navigate from an element in an Art file to the corresponding symbol or line in a diagram. If the diagram is already open, it will be made visible and the selection will be updated.
You can also open diagrams from the context menu of an Art file in the Explorer view. In this case the Art file will be searched for an element that can be shown in the selected kind of diagram. If more than one such Art element is found, you will be prompted to pick the one to show in the diagram. For example:
The same prompting happens if you open a diagram from an Art file when the cursor position doesn't indicate which Art element to open the diagram for. All valid Art elements in the file will be listed and you can choose which one to open the diagram for.
You can open multiple diagrams of the same kind in one go by selecting multiple Art files in the Explorer view, and then invoke a command for opening diagrams from the context menu. However, in this case only diagrams for the first element found in each file will be opened (i.e. in this case you will not be prompted in case a file contains multiple elements for which the selected kind of diagram could be opened).
"},{"location":"working-with-art/diagrams/#related-diagrams","title":"Related Diagrams","text":"If you already have a diagram open, you can open another diagram that is related to that diagram. And if a symbol or line is selected on the diagram, diagrams related to the selected symbol or line can be opened. Press Ctrl+Space to open the diagram's pop-up menu. If the diagram, or the selected symbol or line, has any related diagrams you may see the following commands:
For a capsule that inherits from another capsule you can open the state diagram of the inherited base capsule by means of the command Open Inherited State Diagram. If this command is performed on an element that is inherited, redefined or excluded in the state diagram, then the corresponding element in the base capsule will be highlighted. This command is therefore useful for navigating in an inherited state machine.
"},{"location":"working-with-art/diagrams/#navigating-from-diagram-to-art-file","title":"Navigating from Diagram to Art File","text":"If you double-click a symbol or a line in a diagram, the Art element that corresponds to that symbol or line will be highlighted in the Art file. Note that you need to double-click on the symbol or line itself, and not on a text label shown in the symbol or on the line. However, as an alternative you can instead hold down the Ctrl key and then click on the text label. It will then become a hyperlink that navigates to the Art element that corresponds to that text label. You need to use this approach in case a symbol has multiple text labels each of which represent different Art elements. For example:
In state diagrams you can also double-click on icons that are shown for transitions that contain effect and/or guard code. The presence of effect code is indicated by a blue icon, and guard code with a yellow icon.
Double-clicking these icons will highlight the code snippets in the Art file.
"},{"location":"working-with-art/diagrams/#working-with-diagrams","title":"Working with Diagrams","text":""},{"location":"working-with-art/diagrams/#zooming-and-panning","title":"Zooming and Panning","text":"When a diagram is opened it is initially centered and with medium zoom level which makes all text labels big enough for reading. However, if the diagram is big then all contents may not be visible unless you zoom out. You can zoom the diagram using either the mouse scroll wheel or by means of the two-finger zoom gesture on a touch pad. You can also zoom using the buttons in the Properties view toolbar. There you will also find a Center button which will restore the diagram to its original zoom level.
Alternatively you can use the command Fit to Screen which will set the zoom level so that the entire diagram fits the size of the diagram editor. Note that this command must be invoked from the general Command Palette or by means of the keyboard shortcut Ctrl+Shift+F.
It's also possible to work with a big diagram without zooming, but instead panning the viewport so that a different part of the diagram becomes visible. To pan the viewport click anywhere on the diagram and drag while holding down the mouse button. Note that there are no limits to panning which means you can move the viewport as far away from the center of the diagram as you like. Use the Center or Fit to Screen command for panning back the viewport to its original position. Note that if a symbol or line is selected, the Center command will move the viewport so that the selected symbol or line appears in the middle.
"},{"location":"working-with-art/diagrams/#collapsing-and-expanding-symbols","title":"Collapsing and Expanding Symbols","text":"State and structure diagrams can be hierarchical. A state diagram is hierarchical if it contains a composite state with a nested state machine. A structure diagram is hierarchical if it contains a part typed by another capsule with nested parts or ports. By default symbols that contain nested symbols are collapsed to minimize the size of the diagram:
To expand a collapsed symbol click the yellow button. The symbol will then be resized to show the nested symbols. Click the button again to collapse the symbol and hide the nested symbols. You can use the Expand All and Collapse All buttons in the Properties view toolbar to expand or collapse all symbols so that the full hierarchical diagram becomes visible or hidden.
Information about which symbols that are currently expanded will be remembered if you save the diagram. This information is stored in the file .vscode/art_diagram_settings.json
.
Many diagram commands mentioned above can be invoked using the keyboard. Press Ctrl+Space in a diagram to open a pop-up menu from where you can invoke a diagram command.
In this pop-up menu you also find convenient commands for navigating to related diagrams. For example, from the state diagram of a capsule you can navigate to the structure and class diagrams of that same capsule.
"},{"location":"working-with-art/diagrams/#diagram-appearance","title":"Diagram Appearance","text":"Certain properties on Art elements control how they will appear in a diagram. Currently it's possible to configure which color to use for most elements in diagrams. See the color property for more information.
"},{"location":"working-with-art/diagrams/#diagram-filters","title":"Diagram Filters","text":"To avoid cluttered diagrams with too many text labels, certain information is by default hidden. If you click in the background of the diagram, the Properties view will show various filters that you can turn on or off for showing or hiding such additional information. Here is an example of the filters available for a state diagram:
Information about applied filters will be remembered if you save the diagram. This information is stored in the file .vscode/art_diagram_settings.json
.
Diagram filter properties that have been modified are shown in boldface, and a \"Restore default\" button appears for them. You can click this button to restore the filter property to its default value.
You can also set diagram filters globally using diagram settings. Such filters will apply to all diagrams unless a more specific filter has been set on an individual diagram. You can find these settings by filtering on code-rt.diagram
in the Settings editor:
Note that some diagram filters can only be set globally, and not for individual diagrams.
"},{"location":"working-with-art/diagrams/#elements-in-the-properties-view","title":"Elements in the Properties View","text":"The Properties view can show additional Art elements when you select a symbol or a line. For example, it shows internal transitions of a state.
Showing such elements in the diagram itself would risk making it cluttered, especially when there is a large number of elements.
You can double-click the Art elements in the Properties view to highlight them in the Art file. For internal transitions the same blue and yellow dots are shown as for regular transitions in diagrams. Double-click the blue dot to navigate to the transition effect code and the yellow dot for navigating to the transition guard code.
"},{"location":"working-with-art/diagrams/#renaming-elements","title":"Renaming Elements","text":"You can rename an Art element shown in a diagram by double-clicking on the text label that shows its name. Alternatively select the symbol or line to which the text label belongs and press F2.
Note that this is a \"rename refactoring\" and all references to the renamed element will be updated too.
"},{"location":"working-with-art/diagrams/#creating-and-editing-elements","title":"Creating and Editing Elements","text":"Note
Creating and editing elements is supported in state and structure diagrams but not in class diagrams.
To create a new element in a diagram use one of the New ... commands in the pop-up menu that appears when you press Ctrl+Space. These commands are the same as appear when you use Content Assist in the Art text editor. Which commands that are available depends on what is currently selected in the diagram. If an element is selected in the diagram, a new element will be created inside that element. Otherwise the new element will be created as a top-level element (possible in state diagrams but not in structure diagrams).
To edit an existing element, select it in the diagram and use the Properties view for editing it. There are certain properties that are common for many elements, such as the color property, but most properties are specific for the element that is selected.
Elements are created and edited by updating the Art file, which in turn will update the diagram. Just like when you use Content Assist in the Art text editor a created element will initially get default values for its properties, for example the name. The default value becomes selected so you can directly type to replace it with something else.
You can of course undo a change by pressing Ctrl+z (Undo) in the Art text editor.
"},{"location":"working-with-art/diagrams/#state-diagram-editing","title":"State Diagram Editing","text":"In a state diagram where nothing is selected, the New ... commands will create new top-level elements directly in the state machine.
If a state is selected you can create the following elements inside it (turning the state into a composite state, if it was not already composite).
To create a transition in a state diagram you first need to select the source state (or pseudo-state) and then the target state (or pseudo-state). Then press Ctrl+Space and perform either New Triggered Transition or New Non-Triggered Transition (depending on if the transition needs any triggers or not).
You can redirect a transition, i.e. to change either its source (state or pseudo-state) or its target (state or pseudo-state). You can do it from a state diagram by selecting both the transition and the new source or target. Then press Ctrl+Space and perform either Set Transition Source or Set Transition Target. This will redirect the transition by changing its source or target. If you want to change both the source and target just repeat the procedure once more.
For example, in the diagram below we have selected the transition between states Ready and Heating and also the CoolOffState. We then select Set Transition Source in the menu. This will redirect the transition to instead go from state CoolOffState to state Heating.
"},{"location":"working-with-art/diagrams/#structure-diagram-editing","title":"Structure Diagram Editing","text":"In a structure diagram it's not possible to create anything unless something is selected in the diagram. This is because a structure diagram always has a single capsule as its top-level element. If the capsule is selected you can create parts and ports in it.
If a part is selected you can create a port in the capsule that types the part. In the example below a port will be created in capsule BB.
Both parts and ports have several properties that can be edited using the Properties view.
"},{"location":"working-with-art/diagrams/#deleting-elements","title":"Deleting Elements","text":"Note
Deleting elements is supported in state and structure diagrams but not in class diagrams.
You can delete an Art element shown in a diagram by selecting the symbol or line that represents the element and then press the Delete key. Alternatively use the command Delete in the Ctrl+Space pop-up menu. Multiple symbols or lines can be selected in order to delete many Art elements in one go.
Note that elements are deleted by removing them from the Art file, which in turn will update the diagram. All content within the deleted element will be lost, including any comments. However, you can of course undo the deletion by pressing Ctrl+z (Undo) in the Art text editor.
"},{"location":"working-with-art/outline-view/","title":"Outline View","text":"The Outline view shows information about the Art elements that are defined in an Art file. You can see the most important information for each element, such as its name and other important properties. You can also see the containment hierarchy, i.e. which elements that contain other elements. Below is an example of what it can look like:
You can use the Outline view for getting an overview of what elements an Art file contains, and for searching and navigating to elements.
"},{"location":"working-with-art/outline-view/#navigating","title":"Navigating","text":"To navigate to an element in the Art file, double-click on the element in the Outline view. The cursor will be placed just before the element's name in the Art file (or where the name would be in case it has no name).
You can also single-click on elements to just make the clicked element visible in the Art editor, without changing the cursor position. In this case the element is marked with a thin rectangle:
If you hold down the Ctrl key when clicking, a new Art editor showing the same Art file will open to the side, in a new editor area to the right. The element will then be made visible and marked in that new editor. This can be useful if you don't want to change the original Art editor, for example when comparing two elements located in the same Art file.
It's also possible to navigate in the other direction, i.e. from the Art editor to the Outline view. To do this, set the Outline view to follow the cursor:
Now the Outline view will automatically highlight the element that corresponds to the cursor position in the Art editor.
"},{"location":"working-with-art/outline-view/#searching","title":"Searching","text":"You can use the Outline view when searching for one or many Art elements, as an alternative to searching textually in the Art editor. Start by selecting the element shown first in the Outline view, and then type quickly the first few characters of the element name. After every keystroke the selection will move downwards to an element with a name that matches the typed characters. If you make a brief pause, you can then start to type again to proceed searching further down in the Outline view.
Another way to search is to press Ctrl+f when the Outline view has focus. A small popup will then appear where you can type a few characters. Nodes in the Outline view with a label that matches the typed characters will be highlighted. The matching allows additional characters between the typed characters which is why the typed string \"init\" also matches the transition Waiting -> Terminated
:
When the search has matched a few elements that look interesting you can press the Filter button next to the text field to filter the Outline view so it only shows the matching elements. This can avoid lots of scrolling if the matching elements are far apart.
"},{"location":"working-with-art/references/","title":"References View","text":"The References view shows how Art elements reference each other. Depending on what kind of reference you are interested in, there are different commands to use.
"},{"location":"working-with-art/references/#referencing-elements","title":"Referencing Elements","text":"To find all elements that reference a certain Art element, right-click on the name of the Art element (it must have a name, otherwise it cannot be referenced) and perform the context menu command Find All References. The References view will in this case list all referencing elements, and group them by the Art file where they are located. For example, it could look like this if the command was invoked on a state.
Double-click the items in the References view to navigate to the referencing element in the Art file. You can remove a referencing element from the view by clicking the Dismiss (x) button. This can for example be useful if you are going through a large list of references and want to remove those you have already examined to make the list more manageable. You can restore all referenced element to be shown again by pressing the Refresh button in the toolbar.
An alternative way of finding and going through all referencing elements is to instead use the context menu command Go to References. This commands works the same as Find All References but will show the referencing elements inline in a popup in the Art editor instead of using the References view.
"},{"location":"working-with-art/references/#type-hierarchy","title":"Type Hierarchy","text":"To find how Art elements relate to each other in terms of inheritance, right click on the name of an Art element that can be inherited (i.e. a class, capsule or protocol) and perform the context menu command Show Type Hierarchy. The References view will show the subtypes or supertypes of the selected Art element. For example:
Use the leftmost toolbar button to toggle between showing subtypes or supertypes. Double-click on items in the tree to navigate to a subtype or supertype.
Note that an alternative to using the References view for looking at type hierarchies is to visualize them graphically using class diagrams (see Diagrams).
"}]} \ No newline at end of file diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 6c5d05f..b71bbaf 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -2,167 +2,167 @@Public Types | |
enum | { Size = 250 + |
enum | { Size = RTMESSAGE_BLOCK_SIZE } |
Severity | +Reason | +Quick Fix | +
---|---|---|
Warning | +A TC has prerequisites specified, but its workspace folder has no active TC. | +Set TC as active | +
Setting a TC as active impacts on how references in an Art file are resolved. If your TC has one or many prerequisites, it's typical that the Art files built by the TC have at least some references to Art elements that are built by those prerequisite TCs. For those references to be possible to resolve, it's required to set the TC as active. This validation rule helps you remember to do so by printing a warning if it finds a TC with prerequisites that is located in a workspace folder without an active TC.
+A Quick Fix is available that will fix the problem by setting the TC as active. If your workspace folder contains more than one TC with prerequisites, the warning will be reported for all of them, and you then need to decide which of the TCs that should be made active.
+Note that the concept of an active TC is not applicable when you build an application with the Art Compiler, because then you always specify explicitly which TC that should be built. Therefore, this validation rule only runs in the IDE, and not in the Art Compiler.
There are certain core rules that run before the semantic validation rules mentioned above. They are responsible for making sure that the Art file is syntactically correct and that all references it contains can be successfully bound to valid Art elements.
Since these rules run before semantic validation rules they cannot be disabled or have their severity changed. Core validation rules have ids in the range starting from 9000 and above and are listed below.