as you all know one of our main focus for vvvv in the last couple of months was to make it easier to write plugins for it. we introduced the new plugin interface, which is mainly one interface called ISpread and a feature called “dynamic plugins”, which allows one to write and edit a plugin while vvvv is running. the idea is to allow the patcher to switch between visual or textual code as she/he sees fit in order to accomplish the task at hand. we could also say that it is now possible to “patch” a node (visual programming) or “write” a node (textual programming).
since more and more users show interest in “writing” their nodes and therefor we see and will see more and more contributions in this area i think it’s very important to start a discussion about various questions which might arise when “writing” a node. so feel free to ask here.
we’re also thinking about replacing some native nodes with nodes written with the new plugin interface because they’re less error-prone, dealing with two dimensional spreads is trivial and since they basically only rely on one interface (ISpread) they’re more suitable for future developments of vvvv.
so what i’d also like to achieve here is to come up with some kind of guidelines on how to write a node in a way so it has a chance to make it into the core of vvvv.
ok, now enough introduction talk, i’ll simply start with some guidelines i see important:
use generics
use generics when ever possible. this will ensure that your node is future proof (imagine a vvvv with a real generic type system). for now this looks like this:
public abstract class AbstractNode<T> : IPluginEvaluate
{
[Input("Foo")](Input("Foo"))
protected ISpread<T> FFooIn;
}
// boilerplate code. ugly, but for now there's no better solution.
[PluginInfo(Name = "MyNode", Category = "Value", ...)](PluginInfo(Name = "MyNode", Category = "Value", ...))
public class MyValueNode : AbstractNode<double>
...
[PluginInfo(Name = "MyNode", Category = "String", ...)](PluginInfo(Name = "MyNode", Category = "String", ...))
public class MyStringNode : AbstractNode<string>
...
put all your logic to the abstract class. hopefully at some point in the future it won’t be necessary anymore to define an abstract class and give concrete implementations of it, but for now it is as it is.
avoid the new operator in the Evaluate method
well this might sound strange, but let me try to explain:
the Evaluate method is called every frame. if there’s a new operator in there, memory will be allocated every frame. the allocation of memory in the managed world is super cheap, so no harm done here, but at some point in the future the memory must be freed, which is not that cheap. this is done by the garbage collector (GC). a garbage collector is a very sophisticated piece of technology and there’re tons of material to be found in the web about it, but i think the most important thing to understand about it in the context of writing a node for vvvv is this:
you don’t want the garbage collection to kick in. if you’re lucky, it’s very fast and you won’t notice it, but you’re probably not lucky and it’s not that fast and so you’ll see glitches; your rendering will stop for a few milliseconds every few seconds and that’s probably the worst thing to happen in a real time application. so to avoid the the necessity for the GC to kick in, simply don’t produce much garbage -> avoid calling new every frame.
if you find yourself in such a situation, think about reusing already allocated memory, for example if you need a dictionary in your evaluation see if you can create it once in your constructor and reuse it. or see next point.
make use ISpread or Spread
i often see code like this:
List<T> tmpList = new List<T>();
// do something with the inputs and the list
// and at the end
output.AssignFrom(tmpList);
why not using the output in the first place? missing the Add operation in ISpread? well i miss it too, and will certainly make sure that it’ll be available in the next release, but an add is simply a output.SliceCount++ followed by a output[output.SliceCount - 1](output.SliceCount - 1) = value. this will be done as an extension method.
all pin implementations of ISpread (except ISpread<ISpread>) inherit from Spread and they’ll only allocate new memory when needed. for example if the SliceCount increases, they’ll internally allocate the double amount of memory necessary to avoid allocating new memory every time the slice count changes.
what i’m trying to say here is to simply use the memory already allocated by the in- and output pins. only introduce temporary storages if really necessary and if so, try to create those storages only once (see point above).
buffering ISpread
sometimes you want to store an ISpread in a buffer to reuse it in the next frame. you’ll be tempted to write someting like:
myBuffer[i](i) = myInput;
as i outlined above, our implementations of ISpread try to reuse memory. so if the SliceCount doesn’t change much, they’ll work internally on the same array. now if you write myBufferi = myInput you’ll only store a reference to the input and in the next frame the array where the reference points to will already be overwritten with new data.
you’ll need to copy the data, so to do it right, write this:
myBuffer[i](i) = myInput.Clone();
i must admint here, that a myInput.Clone() is nothing more than a “new Spread(myInput)” call and therefor is not really the best approach (remember, avoid new in Evaluate). better would be to pre-allocate the buffer and do a
myBuffer[i](i).AssignFrom(myInput);
ok, that’s it for now, feel free to ask or add other tips, like i said, my intention is to collect as much information about writing nodes as possible and write a nice wiki article about it in the future.