I wanted to experiment with the ICustomRegion API and see what I could do. And in the process of experimenting, I realised that I had a few questions about the current limitations. It looks like most of the answers are in this discussion:
I would still like to clarify:
I can’t access the content of a region? For example, I want to collect all nodes that have Skia layers output and merge them in the output (arbitrary example).
How is this behaviour implemented in ImGUI? Contexts are collected whether they are connected or not. Connection only guarantees the order of execution.
Are there any plans to extend or update this API in the near future?
So far it seems that custom regions can’t do much (especially in the case of nodes with states). It is probably possible to write more advanced regions in C#, as in the case of ImGUI, but it is not quite clear how to do this. It would be great if you could give more info or some tips to improve understanding. Regions seem like a very powerful concept, I wish it was possible to develop my own
update: I took a close look at the AddToDictionary.vl and realised that this is a very cool structure that allows you to make regions containing nodes with state. Very interesting.
I attached a patch ForEachKey.vl (75.8 KB) with two custom regions that show how you can run the user patch inside the region several times and handle stateful nodes.
The trick is to instantiate the whole user patch several times. There are several ways to do it. I used a Synchronizer and the CustomRegionPatch process node - I sometimes like to patch process-oriented, but you could also use the API more directly: ICustomRegion.CreateRegionPatch is the key idea, which you can call as often you like. You then should keep track of all the patch instances and update them as you see fit. And when done Dispose them. This overhead is the reason why I made the Synchronizer handle the lifetime of the user patches - in my case as long a certain key is present - calling Dispose for me. But there is no right or wrong, just different ways of interacting with the API.
While patching this demo region I stumbled over the task of dynamically creating dictionaries for the output BCPs via reflection. I wasn’ quite sure, but hoped that again a region could step in and help me with that. I tried and ended up with a region that only makes use of the border control points - for specifying which generic type to create - the region that doesn’t instantiate the user patch at all - similar to the comment region.
I hope those examples show you how you can get creative. And yes: we know that the task isn’t trivial.
I forgot to document the configuration of the Region input pin.
When using Splicer BCPs, the Type Constraint is used in a way to relate inside and outside types. This is at vvvv compile time.
In this regard, it’s pretty much one of the first things our compiler looks at when trying to make sense of the user patch and how the region is used. When inferring the types inside and outside the region this is a central puzzle piece that helps inferring the compile time types in the user patch.
The idea back then was: when using a Splicer the type inside might be some T1 and outside it might be M<T1>.
So I entered a constraint with one open parameter, while the other one being fixed.
In the screenshot you see String as the first argument, it should be TKey. Just tried that, but it looks like the compiler gets confused by that way of expression.
That seems to be a bug, which should get looked at, even though we are getting rather special interest here.
@sebl ForEachKey2.vl (75.3 KB)
For me the tricky part was to find the Cast node which turns a non-generic sequence into a generic one.
As always, thank you so much for taking the time to explain it so fully and well. It is always a great pleasure to read your descriptions full of very important technical details.
I will need some time to look at the example in more detail. Generally a very powerful technique, this could be really interesting!
But I have noticed something that I would like to clarify. There’s still all this work going on around BCP. I realise that it’s an undiscovered topic in itself, with powerful potential. But we still can’t access nodes within a region? Maybe I’d like to do something like ImGUI, where I can place widgets anywhere in the region. And then I want those nodes to do some work without connecting to each other (or connecting for some side effects like execution order as in the case of ImGUI). And at the end I could get those nodes, execute them or, which would be really cool, access the pins of those nodes. I might not even want to use BCP in such a scenario.
right… I didn’t get back to some of your specific questions. Let’ go.
verbose
vvvv gamma comes with those different views on it.
development environment with abilities to get enriched: It can be seen as a toolbox, that can be enriched with “HDE extensions” - and yes even a node inspector is such a patched extension that is able to look at the user program on a node, pin and pad level. There is an API to get a hand on live nodes and pins when the development environment is still running.
development environment with the idea to build programs, which also could get exported: a system that allows you to express yourself graphically. With nodes. But what you build has nothing to do with nodes. It’s about objects, their properties, referring to each other, calling each others’ operations.
The former view blends development time and runtime. The latter one is the one that makes a clear distinction between the form of expression and what it translates to: A program.
Sorry, I didn’t find the time to keep it short.
Here is my answer: I think in 99% of the time we should take the latter view, it’s all about the program, not the nodes and regions and pins, which somehow don’t even exist in the end.
But still, yes, you can build something similar.
You could come up with a region that
establishes some way of communication before calling ICustomRegionPatch.Update
nodes picking up on that and talking to the region
or
a region that allows (process node) objects to place themselves into a collection provided by the region.
a region that after calling ICustomRegionPatch.Update knows of all nodes/objects and then does something with that information.
Objects could implement the one or other interface which the region tests for and thus is able to talk to those objects via these interfaces.
or
Like ImGUI regions do it:
Make sure that some context/state machine is just waiting for nodes to do their thing
Call back ICustomRegionPatch.Update, let nodes access that state and mutate it.
ImGUI is also implemented via the custom region API, but in the end that was just the most comfortable way to establish many regions fast.
The ideas of how ImGUI internally works are untouched and already come with this “magic behavior” that allows for mentioning UI elements inside a hierarchical code structure leading to a UI that matches that code structure. This is done by IMmediatly drawing that GUI, thus avoiding “retained-mode” widget structure ideas where you need to build and maintain an object structure.
So ImGUI regions don’t collect nodes. ImGUI regions just make sure that the stage is set for the nodes to draw themselves into. A tree node region would tell the ImGUI library
start treeview node,
do whatever (region user patch)
end treeview node.
The context pins of all ImGUI nodes and regions + “Context BCPs” actually are artificial. They really do not transmit any data and are only to make it easier for the user to establish the correct execution order.
It’s like normal pins which just don’t get used in any way.
Not right now. Fuse for quite a while was the driving force here. Let’s see. When there is good reason to work on the API we’ll put into on our list. Keep the ideas coming.
I started to understand how it works. I must admit it’s very interesting. I did some basic experiments, looked at node sources.
But one thing made me wonder, how does it work? I understand that one of the main mechanisms implemented by a pair of nodes is “Send” and “Receive”. In “Send”, “This” is passed, which makes it possible to access the “Region Object”.
Is it necessary to wrap this group of nodes (InputValues, CustomRegionPatch, SetOutputValues)? Is this due to the fact that we need to create local storage that can be accessed via Receive? Is it something like Scope?
If so, does this “Scope” start to be accessible because a Custom Region Patch is “created” wrapped by “Send”?
I’m clarifying to better understand the process. I have found that essentially only the section where the Custom Region is created needs to be wrapped in Send.
Those “local” Send & Receive nodes are scoped. Probably scoped would be the better name.
By using those nodes your regions also work in a nested fashion: