helo patchers,
here is the fourth in a series of blogposts about our forthcoming nextbigthing that we still call vvvv50 (50). If you haven't already done so please first read the previous issues with the titles:
So "Generics", uff, sounds so serious, I know. Still we have to mention them now because it will help you understand things further down the road. And you'll be surprised how easy the concept is to grasp. Promised.
In vvvv45 (45) we don't have generic nodes. What we have instead is a lot of identical nodes like eg. "Zip" available for different datatypes. There is a Zip (Value), a Zip (String), a Zip (Color)... and the nodebrowser is full of such duplicates for a lot of nodes, like GetSlice, Select, Queue,... all of which are nodes where the operation they do for us is actually independent of the datatype they handle. We can call such operations "generic" in the sense that if for example you think of the functionality of a Zip it is not really important to know whether it zips slices of a spread of strings or a spread of colors. Easy? Yep.
Only recently we've introduced a way for plugin developers to easily create duplicates of those generic nodes for their own datatypes but that is really only a workaround to the fact that we don't have support for generics built right into 45. Still better than nothing, see: generic-nodes.
Now when we say 50 has support for generics we can advertise that in two ways:
First, the nodebrowser will have less nodes to choose from because it can leave out many duplicates (well, it will have many more other nodes but at least no datatype-duplicates). If you want a Zip you don't have to decide for which type you want it. Just place it in the patch, connect anything to it and you're done. 50 will figure out what you mean.
Secondly (and this is probably a bit more abstract to wrap your head around, but please try:) when you patch an operation you can be unspecific about the datatypes of its inputs and outputs. Sounds exciting? Here is an example: Consider someone patched the functionality of a Queue. This is what it could look like in 45 and 50:
Reading those two patches:
The Inlets and Outlets (Item, Insert, ..) of both implementations are the same and the FrameDelay, as we've already learned, is replaced by a property (called "Items" here). And while both kind of look generic, in the 45 implementation we see the Item obviously is a value IOBox. Therefore we know that this is a specific implementation for values.
In the 50 implementation you see all the operations (clear, insert, take) are working on the generic collection type Spread, ie. they have not yet been forced to operate on a specific type like Spread of value or Spread of color. And you can easily identify pins, in- and outputs and the property that are generic as they are visualized in a different way (ie. only showing their contour). So here is a single implementation of a queue that works for any datatype at a time, even ones that you create yourself (more on that in the next post).
What you take away here is that 50 comes with a set of generic spread operations (insert, take, zip,...) for handling any kind of data and the problem you sometimes faced in 45 where individual spread operations were only available for specific datatypes, is no more.
And the best of it all which is really only a side-note here:
For all those basic generic spread operations we don't have to write a single line of code. In 50 we can magically make use of that functionality as it comes with the .net library. Besides the fact that this saves us a lot of time it also means those basic operations can be assumed virtually bug-free, not only because we didn't write them but also because Microsoft has been taking care of testing that code since years.
That just for a little soothing happynew50 update. Now fasten your seatbelts for the next issue with the blockbuster title "Custom Datatypes".
If what you just learned makes you feel like inserting coin, don't hesitate and click: /downloads|vvvv?.
anonymous user login
~5h ago
~7d ago
~7d ago
~7d ago
~21d ago
~1mth ago
~1mth ago
~1mth ago
~1mth ago
~1mth ago
As much as I enjoy the blog posts, I would really like to see a bit more complicated examples, for now it feels a little bit too much "hello world" style samples.
This one is actually a perfect example so let's expand a little bit.
First this could be achieved to a certain extend with vvvv45 (on the exception of value/string/color/transform types which have a different interface for creation), since there is already a connection handler, so overriding this functionality can allow to have this (more or less store the type in a config pin to set connection).
Adding functionality to draw in patch from plugins (custom iobox and pin editor) and that would be pretty much set.
So now let's try to go out of the hello world case, and go into some real world scenarios.
1/Structs and "binary" compatible types.
Let's say we connect a float to a zip node, now we can't connect a Vector2 to it, whereas it could be totally acceptable (Vector2 is just two floats packed together, and with bit of il magic conversion is basically cost free, no need to copy memory at all).
2/Classes
Now things become more interesting :)
Let's say we have two interfaces iDummy1
and we have 2 classes cTest1 and cTest2 (both implementing iDummy1)
Now we connect something outputting cTest1 to the zip node.
Two scenarios are now possible:
3/Conversion
Let's now say we use Color4 and Vector4 (from SharpDX).
In c# you can just do:
Vector4 v = new Vector4(some data here)
Color4 c = v;
Since there's an implicit operator under the hood.
So would this Zip node (as example) which has a Vector4 input connected also accept a Color4 next to it (otherwise we end up in same situation having converter nodes everywhere)?
hey vux!
thanks for your ideas on this!
i do understand your impatience. And there may be some more people feeling the same regarding the pace of explanation in these blog posts. And we will need to go into quite more detailed / advanced topics sooner or later. That's for sure.
Anyway we decided that is not a good idea to confront people with too much details. Why? Because we want everybody to be able to follow. These blog posts are intended as a glimpse on the big picture, not the potentially distracting details.
Oh well. Well, the exceptions, right. And not only the fact that there are some types that are treated differently than others. Also the fact that spreads by itself are not treated as a type at all, they don't exist as a data type in vvvv45 since they are there implicitly everywhere as you know, which makes them another exception. You need to use Zip (Value Bin) to zip spreads of values...
So there are some issues that are intrinsic to vvvv45 that don't allow to build in generics into vvvv45 in a pleasing way.
That said there may be some ways to improve the situation. But this should be discussed elsewhere.
Let's say we connect a float to a zip node, now we can't connect a Vector2 to it, whereas it could be totally acceptable (Vector2 is just two floats packed together, and with bit of il magic conversion is basically cost free, no need to copy memory at all).
Let's first see what we expect the zip node to do:
A Zip (Value) shall take one value from the first spread, one Value from the second spread ...
A Zip (2d) shall take 2 values from the first spread, then two values from the second.
In vvvv45 these are already different nodes: they generate different output!
So it feels natural that a zip in vvvv50 at least treats those cases differently. I guess we agree on that.
We now can discuss about
I guess a spread of (1, 2, 3) makes a bad spread of 2d vectors and implicit conversion should not occur.
The other case that you have a spread of 2d vectors which should convert into a spread of values is of course thinkable. Let me just tell you that we have it on our radar and also already combine generics with some few implicit conversions.
2/Classes
The type inference which is running in the background is taking care of subtype realtionships, yes.
It is maybe of importance to explain that a zip node is not fixed to a certain type once the first connection has been made. After every single change of the patch - like a new connection - the system will take a fresh unbiased look at the patch and figure out the types for the generic nodes.
That way it typically figures out the right type for you, which might be a super type of two classes.
And in the rare cases where it doesn't, you will be able to interfere and make type annotations or force a link that the system wouldn't allow based on the currently infered types. After the forced link it will again look at the patch in a way that the new link is nothing special. It might then infere another type or might also give you a red link somewhere when the forced connection just doesn't make any sense - to the system that is... ;)
3/Conversion
As discussed before it is on our radar.
Thank you again for bearing with us!
i think our brains can handle more input.
and i really like what i see. can't wait until node...
hehe. i know. sorryy.
on the other hand: i just tried to remember the last "hello world" that shows how to implement a generic queue. and failed ;)
but you guys are still right: we are leaving out some details. feel invited to ask for more!
so good to know. we count on you, sebl! :)
Really good article, it explains clearly the concept. The feature is nice and will help simplify and create cleaner patches as well as reusable pieces of software. Good job!
Well, I guess the point of these posts is to to review the current situation, give a simple example how things are changing and what the benefits are ... without being able to play with it.
So, to me: Nailed it.
I'm wondering if spread apparently being a proper data type now implies that there can be spreads of spreads (i.e. multi-dimensional spreads without binsizing!) that you can wrap and unwrap layer by layer.
@TheSeven: exactly, good thinking. only also understand that the usecase for spreadofspread in 45 is mostly a workaround for lacking custom datatypes...