Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions docs/assets/code/c/src/TransientFederates.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
target C

preamble {=
#include <stdlib.h>
#include <stdio.h>
=}

/** Persistent upstream federate. Sends 0, 1, 2, ... every 2 seconds. */
reactor Up(period: time = 2 s) {
output out: int
timer t(0, period)
state count: int = 0

reaction(t) -> out {=
lf_set(out, self->count);
lf_print("Up sending %d", self->count);
self->count++;
=}
}

/**
* Transient federate that forwards inputs from `Up` to `Down`.
* After four inputs it leaves the federation by calling `lf_stop()`.
*/
reactor Middle {
input in: int
output out: int
output join: int
state count: int = 0

reaction(startup) -> join {=
tag_t t = lf_tag_start_effective();
lf_print("Middle joined at effective start tag (" PRINTF_TIME ", %u)",
t.time - lf_time_start(), t.microstep);
lf_set(join, 0);
=}

reaction(in) -> out {=
self->count++;
lf_print("Middle forwarding %d (count %d)", in->value, self->count);
lf_set(out, in->value);
if (self->count == 4) {
lf_stop();
}
=}
}

/** Persistent downstream federate. Continues even while Middle is absent. */
reactor Down(period: time = 2 s) {
timer t(0, period)
input in: int
input join: int

reaction(t) {=
lf_print("Down timer at (" PRINTF_TIME ", %u)",
lf_time_logical_elapsed(), lf_tag().microstep);
=}

reaction(join) {=
lf_print("Down observed Middle join");
=}

reaction(in) {=
lf_print("Down received %d from Middle", in->value);
=}
}

federated reactor {
up = new Up()
down = new Down()

@transient
mid = new Middle()

up.out -> mid.in
mid.join -> down.join
mid.out -> down.in
}
76 changes: 76 additions & 0 deletions docs/assets/code/py/src/TransientFederates.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
target Python

preamble {=
import os
import subprocess
import sys
=}
Comment thread
ChadliaJerad marked this conversation as resolved.

# Persistent upstream federate. Sends 0, 1, 2, ... every 2 seconds.
reactor Up(period=2 s) {
output out
timer t(0, period)
state count = 0

reaction(t) -> out {=
out.set(self.count)
print("Up sending {}".format(self.count))
self.count += 1
=}
}

# Transient federate that forwards inputs from `Up` to `Down`.
# After four inputs it leaves the federation by calling `lf.stop()`.
reactor Middle {
input inp
output out
output join
state count = 0

reaction(startup) -> join {=
t = lf.tag_start_effective()
print("Middle joined at effective start tag ({}, {})".format(
t.time - lf.time.start(), t.microstep))
join.set(0)
=}

reaction(inp) -> out {=
self.count += 1
print("Middle forwarding {} (count {})".format(inp.value, self.count))
out.set(inp.value)
if self.count == 4:
lf.stop()
=}
}

# Persistent downstream federate. Continues even while Middle is absent.
reactor Down(period=2 s) {
timer t(0, period)
input inp
input join

reaction(t) {=
print("Down timer at ({}, {})".format(
lf.time.logical_elapsed(), lf.tag().microstep))
=}

reaction(join) {=
print("Down observed Middle join")
=}

reaction(inp) {=
print("Down received {} from Middle".format(inp.value))
=}
}

federated reactor {
up = new Up()
down = new Down()

@transient
mid = new Middle()

up.out -> mid.inp
mid.join -> down.join
mid.out -> down.inp
}
1 change: 1 addition & 0 deletions docs/assets/images/diagrams/TransientFederates.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions docs/glossary/glossary.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ description: Glossary of terms used in the Lingua Franca documentation.

Glossary of terms used in the Lingua Franca (LF) documentation.

### Federate
A top-level reactor instance in a [federation](#federation). The compiler generates a separate program for each federate.

### Federation
A distributed Lingua Franca program, specified with a `federated reactor`. The compiler generates a separate program for each top-level reactor instance (each **federate**) plus, for most targets, an RTI that coordinates startup and shutdown. See [Distributed Execution](../writing-reactors/distributed-execution.mdx).

### Persistent Federate
A federate that must be present when a federation starts and that remains until the federation ends. Federates are persistent unless they are marked `@transient`. See [Transient Federates](../writing-reactors/transient-federates.mdx).

### Transient Federate
A federate marked `@transient`. It need not be present when the federation starts, and it may join and leave during execution. Supported for the C and Python targets. See [Transient Federates](../writing-reactors/transient-federates.mdx).

### LF File
A source file with the `.lf` or `.ulf` extension, representing a Lingua Franca (LF) program. The `.ulf` extension is used for [micro-LF](https://micro-lf.org) programs, and the `.lf` extension is used for all other LF programs.

Expand Down
17 changes: 12 additions & 5 deletions docs/reference/target-language-details.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1706,8 +1706,9 @@ There are also some useful functions for accessing physical time:
- `instant_t lf_time_physical()`: Get the current physical time.
- `instant_t lf_time_physical_elapsed()`: Get the physical time elapsed since program start.
- `instant_t lf_time_start()`: Get the starting physical and logical time.
- `tag_t lf_tag_start_effective()`: Get the tag at which this federate effectively started. For a [transient federate](../writing-reactors/transient-federates.mdx) that joins a running federation, this may be later than the federation start tag.

The last of these is both a physical and logical time because, at the start of execution, the starting logical time is set equal to the current physical time as measured by a local clock.
The last two of these relate to start time. `lf_time_start()` is both a physical and logical time because, at the start of execution, the starting logical time is set equal to the current physical time as measured by a local clock.

A reaction can examine the current logical time (which is constant during the execution of the reaction). For example, consider the [GetTime](https://github.com/lf-lang/lingua-franca/blob/master/test/C/src/GetTime.lf) example:

Expand Down Expand Up @@ -1951,8 +1952,9 @@ There are also some useful functions for accessing physical time:
- `lf.time.physical() -> int`: Get the current physical time.
- `lf.time.physical_elapsed() -> int`: Get the physical time elapsed since program start.
- `lf.time.start() -> int`: Get the starting physical and logical time.
- `lf.tag_start_effective() -> Tag`: Get the tag at which this federate effectively started. For a [transient federate](../writing-reactors/transient-federates.mdx) that joins a running federation, this may be later than the federation start tag.

The last of these is both a physical and a logical time because, at the start of execution, the starting logical time is set equal to the current physical time as measured by a local clock.
The start time from `lf.time.start()` is both a physical and a logical time because, at the start of execution, the starting logical time is set equal to the current physical time as measured by a local clock.

A reaction can examine the current logical time (which is constant during the execution of the reaction). For example, consider the [GetTime.lf](https://github.com/lf-lang/lingua-franca/blob/master/test/Python/src/GetTime.lf) example:

Expand Down Expand Up @@ -2820,13 +2822,17 @@ For micro-LF documentation, see [micro-lf.org](https://micro-lf.org).

A reaction may request that the execution stop after all events with the current timestamp have been processed by calling the built-in method `request_stop()`, which takes no arguments. In a non-federated execution, the actual last tag of the program will be one microstep later than the tag at which `request_stop()` was called. For example, if the current tag is `(2 seconds, 0)`, the last (stop) tag will be `(2 seconds, 1)`. In a federated execution, however, the stop time will likely be larger than the current logical time. All federates are assured of stopping at the same logical time.

To stop only the calling federate, without requesting that the rest of the federation stop, call `lf_stop()`. That is used by [transient federates](../writing-reactors/transient-federates.mdx) to leave a running federation.

> The [timeout](<../writing-reactors/termination.mdx#timeout>) target property will take precedence over this function. For example, if a program has a timeout of `2 seconds` and `request_stop()` is called at the `(2 seconds, 0)` tag, the last tag will still be `(2 seconds, 0>)`.

</ShowIf>
<ShowIf py>

A reaction may request that the execution stop after all events with the current timestamp have been processed by calling the built-in method `lf.request_stop()`, which takes no arguments. In a non-federated execution, the actual last tag of the program will be one microstep later than the tag at which `lf.request_stop()` was called. For example, if the current tag is `(2 seconds, 0)`, the last (stop) tag will be `(2 seconds, 1)`. In a federated execution, however, the stop time will likely be larger than the current logical time. All federates are assured of stopping at the same logical time.

To stop only the calling federate, without requesting that the rest of the federation stop, call `lf.stop()`. That is used by [transient federates](../writing-reactors/transient-federates.mdx) to leave a running federation.

> The [timeout](<../writing-reactors/termination.mdx#timeout>) target property will take precedence over this function. For example, if a program has a timeout of `2 seconds` and `request_stop()` is called at the `(2 seconds, 0)` tag, the last tag will still be `(2 seconds, 0>)`.

</ShowIf>
Expand Down Expand Up @@ -2967,10 +2973,11 @@ Reactions in C can use a number of [pre-defined functions](https://www.lf-lang.o

- **File Access**

- LF_SOURCE_DIRECTORY: A C string giving the full path to the directory containing the `.lf` file of the program.
- LF_PACKAGE_DIRECTORY: A C string giving the full path to the directory that is the root of the project or package (normally, the directory above the `src` directory).
- LF_SOURCE_DIRECTORY: A string giving the full path to the directory containing the `.lf` file of the program.
- LF_PACKAGE_DIRECTORY: A string giving the full path to the directory that is the root of the project or package (normally, the directory above the `src` directory).
- LF_SOURCE_GEN_DIRECTORY: The directory in which generated files and any files in the [files](<target-declaration.mdx#files>) target directive are placed.
- LF_FILE_SEPARATOR: A C string giving the file separator for the platform containing the `.lf` file ("/" for Unix-like systems, "\\" for Windows).
- LF_FED_PACKAGE_DIRECTORY: A string giving the full path to the directory that is the root of the federate package (normally, the directory above the `src` directory appended with `fed-gen/name`, where `name` is the name of the federation).
- LF_FILE_SEPARATOR: A string giving the file separator for the platform containing the `.lf` file ("/" for Unix-like systems, "\\" for Windows).

These are useful when your application needs to open and read additional files. For example, the following C code can be used to open a file in a subdirectory called `dir` of the directory that contains the `.lf` file:

Expand Down
4 changes: 4 additions & 0 deletions docs/sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ const sidebars: SidebarsConfig = {
"type": "doc",
"id": "writing-reactors/distributed-execution"
},
{
"type": "doc",
"id": "writing-reactors/transient-federates"
},
{
"type": "doc",
"id": "writing-reactors/polyglot"
Expand Down
4 changes: 4 additions & 0 deletions docs/writing-reactors/distributed-execution.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ The time value is specified by a positive integer followed by units, one of `ns`
</ShowOnly>

When a federate receives the starting time from the RTI, then it will wait until its local physical clock matches or exceeds that starting time. Thus, to the extent that the machines have [synchronized clocks](#clock-synchronization), the federates will all start executing at roughly the same physical time, a physical time close to the starting logical time.

<ShowOnly c py>
By default, the RTI waits for **every** federate to register before choosing that start time. You can instead mark some federates `@transient` so that they need not be present at startup and can join and leave during execution. See [Transient Federates](./transient-federates.mdx).
</ShowOnly>
</ShowOnly>

<ShowOnly uc>
Expand Down
4 changes: 4 additions & 0 deletions docs/writing-reactors/termination.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ When the RTI receives a **STOP_REQUEST** message from a federate, it forwards it

When a federate receives a **STOP_REQUEST** message, it replies with its current logical time _t_, completes its current tag (if one is progress), and blocks, waiting for a **STOP_GRANTED** message from the RTI. When it gets the reply with payload _s_, if _s_ > _t_, then it sets `timeout` = _s_ and continues executing, using the timeout mechanism (see above) to stop. If _s_ = _t_, then it schedules the shutdown phase to occur one microstep later, as in the unfederated case.

<ShowOnly c py>
To stop **only the calling federate** without stopping the federation, call <ShowOnly c inline>`lf_stop()`</ShowOnly><ShowOnly py inline>`lf.stop()`</ShowOnly>. That is the mechanism [transient federates](./transient-federates.mdx) use to leave while the rest of the federation continues.
</ShowOnly>

</ShowOnly>

## External Signal
Expand Down
Loading
Loading