.. _runtime_attribute_value_initialization_in_omnigraph_nodes:

Runtime Attribute Value Initialization In OmniGraph Nodes
=========================================================

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:

.. code-block:: json

    {
        "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 :ref:`internal state tutorial<ogn_tutorial_state>` you can see that the way to add state information to a C++
node is to make some class members. We'll add a boolean class member to tell us if the node is initialized or not,
and check it in the **compute()** method.

.. code-block:: cpp

    #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.internalState<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.

Putting this in with the above code you get this:

.. code-block:: cpp

    #include <OgnRandomIntDatabase.h>
    #include <cstdlib>

    class OgnRandomInt
    {
        bool m_initialized{ false }; // State information the tells if the attribute value has been initialized yet

        static void attributeChanged(const AttributeObj& attr, const void*)
        {
            auto& state = OgnRandomIntDatabase::sInternalState<OgnRandomInt>(attr.iAttribute->getNode(attr));
            state.m_initialized = true;
        }

    public:
        static bool compute(OgnRandomIntDatabase& db)
        {
            auto& state = db.internalState<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 :ref:`internal state tutorial<ogn_tutorial_state_py>` 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.

.. code-block:: python

    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.internal_state.initialized:
                db.outputs.number = randint(-0x7fffffff, 0x7fffffff)
                db.internal_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.
