Hi, in this very short 2nd introduction video I'll...
# two-minute-week
m
Hi, in this very short 2nd introduction video I'll show you how to create a body mass index calculator with my flow-editor step by step. It also shows (very briefly) the new debug functionality to help "follow" the flow.

https://youtu.be/oVNdm3JWE4g

👍 1
🎉 2
w
Here I see one slider feeding into another feeding into an expression... so what do the arrows mean in this editor?
m
Do you mean the arrows of the connections between the nodes? That's the path and direction the flow follows when it is executed. In this example the sliders both trigger the weightSliderTask-node (each slider node has an "onChange" property which is assigned to the weightSliderTask-node for both sliders in this example)
The tech stack on the frontend for those interested : mainly react, redux, react-konva, victor, rxjs, bootstrap, material ui slider, immer and a custom service worker running the flowrunner
r
I find it a bit strange that weight flows into height when no data flowing from weight will change height in anyway. Do you think it would make more sense for both sliders to feed into the expression in parallel?
m
I see your point, from my perspective the current approach makes sense because you let the payload grow as it progresses through the flow. From a user perspective that might be making less sense. I've got support for parellel flows already, I'll see if that will work here. Thanks for the feedback!
👍 1
c
My synthesiser project works in a similar way - multiple notes and generated channels of audio data flow along the pipeline, sometimes combining and splitting (like when the notes are combined into a stereo stream or when a pair of oscillators go into a mixer). There is a definite ‘flow direction’.
👍 1
r
Maybe there's a way to indicate the data being collected as it flows down the pipeline. If we could see it, I believe it would make more sense.
g
i understand that the visualization is a work in progress—I’m more excited by exactly how little you have to type to get these things connected together. so much of traditional UI programming depends on essentially “watch” statements
m
Yes it's a work in progress but there's already much more too show then I did so far (2 minutes is very short offcourse ;-) ). Maybe I'll prepare a longer video and post it in #feedback, I'll think about it
The way parallel flows currently works: there's a special start node and a parallel-resolve node.. the start node can split into multiple flows and the resolve node waits until they all ran and then passes all payloads through to the next node.
👍 1
c
Have you considered a directed graph where you 'pull data' from the far end? So that parallel resolve will happen because the dependent node will evaluate the nodes further back until all inputs are up to date.
m
I've got a special type of connection which is called "injection" which sounds like this and I was thinking about it actually as an alternative. The current implementation of that is rather limited though, it's just one level deep instead of going further if there are more nodes connected.
this is the current parallel solution. Both sliders trigger the first node when a slider is changed in the UI, this is specified in the 'onChange' property of both slider nodes (you can see it in the video)
c
I see, so does your diagram imply that you only have a single input/output for each node?
(at least in terms of flow...?)
m
Each node can be triggered by multiple nodes independently (the parallel example is an exception to that) and the payload the node receives determines the input parameters/properties and not all properties need to be handled by the node, most of them are usually just passed on to the next node. A node can trigger multiple output nodes
The current system probably is limited in its applications because it doesn't have "control-inputs", I think I could implement these by adding observable properties in a task. I am going to that explore that idea further. @Chris Maughan how is this implemented in your application?
In the meantime I want to share another screenshot to show a bit more of the other applications of the flow-editor : here the flow-editor is used to create a flow which controls a react-native app. When you press save in the editor a json file is stored on the local file system and triggers the rebuild of the reactnative-app. The flow flow contains both the navigational structure and the form definitions as well as the calculations. The flow-editor runs locally here (using node.js).
c
Hi @Maikel, I think my approach is quite different to yours. Essentially I have a directed graph. Once per frame, I read the output node(s) and they try to 'compute'. First they look at their inputs, and if any of them are 'flow data' or 'control data', they are checked to see if they are current. If not, the graph walks further up and evaluates until the node inputs are 'current'. Then it can be computed. So it's very much a 'pull' architecture. I have 3 basic types of 'pins' on my nodes. • Flow Data. This is like a big bundle of data arrays. The node is expected to work on all data streams it receives in each compute step. For music, these arrays are typically buffers of audio data; often with 1 buffer per note of polyphony. • Control Data. These are also bundles of arrays, but typically contain control information, such as modulation curves. It is just convenient for me to separate the concept of control from data. • Parameters. These are just data values such as float, int, etc. They can be connected to other nodes, but they are not 'evaluated' to satisfy graph compute. They are considered 'always valid', but that doesn't mean that the node won't walk back up the connection chain of the parameter to find the source value. It should be considered prototype/research code, but you can see the graph Compute function here: https://github.com/cmaughan/nodegraph/blob/master/src/model/graph.cpp
m
Hi, thanks for sharing your approach! Summarizing: the main difference between our implementations is that in yours in each frame the whole graph gets evaluated and in mine a node gets only triggers by an event (which can be external) and this can be dependent of time but certainly not necessarily and most of the time it's user input that trigger a node or other node's triggering other nodes. Both our graphs are directed I think. Also the way the graph gets evaluated is different, in yours the pull architecture as you describe is very different then in mine , which is forward directed. Very interesting this discussion about our approaches, I am going to think about it some more.
c
Yes, you have it right I think. Except to say that nodes in my graph that aren't required won't wind up getting evaluated; i.e. if they aren't part of the dependency chain of the output node, then they will not evaluate, and if a node is 'current', it doesn't need to do any work. I don't allow loops in the network; i.e. a node will not evaluate twice per graph evaluation. For this reason, I have a generation number on each node which can track the global generation and avoid repeat computation. It makes the evaluation logic quite clean I think.
I am probably quite influenced by the Maya directed graph approach, which was my first exposure to such an idea; though I don't implement the push/pull architecture that they do.
m
My influence was the node-red project
@Chris Maughan thanks for the detailed descriptions! It's really great to read about other implementations
c
No problem 😉 happy to share ideas.
i
This idea of each node building up the data being passed through in an accumulative way, rather than just "consuming" the input, is really, really interesting to me. It makes total sense, and feels like it could allow some really interesting / different ways of working with dataflow.
Can a slider's "onChange" trigger more than one node? How would that work? Just make onChange an array?
Also — when you moved the slider on the right, was that rewinding the execution history? If so, I found it odd that the sliders didn't update. Perhaps I'm just misunderstanding what it should do.
m
The slider on the right moves through the node execution history and selects the node that was executed and shows the output payload on the bottom right. And you're right I think: it would help if a node with a slider or other kind of input control also gets updated when the history slider is moved. In one of your video's I actually saw something similar, which was my inspiration to build this history slider. In the past I have experimented with a small list of payloads to show on the side of the screen, and for debugging purposes to add additional debug-nodes. But this works better.
Currently the onChange property can only contain a single node, being able to add an array for this to trigger multiple nodes is a good idea. In the future I also want to make the editing of a node easier with a proper form editor or with a more visual approach (the onchange property should actually be a special kind of connection, for other similar properties this is already partly implemented)
👍 1
Yeah, the gradually building up of the payload works quite nice, as you can see in this example it can get quite complex. Because by manipulating payload properties you can make loops. The flow in this screenshot is an implementation of the quicksort algorithm, just to see if that would be possible with the flowrunner. It even has a recursive function 😉. The blue lines are hidden connections which are configured in the nodes (here it are connections to the node where the list is stored which should be sorted). I also want to visualize the 'onChange' properties of the slider-node of the other examples like this.
👍 1
c
Awesome 🙂
m
Thanks! :-) that quicksort implementation might look impressive but I must admit : something like list.sort() is a lot easier to use 😉