Vuo  0.4.1
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages

When a node allocates data on the heap (for example, with malloc) and sends it through an output port, the data travels to other nodes. The other nodes can hold onto that data for any amount of time. There's no guarantee of which node will be the last to use the data. Who is responsible for deallocating that data?

The answer is that the Vuo runtime is responsible for deallocating the data – but type and node class definitions are responsible for informing the Vuo runtime about the data.

The Vuo runtime uses reference counting to keep track of heap-allocated memory and deallocate it when all nodes are finished using it. The Vuo API for node classes and types provides three functions for reference counting:

To make the reference-counting system easier for you to use, the Vuo compiler automatically inserts most of the necessary calls to vuoRegister, vuoRetain, and vuoRelease. If you're implementing a stateless node class, you never need to call these functions. If you're implementing a stateful node class or a type, read on to learn when to call these functions.

Defining a heap-allocated type

If you define a type, and the type is a pointer to heap-allocated data, then the *_valueFromString function is responsible for registering that data. For example:

typedef char * VuoString;
VuoString VuoString_valueFromString(const char *initializer)
{
VuoString s = ...;
vuoRegister(s, free);
return s;
}

(The *_stringFromValue and *_summaryFromValue functions should not register their return values.)

If your type is a struct, and some of its members are pointers to heap-allocated data, then the type definition is responsible for registering that data. For example:

typedef struct {
float *elements;
size_t elementCount;
} ArrayOfFloats;
ArrayOfFloats ArrayOfFloats_valueFromString(const char *initializer)
{
ArrayOfFloats a;
a.elementCount = ...;
a.elements = (float *)calloc(elementCount, sizeof(float));
vuoRegister(a.elements, free);
return a;
}

If your type is some other container, and some of its elements are pointers to heap-allocated data, then the type definitions is responsible for registering each element that's allocated, retaining each element that's added, and releasing each element that's removed — as well as registering the container itself. For example:

typedef VuoString * PairOfStrings;
PairOfStrings PairOfStrings_valueFromString(const char *initializer)
{
PairOfStrings pair = (PairOfStrings)calloc(2, sizeof(VuoString));
vuoRegister(pair, PairOfStrings_destroy);
return pair;
}
void PairOfStrings_destroy(void *p)
{
PairOfStrings pair = (PairOfStrings)p;
vuoRelease(pair[0]);
vuoRelease(pair[1]);
free(pair);
}
void PairOfStrings_replaceFirst(PairOfStrings pair, VuoString newFirst)
{
vuoRetain(newFirst, free);
vuoRelease(pair[0]);
pair[0] = newFirst;
}

Using heap-allocated node instance data

If you use a node instance data type that already has a Vuo type definition (as described in the previous section), then you don't need to do anything. Memory management is already handled for you.

If you define a custom node instance data type within the node class, and the custom type consists of, or contains, pointers to heap-allocated data, then the node class is responsible for the same memory management as if you were defining a full-fledged Vuo type (as described in the previous section). The only difference is the function names (e.g. nodeInstanceInit() instead of *_valueFromString()).

For example, if the node instance data is a pointer to heap-allocated data, then the nodeInstanceInit() function is responsible for registering that data:

typedef char * MyString;
MyString nodeInstanceInit()
{
MyString s = ...;
vuoRegister(s, free);
return s;
}

As another example, if the node instance data is a struct, and some of its members are pointers to heap-allocated data, then the node class is responsible for registering that data:

typedef struct {
int *elements;
size_t elementCount;
} ArrayOfIntegers;
ArrayOfIntegers nodeInstanceInit()
{
ArrayOfIntegers a;
a.elementCount = ...;
a.elements = (int *)calloc(elementCount, sizeof(int));
vuoRegister(a.elements, free);
return a;
}

Automatically-inserted calls to the reference-counting functions

Except for the situations described above, the Vuo compiler automatically inserts calls to vuoRegister, vuoRetain, and vuoRelease where needed. Specifically:

When a port or node instance data has a struct type, the Vuo compiler automatically inserts calls to vuoRetain and vuoRelease any members of the struct (and members of members of the struct, etc.) that are pointers.