-
Notifications
You must be signed in to change notification settings - Fork 443
Home
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
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:
- Your nodes can execute any Python code and can be arbitrarily complex.
- The more complicated your node setup the more careful design is required.
- Ryven itself doesn't do much, probably less than you expect right now.
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.
There are a couple of different modes / algorithms for executing a 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!
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
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
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.
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.
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.
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.
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, useinp_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
- or whatever custom
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 ingui.py
which you will import just like you imported thewidgets
before; see example packages - change
export_widgets(...)
->export_guis([...])
-
NodeInputWidget.get_val()
was removed -
NodeInputWidget.update_node_input()
was introduced
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.