Hello, Real-Time World!¶
This chapter introduces the smallest complete Termina application: a single task that prints a message to the console once every second. Despite its simplicity, the example exercises the essential elements of the language's reactive model, namely an event emitter that produces periodic activations, a task that reacts to those activations, and an access port through which the task reaches the services of the underlying platform. It also illustrates the canonical structure of a Termina project, in which the definitions of the component classes reside in their own modules, while the main application file is reserved for declaring the entities of the system and describing how they are connected.
Creating the project¶
In addition to translating source code, the transpilation tool is used to
create new Termina projects, and it therefore supports several commands. In
particular, the new command generates a new project consisting of an initial
directory tree together with a configuration file populated with default
parameters. Since no target platform is specified, the project adopts the
default posix-gcc platform, which executes Termina applications on
conventional Unix-like systems and is intended for prototyping and validation.
To create a project named hello_world, the following command is executed
from the terminal:
Once the command has completed, a directory named hello_world will have been
created with the following structure:
The file termina.yaml is the project's configuration file, in which the
project name, build options, and target platform are specified. The app
directory contains the main application module, named app.fin by default,
whose role is to declare the entities that constitute the system and to define
the way in which they are connected. The src directory holds the additional
modules that define the component classes and support functions of the
application. Finally, the output directory will store the C code generated by
the transpiler.
Defining the task class¶
The behavior of the application is encapsulated in a task class. In Termina, a task is a reactive component that executes one or more actions in response to the events or messages it receives. A task may hold internal state and exposes ports that connect it to other elements of the system, such as event sinks or access ports to shared resources. Each action runs to completion without preemption, which guarantees deterministic and analyzable behavior.
The class definitions of an application are placed in modules within the src
directory, and never in the main application file. Accordingly, the task class
of this example is defined in a new module located at
src/tasks/hello_world.fin, with the following content:
task class CHelloWorldTask {
timer_port : sink TimeVal triggers timeout;
system_port : access SystemAPI;
action timeout(&priv self, _current_time : TimeVal) -> Status<i32> {
let msg : [char; 32] = "Hello, Real-Time World!";
let ret : Status<i32> = Success;
self->system_port.println(&msg);
return ret;
}
};
The class CHelloWorldTask declares two ports. The first, timer_port, is an
event sink declared as sink TimeVal triggers timeout, which specifies that
the action named timeout is to be executed automatically whenever an event
is received through this port. Each such event carries a value of type
TimeVal, corresponding to the timestamp of the activation, which in this
example is not used and is therefore named _current_time. The second port,
system_port, is an access port to the SystemAPI interface provided by the
platform, through which the task reaches low-level services such as writing to
the console.
The action timeout constitutes the entire behavior of the task. It declares
a fixed-length character array initialized with the message to be displayed and
invokes the println procedure of the system port to print it, passing a
reference to it. The action returns a value
of type Status<i32>, one of the built-in result types of the language, which
indicates whether the action completed successfully or encountered an error. In
this example it always returns Success.
Declaring the application architecture¶
With the task class defined, the system is assembled in the main application
file app/app.fin. As noted above, this file does not contain class
definitions; its purpose is to declare the entities of the application and to
establish the connections between them. The module begins by importing the
module that provides the task class:
The periodic activation of the task is produced by an event emitter. The
following declaration creates an emitter named timer of type
PeriodicTimer, whose period field specifies the interval between
consecutive events by means of its tv_sec and tv_usec components, here set
to one second:
Finally, an instance of the task class is declared and connected to the rest of
the system. The annotation #[priority(10)] assigns the task a fixed
scheduling priority, defined statically at compile time. The connection
timer_port <- timer binds the task's event sink to the periodic emitter, so
that the timeout action is executed on every emitted event, while the
connection system_port <-> system_entry links the access port to the
runtime-provided system_entry resource, granting the task access to the
platform's input and output services:
Enabling the system port¶
The system_entry resource referenced above acts as the gateway between
Termina code and the services of the Operating System Abstraction Layer, and it
is not instantiated by default. Its deployment is controlled by a configuration
parameter in the project's termina.yaml file, which must be enabled by adding
the following line:
When this option is set, the runtime instantiates and exposes the
system_entry resource during system initialization, making it available for
connection to the task's access port.
Building and running the application¶
With the task class defined, the architecture declared, and the system port enabled, the application is ready to be built. From the project's root directory, the build command is executed:
The termina build command invokes the transpiler, which validates the
program, checks type safety, verifies the port connections, and translates the
source modules into C code placed in the output directory. The generated code
is then compiled with the build system provided for the posix-gcc platform,
and the resulting executable is run:
The program prints the message Hello, Real-Time World! to the console once
every second. To terminate its execution, the key combination Ctrl+C is
entered in the terminal.