Shared logic for multiple Process nodes

Hello everyone,

I’m making several Process nodes that all share the same structure and input/output types. Each node has a common “framework” (setup, IO, main processing flow) but differs in one (stateful) custom function inside the patch.

I’d like to avoid duplicating the framework code for each node — so that if I update the shared logic later, all variants automatically inherit those updates.

What’s the best way to create such a template or “common” Process node where only a part (the custom function) is replaceable or overridable by child nodes?

I’ve acheived the desired functionality via Interfaces, but the documentation says that they’re not officially supported yet - what does it mean in practice?

I’m using them a lot. From the description you’re making it sounds like interfaces are indeed the way to go.

I agree. I was just a bit confused by this support notice in the docs

There is not much you can do, in C# world it’s called composition e.g.:

class Base 
{
   string Bar ...
}
class Foo: Base {}

var foo = new Foo();
var bar = foo.Bar;

You can get this behaviour if you define prcocesses in c# , in gamma there is only Configure → Connect To Signature, but it’s bit tidy to work…

So, you mean that duplicating process node is the only reliable option?

Didn’t quite get this trick

To me, it sounds like you want to have a Common node that takes a delegate.

The implementation of a typical node then uses the Common node (e.g. as region) and feeds the missing parts.

To inherit the inputs and outputs of the Common node, you might want to try Configure → Connect to signature.

But Delegates are stateless and don’t work in cases like this one: Naive questions about textures and delegates

I think the problem here is a cumbersomeness of VL:

_Configure → Connect to signature. approach:

  1. Define AAAA process

  2. Define XXXX proccess:

Now XXXX includes AAAA

This works for simple stuff, let’s notice when it’s starts to be cumbersome:
Let’s define an operation in AAAA:

When we expose operation like that notice the pin is greyed out:

However on our forward it’s not

Now if we define BBBB operation here:


We notice our pin still not grayed out. (You can likely fix that with interface)

So… I guess you can do that - that way, but if you are using Operations (methods) at may lead to confusion…

That’s just one of the approaches…

process-connect-to-signature.vl (9.3 KB)

3 Likes

this sounds like you need class and interface really:

Let’s define two classes, process and interface:


Interface would have one method Invoke

In our process define Input of type “IIII”

Let’s implement interface on classes and do them process with state output:

Now we can switch AAAA and BBBB


class-interface-process.vl (14.4 KB)

3 Likes

State full regions is nice, but it’s not that simple to do, e.g. it’s grabbing a patch and converts it to delegate, statefull means Update still called inside region but, this only sounds good in practice it’s super unobvious… Idc there are examples with it, last time i spent 3 days to try to figure out how it works, for library it may have sense, for project not so sure about that…

2 Likes

Thank you a lot for the very detailed explanation!

I think that is the easiest way to implement this kind of logic so far. It has some definite trickyness when dealing with mutable data (e.g. textures), but this is not the scope of this topic, I guess. And it works way more predictable than “Connect to Signature” approach (which is still a bit obscure to me despite clear explanation)

niche side issue “forwards pins of multiple node-fragments from Core node”:
true. It only works well for pins on “Update”. For other pins this is the way I’d continue:


tried to give more meaningful names to the interface and the common patch:

Edit: “Update” instead of “Invoke”?
class-interface-process(2).vl (28.4 KB)

This also shows how you could potentially interact with properties of the common part.

Still: interfaces and classes - instead of delegate regions - lead to a lot of types that you need to manage. But you are right, currently the stateful region approaches are a bit tough to use still.
But it would be great to have only A, which uses a common region allowing to directly place the differing parts right there, without the A_Heart.
Please have a look at the help patch “Stateful delegate-based While loop”. Maybe this helps?

2 Likes

Thank you for sharing the patch! It looks like it could get really tricky to manage it when it comes to A, B, C, D, E at least. I couldn’t find this help patch unfortunately - is it specific to v7?

I’ve also tried to recreate my version of this, based on what I’ve understood so far (and what would be the desired pattern), but still there’re cases where it doesn’t work well:

InterfaceSharedLogic.vl (72.4 KB)

Hi, so first issue is LFO here:

when u define like that:


the function is not going to be evaluated (e.g. update would not ever be called)

So if we change the definitions to be properly processed:


Note for C we need updated exposed:

We already see different picture:

Now, few words on gradient:

You see this cord is actually mutable, what that means, on the back side of process, you create a class process node, it’s a a singe item, it has an single item gradient texture (e.g. pointer), the texture pointer is the same for whatever invoke caller, so what happens when it called with different args the texture will change for all of them, but since it’s a same memory address you see only latest change (e.g. blue)

To fix that, if you want to have consider removing this part basically:

Instead you should do that like that:

And now you can see it’s correct on all of your cases:

InterfaceSharedLogicFix.vl (71.2 KB)

You see the things that are unobvious, is that VL is not going to do for you stuff you have to do, like memory allocations. E.g. the Gradient is not something that exists in a method Invoke, it’s something that exists in an Instance of Class e.g. Node.

For the other issue, with Create, the point here that node is something more then just Create like if you want something Updated, it should be made that way that Update is called. You can think about that like, everything inside a patch is already inside Update method, if you call Create something and you want it to update it should be a node inside a patch not a method.

1 Like

Hey, thank you for pointing at the Operation issue, I’ve totally missed it. Is it necessary in that case to enable Process Node and State Output? When the LFO is evaluated on Invoke (suppose, the link from LFO assigned to Invoke operation) it should be properly updated or I’m missing something?

Regarding the gradients, thank you very much for the explanation, it makes total sense. The thing is that sometimes manipulation with mutable data can be confusing in this specific case (textures). Your corrections are 100% right way to address this issue, but consider another (unrelated to the initial topic) example:


From what I’ve understood it should work like so:

  1. A single texture in Color is created
  2. In Repeat node, a reference to the texture is duplicated few times. Being immutable Spread can hold mutable values anyway, if I got this right
  3. Repeat region operates on the same texture changing hue value resulting the last output texture, like in example with gradients

But the result is different - the output produces 3 different textures. It actually also looks like a reasonable behaviour. In fact I thought that it will work the same way in the gradient example, but it didn’t

Let’s start from the beginning. There is two major different types, is a value type (e.g. struct) and a reference type (e.g. class). Those types are started to evolve e.g. records were introduced to bridge gap between class and struct (e.g. mutable vs immutable).

So in your example the Color is just struct, the Texture is a special kind of class. You can think about that as number and a clock. The clock contains numbers, but number is not containing a clock. The clock should tick, the number is just number…

No, it’s not, right now you have 3 colors, and 3 textures created.

Repeat node creates a private variable (unseen to user) that holds a spread, and adds new Texture for that spread, then it used to track to call Update on that textures (so you can see changes in color in real-time)

The Texture is not something immutable, it’s a kind of resource managing class, that reads shader, compiles it on gpu to some gpu resource, then updating that resource…

1 Like

I think, I got the idea, thank you very much for the clarification!

Still, it would be handy to have such kind of tooltip about data type to make proper assumtion about it. Like in UE, where you can trace all relationships of parent-child classes, clearly distinct values from stuct e.t.c.