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.
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
Nodes that can consume
control-flow-excluded values are:
- If/Else - If the
control-flow-excludedis passed into the
Ifport, then the
Elsevalue will be passed through instead. If the
Elsevalue is not connected, then the result will again be
- Coalesce -
control-flow-excludedwill 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-excludedmay 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-excludedvalues in order to run multiple times. Additionally, passing a
continueport counts as a "successful" iteration of the loop, and will cause the loop to run again.
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.