Tutorial 18 - Node With Internal State

This node illustrates how you can use internal state information, so long as you inform OmniGraph that you are doing so in order for it to make more intelligent execution scheduling decisions.

The advantage of using internal state data rather than state attributes is that the data can be in any structure you choose, not just those supported by OmniGraph. The disadvantage is that being opaque, none of the generic UI will be able to show information about that data.

OgnTutorialState.ogn

The .ogn file containing the implementation of a node named “omni.graph.tutorials.StatePy”, with an empty state set to inform OmniGraph of its intention to compute using internal state information.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{
    "State" : {
        "version": 1,
        "categories": "tutorials",
        "description": [
            "This is a tutorial node. It makes use of internal state information",
            "to continuously increment an output."
        ],
        "metadata":
        {
           "uiName": "Tutorial Node: Internal States"
        },
        "inputs": {
            "overrideValue": {
                "type": "int64",
                "description": "Value to use instead of the monotonically increasing internal one when 'override' is true",
                "default": 0,
                "metadata": {
                    "uiName": "Override Value"
                }
            },
            "override": {
                "type": "bool",
                "description": "When true get the output from the overrideValue, otherwise use the internal value",
                "default": false,
                "metadata": {
                    "uiName": "Enable Override"
                }
            }
        },
        "outputs": {
            "monotonic": {
                "type": "int64",
                "description": "Monotonically increasing output, set by internal state information",
                "default": 0,
                "metadata": {
                    "uiName": "State-Based Output"
                }
            }
        },
        "tests": [
            { "inputs:overrideValue": 555, "inputs:override": true, "outputs:monotonic": 555 }
        ],
        "$tests": "State tests are better done by a script that can control how many times a node evaluates"
    }
}

OgnTutorialState.cpp

The .cpp file contains the compute method and the internal state information used to run the algorithm.

By adding non-static class members to your node OmniGraph will know to instantiate a unique instance of your node for every evaluation context, letting you use those members as state data. The data in the node will be invisible to OmniGraph as a whole and will be persistent between evaluations of the node.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// Copyright (c) 2020-2021, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto.  Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#include <OgnTutorialStateDatabase.h>
#include <atomic>

// Implementation of a C++ OmniGraph node that uses internal state information to compute outputs.
class OgnTutorialState
{
    // ------------------------------------------------------------
    // The presence of these members will be detected when a node of this type is being instantiated, at which
    // time an instance of this node will be attached to the OmniGraph node and be made available in the
    // database class as the member "db.internalState<OgnTutorialState>()".
    //

    // Start all nodes with a monotinic increment value of 0
    size_t mIncrementValue{ 0 };

    // ------------------------------------------------------------
    // You can also define node-type static data, although you are responsible for dealing with any
    // thread-safety issues that may arise from accessing it from multiple threads (or multiple hardware)
    // at the same time. In this case it is a single value that is read and incremented so an atomic
    // variable is sufficient. In real applications this would be a complex structure, potentially keyed off
    // of combinations of inputs or real time information, requiring more stringent locking.
    //
    // This value increases for each node and indicates the value from which a node's own internal state value
    // increments. e.g. the first instance of this node type will start its state value at 1, the second instance at 2,
    // and so on...
    static std::atomic<size_t> sStartingValue;
public:
    // Operation of the state mechanism relies on the presence of a default constructor. If you don't have to do
    // any initialization you can let the compiler provide the default implementation.
    OgnTutorialState()
    {
        mIncrementValue = sStartingValue;
        // Update the per-class state information for the next node. The values go up by 100 to make it easier for
        // tests to compare the state information on different nodes.
        sStartingValue += 100;
    }

    // Helper function to update the node's internal state based on the previous values and the per-class state.
    // You could also do this in-place in the compute() function; pulling it out here makes the state manipulation
    // more explicit.
    void updateState()
    {
        mIncrementValue += 1;
    }

public:
    // Compute the output based on inputs and internal state
    static bool compute(OgnTutorialStateDatabase& db)
    {

        // This illustrates how internal state and inputs can be used in conjunction. The inputs can be used
        // to divert to a different computation path.
        if (db.inputs.override())
        {
            db.outputs.monotonic() = db.inputs.overrideValue();
        }
        else
        {
            // OmniGraph ensures that the database contains the correct internal state information for the node
            // being evaluated. Beyond that it has no knowledge of the data within that state.
            auto& state = db.internalState<OgnTutorialState>();
            db.outputs.monotonic() = state.mIncrementValue;

            // Update the node's internal state data for the next evaluation.
            state.updateState();
        }

        return true;
    }
};

std::atomic<size_t> OgnTutorialState::sStartingValue{ 0 };

REGISTER_OGN_NODE()