Control Flow
The flow of data in Rivet (and the control of that flow) is handled in two passes on the graph of nodes.
First Pass: Topological Sort & Entry Points
The first pass over nodes works on a topological sort basis. Rivet will find all nodes with no nodes that depend on them. These nodes are considered the "output nodes" of the graph.
Rivet will then find all nodes that depend on the output nodes, and so on, adding the node to a "needs to be processed" list.
Should a cycle be encountered at this point, Rivet will proceed as normal.
During the first pass, all nodes that have no dependencies (no data flowing into them) will be marked as "input nodes".
Second Pass: Execution
Starting at the input nodes marked in the first pass, rivet will execute all pending nodes in parallel.
Every time one of the nodes that is currently executing finishes, it will check to see if any of the nodes that depend on it are ready to be executed. If so, it will execute them in parallel with any other currently-executing node.
A node is defined as ready to execute if all of its dependencies have been satisfied. A dependency is satisfied if the node it depends on has finished executing and has a value to pass to the dependent node.
Control Flow Exclusions
What happens when an If node is encountered, and the output of the If node should not run? In this case, the output of the If node is the special control-flow-excluded
value. If this value is passed into any node, then that node will not execute.
Then, every dependent node of the node that returned control-flow-excluded
will also return control-flow-excluded
, and so on. In this respect, control flow exclusion "spreads" to every dependent node after the value has been returned.
Many nodes can return a control-flow-excluded
value, such as a Match Node (for branches that do not match), and an Extract Object Path Node (for when the input path is invalid for a given object).
Control Flow Excluded Consumers
Certain types of nodes are registered as able to "consume" a control-flow-excluded
value. This means that when the node encounters this value, it will actually run with the actual control-flow-excluded
value. This allows certain nodes to "break out" of the spreading of control-flow-excluded
values.
Nodes that can consume control-flow-excluded
values are:
- If/Else - If the
control-flow-excluded
is passed into theIf
port, then theElse
value will be passed through instead. If theElse
value is not connected, then the result will again becontrol-flow-excluded
. - Coalesce -
control-flow-excluded
will be considered "falsy" for the sake of the Coalesce node. The values will be skipped over, and subsequent truthy values connected to the Coalesce node will be passed through instead. - Race Inputs - If one of the branches passed into the Race Inputs node returns
control-flow-excluded
, then that branch will simply be not considered for the race. Other branches may still execute and return a value, which will be passed through the output of the Race Inputs. - Graph Output - A Graph Output's
control-flow-excluded
may pass out of the graph to become one of the outputs for a Subgraph node. This way, some of the outputs of a Subgraph may not run, and others may run. - Loop Controller - A loop controller needs to consume
control-flow-excluded
values in order to run multiple times. Additionally, passing acontrol-flow-excluded
to thecontinue
port counts as a "successful" iteration of the loop, and will cause the loop to run again.
Loop Controller
The loop controller is special, however, in particular its Break
port. The Break
port will not pass a control-flow-excluded
value to the next node
until the loop has finished executing. Otherwise, the loop controller itself could not run multiple times before finally passing a value to the next node.
If any other input port to the loop controller receives a control-flow-excluded
value, then the loop controller will not run again, and will pass the control-flow-excluded
value to the node connected to Break
. Thus, it is important to use an If/Else or Coalesce node inside your loop as a "null check" to make sure the loop controller never receives a control-flow-excluded
value unless you want it to.