Skip to content
Leon Thomm edited this page May 29, 2023 · 1 revision

Welcome to the Ryven wiki!

  • This wiki provides some explanation of the abstract concepts
  • Read the quick start guide in the README first
  • Ryven itself doesn't have a precise documentation, but provides examples
    • Instead of an out-of-date documentation for everything, I am simply providing a few example packages which I try to keep mostly running.
  • The backbone library ryvencore which defines most of the nodes API does have a documentation

Intro

The Ryven editor is a general purpose visual nodes editor for Python. It puts as few constraints on your nodes as possible. This implies mainly three aspects to consider:

  1. Your nodes can execute any Python code and can be arbitrarily complex.
  2. The more complicated your node setup the more careful design is required.
  3. Ryven itself doesn't do much, probably less than you expect right now.

Basic Internals

In Ryven, the top-level object is the Session which represents the current project. You might never access it directly (though you can). The session keeps track of:

  • flows
  • node types
  • add-ons
    • such as ryvencore's Logger and Variables add-ons

Unlike most other node editors, Ryven supports data connections and exec connections. Data connections transmit data from one node to another, and exec connections only transmit a trigger signal. Pure data flows (only data connections) are like the Unreal Engine Material Editor, while exec flows (some exec connections) are more like the Unreal Engine BluePrints. You can choose the appropriate mode for each flow individually.

Flow execution

There are a couple of different modes / algorithms for executing a flow.

Data Flow

Data is forward propagated on change. When a node receives some data at an input, its update_event() is executed. This is where the node can start processing data. When new data is generated during that processing, new output values can be pushed with set_output_value(index, data) which then causes the new data to be pushed to all successors connected to that indexed output, and again causes an update_event() in each respective successor.

In the example below, changing the slider value would therefore cause immediate updates and a visible change in the result node on the right. The get var and lower / nodes are not recomputed when doing so!

data_flow_example

The default flow execution mode is this simple data-flow mode where the flow immediately updates successor nodes when node outputs are set.

Assumptions in the data-flow mode

  • no non-terminating feedback loops

Data Flow with Optimization

You might have noticed that this naïve data-flow approach can lead to immense performance issues depending on your nodes' update patterns, and the shape of the graph (especially in case of "diamonds" in the graph). With the simple approach, every time the node calls self.set_output_val(...) the successors are updated. To provide a more suitable approach for many scenarios under a slightly tightened assumption, there is an optimized data-flow mode, creatively called data-flow-opt.

Assumptions in the data-flow-opt mode

  • no feedback loops
  • nodes never modify their ports during execution

Notice that while the graph representation naturally makes most types of feedback loops very obvious (because you have a cycle in the graph), not all possible types of feedback loops are visible this way. If some node $A$ causes eventually an update of a successor $B$, which in turn e.g. updates a script variable (explained further below) or even manually updates another node which causes update of $A$, this is semantically still a feedback loop, even though there is no syntactical cycle in the graph. Any kinds of such loops are not allowed here, the flow of data should strictly follow a tree shape.

How it works Whenever a new execution is invoked somewhere (some node or output is updated), it analyzes the graph's connected component (of successors) where the execution was invoked and creates a few auxiliary data structures (running in $\mathcal{O}(|V|+|E|)$) to reverse engineer how many input updates every node possibly receives in this execution. A node is updated whenever it receives input, and outputs are propagated only when no input can still receive data from a predecessor node (notice, a predecessor does not need to update all outputs during the update). Therefore, while a node gets updated every time an input receives some data, every *output* is only updated *once*. This implies that every connection is activated at most once during a single execution.

For the case of consecutive flow executions with no changes to the flow structure in between, the auxiliary data structures are cached, causing significant performance improvement.

This can result in asymptotic execution speedup in large data flows compared to normal data flow execution where any two executed branches that merge again in the future will result in two complete executions of the whole subgraph behind the merge.

Exec Flows

In execution flows, data isn't forward propagated on change, but generated on request (backwards), only causing update events in affected nodes once the data of an output is requested somewhere (through self.input() in a node). In the example above, in a flow in exec mode, changing the slider value would not lead to a change in the result node in an exec flow, but if an active node requests this data, like shown below, then the whole expression gets executed. Exec connections behave like data connections in the default data mode.

exec_flow_example

Assumptions in the exec-flow mode

  • no non-terminating feedback loops (neither with exec nor with data connections)
  • a node pushes all new output data first, before executing any exec outputs

The data flow paradigm is the more fundamental one, and there might be changes for the exec mode in the future.

While you can choose the according mode for a flow, it turned out to be a use case too to use the data flow mode in combination with some exec connections as well. This can lead to performance issues, but is quite powerful if used in the right way. For more precise definitions on the aspects of flow execution, see ryvencore.

Porting from Ryven v3.2

After over a year of improvements mainly in ryvencore, a lot has changed, and many breaking API changes will make your old projects and packages incompatible.

Porting Nodes

You need to apply the following changes in your nodes packages.

How to transform a nodes.py file

For all this, see the example packages for details on how things work in v3.4.

  • change from ryven.NENV import * -> from ryven.node_env import *
  • change widgets = import_widgets(...) -> guis = import_guis(__file__)
  • change export_nodes(...) -> export_nodes([...])
  • change NodeInputBP -> NodeInputType
  • change NodeOutputBP -> NodeOutputType
  • dtypes are gone, use inp_widgets in GUI classes now
  • change self.input(i) -> self.input(i).payload
  • change self.set_output_val(i, x) -> self.set_output_val(i, Data(x))
    • or whatever custom Data class you want to use

How to transform a widgets.py (now gui.py) file

  • change widgets.py -> gui.py
  • change MWB -> NodeMainWidget
  • change IWB -> NodeInputWidget
  • for each node for which you defined anything GUI-related (like a color, a display title, widgets, etc) you will have to create a corresponding NodeGUI subclass in gui.py which you will import just like you imported the widgets before; see example packages
  • change export_widgets(...) -> export_guis([...])
  • NodeInputWidget.get_val() was removed
  • NodeInputWidget.update_node_input() was introduced

Porting Projects

The significant changes make it hard to load old projects. In case you have an old project file which you really want to get running on v3.4 without re-making it, I wrote a hacky translation algorithm that, when you load a Ryven v3.2 project in Ryven v3.4, tries to change the project format so it can be loaded with the new version. Your nodes will initially have no output value, you need to update them first. Same for values of your widgets. You may have to remove some nodes, and place them again. When your project works again, save it and you should be able to load it again successfully.