Initializing Attributes to Non-Default Values
Normally you will specify attribute default values in your .ogn files and the attributes will be given those values when the node is created. Occasionally, you may wish to provide different default values for your attributes based on some condition that can only be ascertained at runtime. This document describes the current best-practice for achieving that goal, in both C++ and Python nodes.
As of this writing the database cannot be accessed during the node’s initialize() method, so providing new values in there will not work. (If in the future that changes this document will be updated to explain how to use it instead.)
In fact, the only time the data is guaranteed to be available to use or set is in the node’s compute() method, so that will be used to set up a delayed initialization of attribute values.
The general approach to this initialization will be to use a boolean state value in the node to determine whether the attribute or attributes have been given their initial values or not when the compute() method is called. It’s also possible that the attribute would have been given a value directly so that also must be considered when managing the boolean state value.
For these examples this node definition will be used:
{
"RandomInt": {
"version": 1,
"description": "Holds an integer with a random integer number.",
"outputs": {
"number": {
"description": "The random integer",
"type": "int"
}
},
"state": {
"$comment": "This section exists solely to inform the scheduler that there is internal state information."
}
}
}
Note
For the Python node assume the addition of the line "language": "python"
.
Initializing Values In C++ Nodes
In the internal state tutorial you can see that a way to add state information to a C++ node is to make some class members. When the owning graph is instantiated, we can have that state information shared amongst all graph instances (for each copy of our node type - a “node instance”), or/and have that information linked to each individual graph instance for that node instance. In our example here, we’ll add a boolean class member to tell us if the node is initialized or not, for each graph instance, and check it in the compute() method.
#include <OgnRandomIntDatabase.h>
#include <cstdlib>
class OgnRandomInt
{
bool m_initialized{ false }; // State information the tells if the attribute value has been initialized yet
public:
static bool compute(OgnRandomIntDatabase& db)
{
auto& state = db.perInstanceState<OgnRandomInt>();
if (! state.m_initialized)
{
db.outputs.number() = std::rand();
state.m_initialized = true;
}
// Normally you would have other things to do as part of the compute as well...
return true;
}
};
REGISTER_OGN_NODE()
If you know your attribute will never be set from the outside then that is sufficient, however usually there is no guarantee that some script or UI has not set the attribute value. Fortunately the node can monitor that using the registerValueChangedCallback() ABI function on the attribute. It can be set up in the node’s initialize() method. The callback will need to iterate through all graph instances in order to setup the state information. If the graph happens to not be instantiated, it still runs on its “default” instance, so it is safe to reset at least one state.
Putting this in with the above code you get this:
#include <OgnRandomIntDatabase.h>
#include <cstdlib>
class OgnRandomInt
{
bool m_initialized{ false }; // State information that tells if the attribute value has been initialized yet
static void attributeChanged(const AttributeObj& attrObj, const void*)
{
// Even if the graph is not instantiated (instCount == 0), we still have the "default" instance.
// Using do-while to make sure at least the default instance is updated
OgnRandomIntDatabase db(attrObj.iAttribute->getNode(attrObj));
int64_t instCount = (signed) db.getGraphTotalInstanceCount();
do
{
auto& state = db.perInstanceState<OgnRandomInt>();
state.m_initialized = true;
db.moveToNextInstance();
} while (--instCount > 0);
}
public:
static bool compute(OgnRandomIntDatabase& db)
{
auto& state = db.perInstanceState<OgnRandomInt>();
if (! state.m_initialized)
{
db.outputs.number() = std::rand();
state.m_initialized = true;
}
// Normally you would have other things to do as part of the compute as well...
return true;
}
static void initialize(const GraphContextObj&, const NodeObj& nodeObj)
{
AttributeObj attrObj = nodeObj.iNode->getAttributeByToken(nodeObj, outputs::number.m_token);
attrObj.iAttribute->registerValueChangedCallback(attrObj, attributeChanged, true);
}
};
REGISTER_OGN_NODE()
Initializing Values In Python Nodes
In the internal state tutorial you can see that the way to add state information to a Python node is to create a static internal_state method. We’ll create a simple class with a boolean class member to tell us if the node is initialized or not, and check it in the compute() method. Be aware that in python, there is no notion of a shared state between all graph instances. A distinct state object is created for each graph instance for that node copy, or “node instance” in the graph.
from dataclasses import dataclass
from random import randint
class OgnRandomInt:
@dataclass
class State:
initialized: bool = False
@staticmethod
def internal_state() -> State:
return OgnRandomInt.State()
@staticmethod
def compute(db) -> bool:
if not db.per_instance_state.initialized:
db.outputs.number = randint(-0x7fffffff, 0x7fffffff)
db.per_instance_state.initialized = True
# Normally you would have other things to do as part of the compute as well...
return True
If you know your attribute will never be set from the outside then that is sufficient. Unfortunately the Python API does not yet have a method of getting a callback when an attribute value has changed so for now this is all you can do.