Help with stateful nodes

Hi guys ;)

I wonder if any coders out there could help me finally understand stateful nodes.
I don’t really know much about code, but I love to explore and create nodes to learn to code.

I guess I understand the main basic concepts, but I’m definitely stuck.

I’ve been watching YouTube videos about pointers, pointer to pointers and reading some articles about it, but no matter how long I look at the examples and the codes of the stateful stock nodes, I’m having a hard time.

I know what a variable is, I know a pointer stores the address of a variable.
That you can declare and initialize a pointer using datatype1 *myPointer.
That one can access (dereference) the value a pointer points to using *myPointer or **myPointer2 to access the value of a pointer to pointer.

But still :sob:

If someone could help me comment and change my comments and questions on this simplified Count stock node code, and understand the phases of it and how data travels where to do what, that would be much appreciated !
I think I get the main idea of memory being freed, and then setting values at those memory locations, but the exact steps and the syntax being used and why I fail.

Thanks ;)

/* @copyright Copyright © 2012–2022 Kosada Incorporated. This code may be modified and distributed under the terms of the MIT License. For more information, see https://vuo.org/license.*/

VuoGenericType1 * nodeInstanceInit // Are we declaring a pointer named "nodeInstanceInit" of type VuoGenericType1 ?
(
		VuoInputData(VuoGenericType1) setCount // Declare a variable named setCount ?
)
{
	VuoGenericType1 *countState = (VuoGenericType1 *) malloc(sizeof(VuoGenericType1)); // Declare and initialize a pointer named counState to what ? To the memory addresses of setCount ?
	VuoRegister(countState, free); // Free and allocate memory for the pointer counState ?
	*countState = setCount; // On node launch, the value the pointer points to is set to the value of "setCount" ?
	return countState; // Return the pointer
}

void nodeInstanceEvent
(
		VuoInstanceData(VuoGenericType1 *) countState, // Get/pass the data of countState ? And what is (VuoGenericType1 *) ? I thought nodeInstanceInit was the pointer ?
		VuoInputData(VuoGenericType1, {"defaults":{"VuoInteger":1, "VuoReal":1.0}}) increment, // Ok I get this, we declare a Vuo Port
		VuoInputEvent({"eventBlocking":"none","data":"increment"}) incrementEvent, // Here we declare event blocking, ok
		VuoInputData(VuoGenericType1, {"defaults":{"VuoInteger":0, "VuoReal":0.0}}) setCount, // Another port, ok
		VuoInputEvent({"eventBlocking":"none","data":"setCount"}) setCountEvent, // Ok
		
		VuoOutputData(VuoGenericType1) count // Ok, this is the ouput port of the node
)
{
	if (incrementEvent) // Ok, each time the node receives an event do ...
		**countState += increment; // ? If *countState is the value at the address of setCount, what is **countState ? We set 2** because it's a pointer inside of "VuoGenericType1 *" ? Or because single * are fort output ports ?
	if (setCountEvent) // when this receives en event, do ...
		**countState = setCount; // ___________ ? will become the value inserted at the setCount input port

	*count = **countState; // The output port "count" (is it somehow a pointer too ?) becomes ___________ ?
}
1 Like

I’ll try and come back to this and help. I started learning C to write nodes for Vuo but have been distracted. I would definitely start with stateless nodes to get your confidence up first, queue/hold the output if need be.  

1 Like

Hehe thanks @useful_design :pray::wink:
Can’t wait to see what you’ll make with C ! Love to see nodes made by the different users ;)

My problem I think is that watching some tutorials about C and reading articles, is that you get quite a scholar approach (declare a pointer like this, then link that address with &myVariable …)
But here the Vuo stateful node code is real code, with probably advanced syntax and methods and possible way of writing stuff, structures, nested inside stuff etc. So I guess quite quickly you’re far away from the simple scholar approaches.

I’ve read the Vuo API and its information, which is great, but I hope a commented code line by line could maybe help (this is a variable, this is a pointer named …, this line does this and refers to that, the data travels back here …) :)

Once you get used to pointers and certain patterns become familiar, it gets easier :) You don’t have to put all of your conscious focus into this being the address of that, you just know in the back of your mind.

The vuo.math.count node class’s code is actually more complicated than it needs to be. There was a limitation in earlier versions of Vuo that meant we had to use more pointers than would otherwise be necessary. When we fixed that, we didn’t go back and simplify the vuo.math.count code. (Now that you’ve brought it to our attention, we will.)

First, here’s an annotated version of your code, since you’ve already familiar with it. I’ve left in the comments for the parts you already understood.

// Define the `nodeInstanceInit` function. It returns a value of type `VuoGenericType1 *`.
// Think of it like this:
//
// VuoGenericType1 * nodeInstanceInit(VuoGenericType1 setCount)
// {
//    // … body of the function …
// }
// 
// The `VuoInputData` is special syntax required by the Vuo compiler to recognize a parameter as an input port.
VuoGenericType1 * nodeInstanceInit
(
        // Function parameter of type `VuoGenericType1` named `setCount`.
        VuoInputData(VuoGenericType1) setCount
)
{
    // Declare a variable of type `VuoGenericType1 *` named `countState`.
    // Allocate a piece of memory the right size to hold a `VuoGenericType1` value. (The memory just holds uninitialized data at this point.)
    // Set `countState` to the address of that memory.
    VuoGenericType1 *countState = (VuoGenericType1 *) malloc(sizeof(VuoGenericType1));

    // Register `countState` with Vuo's reference-counting system.
    // Later during the composition execution, when the reference count of `countState` goes down to 0, the `free` function will be called to deallocate `countState`.
    // See https://api.vuo.org/latest/group___managing_memory.html
    VuoRegister(countState, free);

    // Copy the value of `setCount` into the piece of memory that `countState` points to.
    *countState = setCount;

    return countState; // Return the pointer
}

// Define the `nodeInstanceEvent` function.
// Think of it like this:
//
// void nodeInstanceEvent(VuoGenericType1 **countState, VuoGenericType1 increment, bool incrementEvent, VuoGenericType1 setCount, bool setCountEvent, VuoGenericType1 *count)
// {
//    …
// }
//
// The `VuoInstanceData`, `VuoInputEvent`, etc. are special syntax required by the Vuo compiler.
void nodeInstanceEvent
(
        // Function parameter of type `VuoGenericType1 **` named `countState`.
        // The `VuoInstanceData` adds an extra layer of pointers, making `countState` an output parameter.
        // See https://stackoverflow.com/questions/42403940/c-input-and-out-parameters-of-a-function .
        // The value of `*countState` in this function is the same as the value returned from the `nodeInstanceInit` function.
        VuoInstanceData(VuoGenericType1 *) countState,

        VuoInputData(VuoGenericType1, {"defaults":{"VuoInteger":1, "VuoReal":1.0}}) increment, // Ok I get this, we declare a Vuo Port
        VuoInputEvent({"eventBlocking":"none","data":"increment"}) incrementEvent, // Here we declare event blocking, ok
        VuoInputData(VuoGenericType1, {"defaults":{"VuoInteger":0, "VuoReal":0.0}}) setCount, // Another port, ok
        VuoInputEvent({"eventBlocking":"none","data":"setCount"}) setCountEvent, // Ok
 
        // Function parameter of type `VuoGenericType1 **` named `count`.
        // The `VuoOutputData` adds an extra layer of pointers, making `count` another output parameter.
        VuoOutputData(VuoGenericType1) count // Ok, this is the ouput port of the node
)
{
    if (incrementEvent) // Ok, each time the node receives an event do ...
        // Add `increment` to the count stored in the node instance data.
        // `**countState` is the piece of memory allocated in `nodeInstanceInit`.
        // `*countState` is a pointer to that memory.
        // `countState` is a pointer to a pointer to that memory.
        **countState += increment;
    if (setCountEvent) // when this receives en event, do ...
        // Set the count stored in the node instance data to `setCount`.
        **countState = setCount;
 
    // Set the output port's value to the count stored in the node instance data.
    // `*count` is the piece of memory where the output port value is stored (created by Vuo outside of this function).
    // `count` is a pointer to that memory (passed into this function by Vuo).
    *count = **countState;
}

Here’s a simplified version with one fewer level of pointers for the node instance data:

// Think of it as:
// 
// VuoGenericType1 nodeInstanceInit(VuoGenericType1 setCount)
// {
//    …
// }
VuoGenericType1 nodeInstanceInit
(
        VuoInputData(VuoGenericType1) setCount
)
{
    return setCount;
}

// Think of it as:
//
// void nodeInstanceEvent(VuoGenericType1 *countState, VuoGenericType1 increment, bool incrementEvent, VuoGenericType1 setCount, bool setCountEvent, VuoGenericType1 *count)
// {
//    …
// }
void nodeInstanceEvent
(
        // Function parameter of type `VuoGenericType1 *` named `countState`.
        // The first time this function is called, `*countState` is the value that was returned from `nodeInstanceInit()`.
        VuoInstanceData(VuoGenericType1) countState,

        VuoInputData(VuoGenericType1, {"defaults":{"VuoInteger":1, "VuoReal":1.0}}) increment,
        VuoInputEvent({"eventBlocking":"none","data":"increment"}) incrementEvent,
        VuoInputData(VuoGenericType1, {"defaults":{"VuoInteger":1, "VuoReal":1.0}}) decrement,
        VuoInputEvent({"eventBlocking":"none","data":"decrement"}) decrementEvent,
        VuoInputData(VuoGenericType1, {"defaults":{"VuoInteger":0, "VuoReal":0.0}}) setCount,
        VuoInputEvent({"eventBlocking":"none","data":"setCount"}) setCountEvent,
        VuoOutputData(VuoGenericType1) count
)
{
    if (incrementEvent)
        *countState += increment;
    if (decrementEvent)
        *countState -= decrement;
    if (setCountEvent)
        *countState = setCount;
    *count = *countState;
}

Thank you @jstrecker !

Will take myself some time to study your answer but already wanted to thank you :)

When we fixed that, we didn’t go back and simplify the vuo.math.count code. (Now that you’ve brought it to our attention, we will.)

Done in Vuo 2.4.2.

1 Like