Reactors can contain instances of other reactors defined in the same file or in an imported file. Assume the Count and Scale reactors defined in Parameters and State Variables are stored in files Count.lf and Scale.lf, respectively,
and that the TestCount reactor from Time and Timers is stored in TestCount.lf. Then the following program composes one instance of each of the three:
c
cpp
py
rs
ts
target C {
timeout: 1 sec,
fast: true
}
import Count from "Count.lf"
import Scale from "Scale.lf"
import TestCount from "TestCount.lf"
main reactor RegressionTest {
c = new Count()
s = new Scale(factor=4)
t = new TestCount(stride=4, num_inputs=11)
c.y -> s.x
s.y -> t.x
}
target Cpp {
timeout: 1 sec,
fast: true
}
import Count from "Count.lf"
import Scale from "Scale.lf"
import TestCount from "TestCount.lf"
main reactor RegressionTest {
c = new Count()
s = new Scale(factor=4)
t = new TestCount(stride=4, num_inputs=11)
c.y -> s.x
s.y -> t.x
}
target Python {
timeout: 1 sec,
fast: true
}
import Count from "Count.lf"
import Scale from "Scale.lf"
import TestCount from "TestCount.lf"
main reactor RegressionTest {
c = new Count()
s = new Scale(factor=4)
t = new TestCount(stride=4, num_inputs=11)
c.y -> s.x
s.y -> t.x
}
target Rust {
timeout: 1 sec,
fast: true
}
import Count from "Count.lf"
import Scale from "Scale.lf"
import TestCount from "TestCount.lf"
main reactor RegressionTest {
c = new Count()
s = new Scale(factor=4)
t = new TestCount(stride=4, num_inputs=11)
c.y -> s.x
s.y -> t.x
}
target TypeScript {
timeout: 1 sec,
fast: true
}
import Count from "Count.lf"
import Scale from "Scale.lf"
import TestCount from "TestCount.lf"
main reactor RegressionTest {
c = new Count()
s = new Scale(factor=4)
t = new TestCount(stride=4, numInputs=11)
c.y -> s.x
s.y -> t.x
}
As soon as programs consist of more than one reactor, it becomes particularly useful to reference the diagrams that are automatically created and displayed by the Lingua Franca IDEs. The diagram for the above program is as follows:
In this diagram, the timer is represented by a clock-like icon, the reactions by chevron shapes, and the shutdown event by a diamond. If there were a startup event in this program, it would appear as a circle.
The <parameters> argument is a comma-separated list of assignments:
<parameter_name> = <value>, ...
Like the default value for parameters, <value> can be a numeric constant, a string enclosed in quotation marks, a time value such as 10 msec, target-language code enclosed in {= ... =}, or any of the list forms described in Expressions.
where the port references are either <instance_name>.<port_name> or just <port_name>, where the latter form is used for connections that cross hierarchical boundaries, as illustrated in the next section.
On the left and right of a connection statement, you can put a comma-separated list. For example, the above pair of connections can be written,
c.y, s.y -> s.x, t.x
A constraint is that the total number of channels on the left match the total number on the right.
In addition, some targets require the types of all the ports to be the same.
A destination port (on the right) can only be connected to a single source port (on the left). However, a source port may be connected to multiple destinations, as in the following example:
c
cpp
rs
ts
py
reactor A {
output y:int
}
reactor B {
input x:int
}
main reactor {
a = new A()
b1 = new B()
b2 = new B()
a.y -> b1.x
a.y -> b2.x
}
reactor A {
output y:int
}
reactor B {
input x:int
}
main reactor {
a = new A()
b1 = new B()
b2 = new B()
a.y -> b1.x
a.y -> b2.x
}
reactor A {
output y:int
}
reactor B {
input x:int
}
main reactor {
a = new A()
b1 = new B()
b2 = new B()
a.y -> b1.x
a.y -> b2.x
}
reactor A {
output y:int
}
reactor B {
input x:int
}
main reactor {
a = new A()
b1 = new B()
b2 = new B()
a.y -> b1.x
a.y -> b2.x
}
reactor A {
output y
}
reactor B {
input x
}
main reactor {
a = new A()
b1 = new B()
b2 = new B()
a.y -> b1.x
a.y -> b2.x
}
Lingua Franca provides a convenient shortcut for such multicast connections, where the above two lines can be replaced by one as follows:
(a.y)+ -> b1.x, b2.x
The enclosing ( ... )+ means to repeat the enclosed comma-separated list of sources however many times is needed to provide inputs to all the sinks on the right of the connection ->.
where <classname> and <alias> can be a comma-separated list to import multiple reactors from the same file. The <path> specifies another .lf file relative to the location of the current file. The as <alias> portion is optional and specifies alternative class names to use in the new statements.
Reactors can be composed in arbitrarily deep hierarchies. For example, the following program combines the Count and Scale reactors within on Container:
c
cpp
py
rs
ts
target C
import Count from "Count.lf"
import Scale from "Scale.lf"
import TestCount from "TestCount.lf"
reactor Container(stride: int = 2) {
output y: int
c = new Count()
s = new Scale(factor=stride)
c.y -> s.x
s.y -> y
}
main reactor Hierarchy {
c = new Container(stride=4)
t = new TestCount(stride=4, num_inputs=11)
c.y -> t.x
}
target Cpp
import Count from "Count.lf"
import Scale from "Scale.lf"
import TestCount from "TestCount.lf"
reactor Container(stride: int(2)) {
output y: int
c = new Count()
s = new Scale(factor=stride)
c.y -> s.x
s.y -> y
}
main reactor Hierarchy {
c = new Container(stride=4)
t = new TestCount(stride=4, num_inputs=11)
c.y -> t.x
}
target Python
import Count from "Count.lf"
import Scale from "Scale.lf"
import TestCount from "TestCount.lf"
reactor Container(stride=2) {
output y
c = new Count()
s = new Scale(factor=stride)
c.y -> s.x
s.y -> y
}
main reactor Hierarchy {
c = new Container(stride=4)
t = new TestCount(stride=4, num_inputs=11)
c.y -> t.x
}
target Rust
import Count from "Count.lf"
import Scale from "Scale.lf"
import TestCount from "TestCount.lf"
reactor Container(stride: u32 = 2) {
output y: u32
c = new Count()
s = new Scale(factor=stride)
c.y -> s.x
s.y -> y
}
main reactor Hierarchy {
c = new Container(stride=4)
t = new TestCount(stride=4, num_inputs=11)
c.y -> t.x
}
target TypeScript
import Count from "Count.lf"
import Scale from "Scale.lf"
import TestCount from "TestCount.lf"
reactor Container(stride: number = 2) {
output y: number
c = new Count()
s = new Scale(factor=stride)
c.y -> s.x
s.y -> y
}
main reactor Hierarchy {
c = new Container(stride=4)
t = new TestCount(stride=4, numInputs=11)
c.y -> t.x
}
The Container has a parameter named stride, whose value is passed to the factor parameter of the Scale reactor. The line
s.y -> y;
establishes a connection across levels of the hierarchy. This propagates the output of a contained reactor to the output of the container. A similar notation may be used to propagate the input of a container to the input of a contained reactor,
Connections may include a logical delay using the after keyword, as follows:
<source_port_reference> -> <destination_port_reference> after <time_value>
where <time_value> can be any of the forms described in Expressions.
The after keyword specifies that the logical time of the event delivered to the destination port will be larger than the logical time of the reaction that wrote to source port. The time value is required to be non-negative, but it can be zero, in which case the input event at the receiving end will be one microstep later than the event that triggered it.
A subtle and rarely used variant of the -> connection is a physical connection, denoted ~>. For example:
main reactor {
a = new A();
b = new B();
a.y ~> b.x;
}
This is rendered in by the diagram synthesizer as follows:
In such a connection, the logical time at the recipient is derived from the local physical clock rather than being equal to the logical time at the sender. The physical time will always exceed the logical time of the sender (unless fast is set to true), so this type of connection incurs a nondeterministic positive logical time delay. Physical connections are useful sometimes in Distributed-Execution in situations where the nondeterministic logical delay is tolerable. Such connections are more efficient because timestamps need not be transmitted and messages do not need to flow through through a centralized coordinator (if a centralized coordinator is being used).