Actions
This article has examples in the following target languages:
- C
- C++
- Python
- Rust
- TypeScript
Action Declaration​
An action declaration has one of the following forms:
The min_delay
, min_spacing
, and policy
are all optional.
If only one argument is given in parentheses, then it is interpreted as an min_delay
,
if two are given, then they are interpreted as min_delay
and min_spacing
.
The min_delay
and min_spacing
are time values.
The policy
argument is a string that can be one of the following:
"defer"
(the default), "drop"
, or "replace"
. Note that the quotation marks are needed.
If the action is to carry a payload, then a type must be given as well:
Logical Actions​
Timers are useful to trigger reactions once or periodically. Actions are used to trigger reactions more irregularly. An action, like an output or input port, can carry data, but unlike a port, an action is visible only within the reactor that defines it.
There are two kinds of actions, logical and physical. A logical
action
is used by a reactor to schedule a trigger at a fixed logical time interval d into the future. The time interval d, which is called a delay, is relative to the logical time t at which the scheduling occurs. If a reaction executes at logical time t and schedules an action a
with delay d, then any reaction that is triggered by a
will be invoked at logical time t + d. For example, the following reaction schedules something (printing the current elapsed logical time) 200 msec after an input x
arrives:
Here, the delay is specified in the call to schedule within the target language code. Notice that in the diagram, a logical action is shown as a triangle with an L. Logical actions are always scheduled within a reaction of the reactor that declares the action.
The time argument is required to be non-negative. If it is zero, then the action will be scheduled one microstep later. See Superdense Time.
The arguments to the lf_schedule()
function are the action named a
and a time. The action a
has to be declared as an effect of the reaction in order to reference it in the call to lf_schedule()
. If you fail to declare it as an effect (after the ->
in the reaction signature), then you will get an error message.
The time argument to the lf_schedule()
function has data type interval_t
, which, with the exception of some embedded platforms, is a C int64_t
. A collection of convenience macros is provided like the MSEC
macro above to specify time values in a more readable way. The provided macros are NSEC
, USEC
(for microseconds), MSEC
, SEC
, MINUTE
, HOUR
, DAY
, and WEEK
. You may also use the plural of any of these, e.g. WEEKS(2)
.
An action may have a data type, in which case, a variant of the lf_schedule()
function can be used to specify a payload, a data value that is carried from where the lf_schedule()
function is called to the reaction that is triggered by the action. See the Target Language Details.
An action may have a data type, in which case, a variant of the schedule()
function can be used to specify a payload, a data value that is carried from where the schedule()
function is called to the reaction that is triggered by the action. See the Target Language Details.
The arguments to the a.schedule()
method is a time. The action a
has to be
declared as an effect of the reaction in order to reference it in the body of
the reaction. If you fail to declare it as an effect (after the ->
in the
reaction signature), then you will get a runtime error message.
The time argument to the a.schedule()
method expects an integer. A collection
of convenience functions is provided like the MSEC
function above to specify
time values in a more readable way. The provided functions are NSEC
, USEC
(for microseconds), MSEC
, SEC
, MINUTE
, HOUR
, DAY
, and WEEK
. You may
also use the plural of any of these, e.g. WEEKS(2)
.
An action may carry data, in which case, the payload data value is just given as a second argument to the .schedule()
method. See the Target Language Details.
The schedule()
method of an action takes two arguments, a TimeValue
and an (optional) payload. If a payload is given and a type is given for the action, then the type of the payload must match the type of the action. See the Target Language Details for details.
::: warning FIXME :::
An action may have a data type, in which case, a variant of the schedule()
function can be used to specify a payload, a data value that is carried from where the schedule()
function is called to the reaction that is triggered by the action. See the Target Language Details.
Physical Actions​
A physical
action
is used to schedule reactions at logical times determined by the local physical clock. If a physical action with delay d is scheduled at physical time T, then the logical time assigned to the event is T + d. For example, the following reactor schedules the physical action p
to trigger at a logical time equal to the physical time at which the input x
arrives:
If you drive this with a timer, using for example the following structure:
then running the program will yield an output something like this:
Action triggered at logical time 201491000 nsec after start.
Action triggered at logical time 403685000 nsec after start.
Action triggered at logical time 603669000 nsec after start.
...
Here, logical time is lagging physical time by a few milliseconds. Note that, unless the fast option is given, logical time t chases physical time T, so t < T. Hence, the event being scheduled in the reaction to input x
is assured of being in the future in logical time.
Whereas logical actions are required to be scheduled within a reaction of the reactor that declares the action, physical actions can be scheduled by code that is outside the Lingua Franca system. For example, some other thread or a callback function may call schedule()
, passing it a physical action. For example:
Physical actions are the mechanism for obtaining input from the outside world. Because they are assigned a logical time derived from the physical clock, their logical time can be interpreted as a measure of the time at which some external event occurred.
In the above example, at startup
, the main reactor creates an external thread that schedules a physical action roughly every 200 msec.
First, the file-level preamble
has #include "platform.h"
, which includes the declarations for functions it uses, lf_sleep
and lf_thread_create
(see Libraries Available to Programmers).
Second, the thread uses a function lf_sleep()
, which abstracts platform-specific mechanisms for stalling the thread for a specified amount of time, and lf_thread_create()
, which abstracts platform-specific mechanisms for creating threads.
The external
function executed by the thread is defined in a reactor-level preamble
section. See Preambles.
Triggering Time for Actions​
An action will trigger at a logical time that depends on the arguments given to the schedule function, the <min_delay>
, <min_spacing>
, and <policy>
arguments in the action declaration, and whether the action is physical or logical.
For a logical
action a
, the tag assigned to the event resulting from a call to schedule()
is computed as follows. First, let t be the current logical time. For a logical action, t is just the logical time at which the reaction calling schedule()
is called. The preliminary time of the action is then just t + <min_delay>
+ <offset>
. This preliminary time may be further modified, as explained below.
For a physical action, the preliminary time is similar, except that t is replaced by the current physical time T when schedule()
is called.
If a <min_spacing>
has been declared, then it gives a minimum logical time
interval between the tags of two subsequently scheduled events. If the
preliminary time is closer than <min_spacing>
to the time of the previously
scheduled event (if there is one), then <policy>
(if supported by the target)
determines how the minimum spacing constraint is enforced.
Note that "previously scheduled" here means specifically the tag resulting from
the most recent call to lf_schedule
for the same action.
Since calls to lf_schedule
can specify arbitrary extra delays,
<min_spacing>
does not necessarily result in events with minimum spacing between them.
If your calls to lf_schedule
result in monotonically increasing tags, however, you will
get events with minimum spacing between them.
The <policy>
is one of the following:
"defer"
: (the default) The event is added to the event queue with a tag that is equal to earliest time that satisfies the minimal spacing requirement. Assuming the time of the preceding event is t_prev, then the tag of the new event simply becomes t_prev +<min_spacing>
."drop"
: The new event is dropped andschedule()
returns without having modified the event queue."replace"
: The payload (if any) of the new event is assigned to the preceding event if it is still pending in the event queue; no new event is added to the event queue in this case. If the preceding event has already been pulled from the event queue, the default"defer"
policy is applied.
Note that while the "defer"
policy is conservative in the sense that it does not discard events, it could potentially cause an unbounded growth of the event queue.
The <policy>
is one of the following:
"defer"
: (the default) The event is added to the event queue with a tag that is equal to earliest time that satisfies the minimal spacing requirement. Assuming the time of the preceding event is t_prev, then the tag of the new event simply becomes t_prev +<min_spacing>
."drop"
: The new event is dropped andschedule()
returns without having modified the event queue."replace"
: The payload (if any) of the new event is assigned to the preceding event if it is still pending in the event queue; no new event is added to the event queue in this case. If the preceding event has already been pulled from the event queue, the default"defer"
policy is applied.
Note that while the "defer"
policy is conservative in the sense that it does not discard events, it could potentially cause an unbounded growth of the event queue.
The
<policy>
argument is currently not supported.
The
<policy>
argument is currently not supported.
The
<policy>
argument is currently not supported.
Testing an Action for Presence​
When a reaction is triggered by more than one action or by an action and an input, it may be necessary to test within the reaction whether the action is present.
Just like for inputs, this can be done in the C target with a->is_present
C++ target with a.is_present()
TypeScript target with a != undefined
Rust target with ctx.is_present(a)
Python target with a.is_present
, where a
is the name of the action.