Skip to main content
Version: Nightly 🚧

Inputs and Outputs

This article has examples in the following target languages:

In this section, we will endow reactors with inputs and outputs.

Input and Output Declarations​

Input and output declarations have the form:

input <name>:<type> output <name>:<type>

For example, the following reactor doubles its input and sends the result to the output:

target C reactor Double { input x: int output y: int reaction(x) -> y {= lf_set(y, x->value * 2); =} }

Notice how the input value is accessed and how the output value is set. This is done differently for each target language. See the Target Language Details for detailed documentation of these mechanisms. Setting an output within a reaction will trigger downstream reactions at the same Logical Time that the reaction is invoked (or, more precisely, at the same tag). If a particular output port is set more than once at any tag, the last set value will be the one that downstream reactions see. Since the order in which reactions of a reactor are invoked at a logical time is deterministic, and whether inputs are present depends only on their timestamps, the final value set for an output will also be deterministic.

The type of a port is a type in the target language plus the special type time. A type may also be specified using a code block, delimited by the same delimiters {= ... =} that separate target language code from Lingua Franca code in reactions. Any valid target-language type designator can be given within these delimiters.

The reaction declaration above indicates that an input event on port x is a trigger and that an output event on port y is a (potential) effect. A reaction can declare more than one trigger or effect by just listing them separated by commas (See Reactions for details). For example, the following reactor has two triggers and tests each input for presence before using it:

target C reactor Destination { input x: int input y: int reaction(x, y) {= int sum = 0; if (x->is_present) { sum += x->value; } if (y->is_present) { sum += y->value; } printf("Received %d.\n", sum); =} }
note

If a reaction fails to test for the presence of an input and reads its value anyway, then the result it will get is target dependent.

In the C target, the value read will be the most recently seen input value, or, if no input event has occurred at an earlier logical time, then zero or NULL, depending on the data type of the input.

Setting an Output Multiple Times​

If one or more reactions set an output multiple times at the same tag, then only the last value set will be seen by any downstream reactors.

If a reaction wishes to test whether an output has been previously set at the current tag by some other reaction, it can test it in the same way it tests inputs for presence.

Mutable Inputs​

Normally, a reaction does not modify the value of an input. An input is said to be immutable. The degree to which this is enforced varies by target language. Most of the target languages make it rather difficult to enforce, so the programmer needs to avoid modifying the input. Modifying an input value may lead to nondeterministic results.

Occasionally, it is useful to modify an input. For example, the input may be a large data structure, and a reaction may wish to make a small modification and forward the result to an output. To accomplish this, the programmer should declare the input mutable as follows:

mutable input <name>:<type>

This is a directive to the code generator indicating that reactions that read this input may also modify the value of the input. The code generator will attempt to optimize the scheduling to avoid copying the input value, but this may not be possible, in which case it will automatically insert a copy operation, making it safe to modify the input. The target-specific reference documentation has more details about how this works.