(ports)= # Ports and Connections Reactors may send and receive messages to or from other reactors using input and output ports. The ports of a reactor define its interface. Outputs of one reactor may be connected to the inputs of others, which gives a mechanism for composing programs from simple building blocks. ## Simple Connections Consider the following reactor. `````{tabs} ````{group-tab} plain ```{literalinclude} ../_examples/ports.py :pyobject: Counter ``` ```` ````{group-tab} with type hints ```{literalinclude} ../_examples/ports_hints.py :pyobject: Counter ``` ```` ````{group-tab} diagram ![diagram connection](./_img/diagram_ports_counter.png){.bg-warning w="35%" align=center} ```` ````` It implements a counting mechanism, similar to the `Timed` reactor that we considered before. In addition to the timer, it also declares an output port called `output` using an {class}`~xronos.OutputPortDeclaration`. The `increment` reaction is triggered by the timer. In addition to the trigger, the reaction also declares an *effect* using {func}`~xronos.ReactionInterface.add_effect`. This returns a {class}`~xronos.PortEffect` object, which the handler may use to write a value to the port using the {func}`~xronos.PortEffect.set` method. Setting a value on the port notifies any reactions that are triggered by a connected port. For instance, we can define a `Printer` reactor that prints any object that it receives. `````{tabs} ````{group-tab} plain ```{literalinclude} ../_examples/ports.py :pyobject: Printer ``` ```` ````{group-tab} with type hints ```{literalinclude} ../_examples/ports_hints.py :pyobject: Printer ``` ```` ````{group-tab} diagram ![diagram counter](./_img/diagram_ports_printer.png){.bg-warning w="22%" align=center} ```` ````` This reactor uses an {class}`~xronos.InputPortDeclaration` to declare an input port called `input_`. The reaction uses {func}`~xronos.ReactionInterface.add_trigger` to declare the input port as its trigger. The returned {class}`~xronos.Trigger` object can be used to read the received value using the {func}`~xronos.Trigger.get` method. Now we can write a `main()` function that instantiates and connects both reactors. `````{tabs} ````{group-tab} plain ```{literalinclude} ../_examples/ports.py :start-at: def main() ``` ```` ````{group-tab} with type hints ```{literalinclude} ../_examples/ports_hints.py :start-at: def main() ``` ```` ````{group-tab} diagram ![diagram printer](./_img/diagram_ports_simple.png){.bg-warning w="60%" align=center} ```` ````` Copy the two reactors and the `main()` function into a new file called `ports.py`. Do not forget to add the required imports: `````{tabs} ````{group-tab} plain ```{literalinclude} ../_examples/ports.py :end-at: import xronos ``` ```` ````{group-tab} with type hints ```{literalinclude} ../_examples/ports_hints.py :end-at: import xronos ``` ```` ````` Executing the program yields output similar to the following: ```{literalinclude} ../_examples/ports_out.txt :language: console ``` This program will execute indefinitely as the timer will always produce new events and we do not use {func}`~xronos.Reactor.request_shutdown`. Use {kbd}`Ctrl`+{kbd}`C` to abort the execution. If the program defines any reactions to {attr}`~xronos.Reactor.shutdown`, these will be invoked after the termination signal is received. Note that `printer` receives the first message right at startup, and all subsequent messages are spaced perfectly by the timer period of 100ms. As mentioned in {ref}`periodic timers`, time does not advance while reactions execute. This is also true for any messages sent via ports. The timestamp of any output produced by a reaction is equal to the timestamp of its trigger. ## Delayed Connections Sometimes it is useful to delay messages so that they are processed at a later point in time. The {func}`~xronos.Environment.connect` method accepts an optional `delay` argument. If it is set, the connection will output the message on the receiving side precisely at `get_time() + delay`. For instance, we can make the following modification to delay all messages by 1s: `````{tabs} ````{group-tab} plain ```{literalinclude} ../_examples/ports_delayed.py :pyobject: main :emphasize-lines: 5 ``` ```` ````{group-tab} with type hints ```{literalinclude} ../_examples/ports_delayed.py :pyobject: main :emphasize-lines: 5 ``` ```` ````{group-tab} diagram ![diagram delayed connection](./_img/diagram_ports_delayed.png){.bg-warning w="65%" align=center} ```` ````` This produces the following output after waiting for 1s: ```{literalinclude} ../_examples/ports_delayed_out.txt :language: console ``` ## Multiple Inputs Reactors may define an arbitrary number of input and output ports. While in prior examples all reactions had a single trigger, it is also possible to trigger reactions by multiple inputs. Consider the following `Multiplier` reactor. `````{tabs} ````{group-tab} plain ```{literalinclude} ../_examples/ports_multiply.py :pyobject: Multiplier ``` ```` ````{group-tab} with type hints ```{literalinclude} ../_examples/ports_multiply_hints.py :pyobject: Multiplier ``` ```` ````{group-tab} diagram ![diagram multiplier](./_img/diagram_ports_multiplier.png){.bg-warning w="40%" align=center} ```` ````` It declares two input ports `factor1` and `factor2` as well as the output port `product`. The `multiply` reaction declares both input ports as triggers. Consequently, the reaction executes when either or both of the inputs receive a message. The {func}`~Trigger.is_present()` method can be used to check if the trigger was activated and carries a value. ```{note} Calling {func}`~xronos.Trigger.get` while {func}`~xronos.Trigger.is_present` returns `False` raises an {class}`AbsentError`. ``` If both triggers are present, the handler writes the product to the output port using the corresponding effect. If only one of the triggers is present, then no output is produced. Note that it is also possible to define other strategies, such as assuming a default value or storing the last observed value on `self`. Now we have multiple options for connecting the `Multiplier` reactor with the `Counter` and `Printer` reactors. For instance, we can create the following pattern. `````{tabs} ````{group-tab} plain ```{literalinclude} ../_examples/ports_multiply.py :pyobject: main ``` ```` ````{group-tab} with type hints ```{literalinclude} ../_examples/ports_multiply_hints.py :pyobject: main ``` ```` ````{group-tab} diagram ![diagram square numbers](./_img/diagram_ports_squared.png){.bg-warning w="80%" align=center} ```` ````` This connects `counter.output` to inputs of `multiplier`, so that the same messages are delivered to both input ports. When executed, the program prints square numbers. ```{literalinclude} ../_examples/ports_multiply_out.txt :language: console ``` We can also achieve the same behavior using two instances of `counter`. `````{tabs} ````{group-tab} plain ```{literalinclude} ../_examples/ports_multiply2.py :pyobject: main ``` ```` ````{group-tab} with type hints ```{literalinclude} ../_examples/ports_multiply2.py :pyobject: main ``` ```` ````{group-tab} diagram ![diagram square numbers](./_img/diagram_ports_squared2.png){.bg-warning w="80%" align=center} ```` ````` Independent of how the reactors are connected, the runtime analyzes the connections as well as the declared triggers and effects of reactions to ensure repeatable behavior. Concretely, the `multiply` reaction does not need to wait for inputs to arrive. The runtime ensures that any reactions that may have an effect on `multiply`'s triggers are executed first. Thus, the values and the `is_present` status of any trigger is guaranteed to be known when the handler executes. Finally, we may use delayed connections to offset the factors received by `multiplier`. `````{tabs} ````{group-tab} plain ```{literalinclude} ../_examples/ports_multiply_delayed.py :pyobject: main ``` ```` ````{group-tab} with type hints ```{literalinclude} ../_examples/ports_multiply_delayed.py :pyobject: main ``` ```` ````{group-tab} diagram ![diagram multiplier delayed](./_img/diagram_ports_multiplier_delay.png){.bg-warning w="100%" align=center} ```` ````` This produces the following output. ```{literalinclude} ../_examples/ports_multiply_delayed_out.txt :language: console ```