Signs of life

By • Apr 6th, 2008 • Category: Developer diaries

There are several different parts of the whole Fnk project I’ve been working over the past few weeks. The primary part I’m working on, as required by the college course itself, is its research paper, where I list some of the goals of the project and what kind of solutions I’m looking into. This is a month or so away from its due date, so it’s a bit rough right now – and completely written in Brazilian Portuguese anyway – so I won’t be posting much about it here (I will probably translate it to english once it’s finished, though). Regardless, it’s not that important right now.

However, another part of the project I’ve been working on – and of more practical importance, at least for anyone else – is the structure that will be used by Fnk itself to process programs made within its editor: its internal patch processing unit. A “patch” is a program built inside Fnk with a bunch of nodes of different types; so the patch processing is the heart of Fnk, and you can even say it’s completely separated from the editor itself.

Creating the processing core of a dataflow language poses a few new problems for me. So, right now, while I think I have most of them tackled, I’m doing a few experiments until I get it all right.

I also don’t have the editor interface working. While I do have a node renderer working more or less well too – another separate part of the project I’ve been working on (more on this in the future) – it’s not tied to the patch processing core. So I’m creating nodes manually, by using some custom code.

The fun part, however, is that it’s starting to really work. Today, I managed to get the main functionality of the processing core working – cycling through every node, and only process nodes whose input parameters had been correctly calculated already, also skipping nodes that don’t need recalculation for speed purposes – and I’m proud to say that the patch processing unit can finally do additions: it can add 1 to 1 and reach the value of 2!

This is the higher-level code used for such an incredible feat (API definitions still subject to change):

var p:Patch = new Patch();
var n1:Node = new org.ffnnkk.core.nodes.number.IO();
n1.setInputValue(0, [1]);
p.addNode(n1);

var n2:Node = new org.ffnnkk.core.nodes.number.IO();
n2.setInputValue(0, [1]);
p.addNode(n2);

var np:Node = new org.ffnnkk.core.nodes.number.Plus();
p.addNode(np);

var lnk1:Link = new Link();
lnk1.inputNode = n1;
lnk1.inputConnector = 0;
lnk1.outputNode = np;
lnk1.outputConnector = 0;
p.addLink(lnk1);

var lnk2:Link = new Link();
lnk2.inputNode = n2;
lnk2.inputConnector = 0;
lnk2.outputNode = np;
lnk2.outputConnector = 1;
p.addLink(lnk2);

p.process();

And this is the output produced by the temporary debugging log:

Fnk version 0.1
Build 162
Built at 2008-04-07 01:10

Patch :: Node created: [object IO]
Patch :: Node created: [object IO]
Patch :: Node created: [object Plus]
Patch :: Link created: [object Link]
Patch :: Link created: [object Link]
Patch :: Process
  Starting a node cycle
    Processing node [0]: [object IO]
      Uplink = null
      Can process
        Processing
        Removing
      Final value = 1
    Processing node [0]: [object IO]
      Uplink = null
      Can process
        Processing
        Removing
      Final value = 1
    Processing node [0]: [object Plus]
      Uplink = [object Link]
      Uplink = [object Link]
      Can process
        Getting value from connector 0
        Getting value from connector 1
        Processing
        Removing
      Final value = 2
  Ended node cycle

It might not look like so, but the actual node cycle procedure is a more complicated beast. Because it can only process nodes that already had their input parameters calculated, sometimes it’ll be forced to skip some nodes until they’re fully fireable – to put it another way, they’re dependent on other nodes. On the above example, because I added the nodes on the order they’re meant to be processed – two Input/Output nodes containing the number “1”, and one “Plus” node that adds boths values – it manages to just process the entire list with one single loop. If I had done it backwards, however…

p.addNode(np);
p.addNode(n2);
p.addNode(n1);

…the process would be slightly different, but with the same final result:

Fnk version 0.1
Build 164
Built at 2008-04-07 01:18

Patch :: Link created: [object Link]
Patch :: Link created: [object Link]
Patch :: Node created: [object Plus]
Patch :: Node created: [object IO]
Patch :: Node created: [object IO]
Patch :: Process
  Starting a node cycle
    Processing node [0]: [object Plus]
      Uplink = [object Link]
      Cannot process -- skipping to next
    Processing node [1]: [object IO]
      Uplink = null
      Can process
        Processing
        Removing
      Final value = 1
    Processing node [1]: [object IO]
      Uplink = null
      Can process
        Processing
        Removing
      Final value = 1
  Starting a node cycle
    Processing node [0]: [object Plus]
      Uplink = [object Link]
      Uplink = [object Link]
      Can process
        Getting value from connector 0
        Getting value from connector 1
        Processing
        Removing
      Final value = 2
  Ended node cycle

So it’s fully working, at least until I have to work on some more advanced nodes – the ones used to create loops on the patch (they will require a frame delay) – or when I have optimize the main process further.

Unfortunately, the whole core doesn’t do much more than simple additions. Of course, there are several dozen (if not hundreds) of node types worth being built, but for test purposes, this is all it’s doing right now.

But another cool feature, for now, is that all input and output connectors treat Arrays (which are lists of values) as if they were a series of normal input values, and simply repeats the data transformation done by a node on all values of the list. In practice, this means that if you try to add a normal numeric value to an array of values, the result will be a new array, but with each value of this new array being the corresponding value of the old array, with that single numeric value added. “Normal” numeric values are actually just one array with a single value. And you can add arrays to arrays, too. So, for example, if I did:

n1.setInputValue(0, [1,1]);
n2.setInputValue(0, [1]);

The final result would be:

[2,2]

If I tried to add an array to an array:

n1.setInputValue(0, [1,1]);
n2.setInputValue(0, [2,2]);

Then the final result would be:

[3,3]

And arrays with a different number of values simple cycle around each other as needed, as the output array will always have the same number of items as the biggest input array. For example, this:

n1.setInputValue(0, [1,2,3,4,5,6]);
n2.setInputValue(0, [1,2,3,4]);

Outputs this:

[2,4,6,8,6,8]

In the future, not all node types will allow multiple input or output parameters in the form of arrays. However, allowing arrays will be the default option, rather than just being a feature used a few times.

I must add that vvvv users will easily recognize this kind of feature as being the same as “spreads“.

So that it is. Not much in actual node functionality, but pretty nifty for a start in terms of general execution structure.

Comments are closed.