.. _ogn_node_architects_guide:

OmniGraph Node Architects Guide
===============================
This outlines the code that will be generated behind the scenes by the node generator from a .ogn file.
This includes the API presented to the node that is outlined in :ref:`ogn_user_guide`.

As the details of the generated code are expected to change rapidly this guide does not go into specific details,
it only provides the broad strokes, expecting the developer to go to the either the generated code or the code
generation scripts for the current implementation details.


Helper Templates
----------------
A lot of the generated C++ code relies on templates defined in the helper files ``omni/graph/core/Ogn*.h``,
which contains code that assists in the registration and running of the node, classes to wrap the internal data used
by the node for evaluation, and general type conversion assistance.

None of the code in there is compiled so there is no ABI compatibility problem. This is a critical feature of these
wrappers.

See the files themselves for full documentation on what they handle.


C ABI Interface
---------------
The key piece of generated code is the one that links the external C ABI for the compute node with the
class implementing a particular node's evaluation algorithm. This lets the node writer focus on their business
logic rather than boilerplate ABI conformity details.

The actual ABI implementation for the *INodeType* interface is handled by the templated helper class
*OmniGraphNode_ABI* from ``OgnHelpers.h``. It implements all of the functions required by the ABI, then uses the
"tag-dispatching" technique to selectively call either the default implementation, or the one provided by the
node writer.

In turn, that class has a derived class of *RegisterOgnNode* (defined in ``OgnHelpers.h``).
It adds handling of the automatic registration and deregistration of node types.

Instantiation of the ABI helper class for a given node type is handled by the *REGISTER_OGN_NODE()* macro, required
at the end of every node implementation file.



Attribute Information Caching
-----------------------------
ABI accesss requires attribute information be looked up by name, which can cause a lot of inefficiency. To prevent
this, a templated helper class *IAttributeOgnInfo* is created for every attribute. It contains the attribute's
information, such as its name, its unique handle token, and default value (if any). This is information that is the
same for the attribute in every instance of the node so there is only one static instance of it.


Fabric Access
-------------
This is the core of the benefit provided by the .ogn generated interface. Every node has a *OgnMyNodeDatabase* class
generated for it, which contains accessors to all of the Fabric data in an intuitive form. The base class
*OmniGraphDatabase*, from ``OgnHelpers.h``, provides the common functionality for that access, including token
conversion, and access to the context and node objects provided by the ABI.

The attribute data access is accomplished with two or three pieces of data for every attribute.

1. A raw pointer to the data for that attribute residing in the Fabric
2. (optional, for arrays only) A raw pointer to the element count for the Fabric array data
3. A wrapper class, which will return a reference object from its *operator()* function

The data accessors are put inside structs names *inputs* and *outputs* so that data can be accessed by the actual
attribute's name, e.g. *db.inputs.myInputAttribute()*.

Accessing Data
++++++++++++++
The general approach to accessing attribute data on a node is to wrap the Fabric data in a class that is suited
to handle the movement of data between Fabric and the node in a way that is more natural to the node writer.

For consistency of access, the accessors all override the call operator (``operator()``) to return a wrapper class
that provides access to the data in a natural form. For example, simple POD data will return a direct reference to
the underlying Fabric data (e.g. a ``float&``) for manipulation.

Inputs typically provide const access only. The declaration of the return values can always be simplified by
using ``const auto&`` for inputs and ``auto&`` for outputs and state attributes.

More complex types will return wrappers tailored towards their own access. Where it gets tricky is with variable
sized attributes, such as arrays or bundles. We cannot rely on standard data types to manage them as Fabric is
the sole arbiter of memory management.

Such classes are returned as wrappers that operate as though they are standard types but are actually intermediates
for translating those operations into Fabric manipulation.

For example instead of retrieving arrays as raw ``float*`` or ``std::vector<float>`` they will be retrieved as the
wrapper class ``ogn::array`` or ``ogn::const_array``. This allows all the same manipulations as a ``std::vector<float>``
including iteration for use in the STL algorithm library, through Fabric interfaces.

If you are familiar with the concept of ``std::span`` an array can be thought of in the same way, where the raw data
is managed by Fabric.

Data Initialization
+++++++++++++++++++
All of this data must be initialized before the *compute()* method can be called. This is done in the constructor
of the generated database class, which is constructed by the ABI wrapper to the node's compute method.

Here's a pseudocode view of the calls that would result from a node compute request:

.. code-block:: text

    OmniGraphNode_ABI<MyNode, MyNodeDatabase>::compute(graphContext, node);
        OgnMyNodeDatabase db(graphContext, node);
            getAttributesR for all input attributes
            getAttributesW for all output attributes
            getDataR for all input attributes
            getElementCount for all input array attributes
            getDataW for all output attributes
            getElementCount for all output array attributes
        return db.validate() ? OgnMyNode::compute(db) : false;


Input Validation
----------------
The node will have specified certain conditions required in order for the compute function to be valid. In the
simplest form it will require that a set of input and output attributes exist on the node. Rather than forcing every
node writer to check these conditions before they run their algorithm this is handled by a generated *validate()*
function.

This function checks the underlying Fabric data to make sure it conforms to the conditions required by the node.

In future versions this validation will perform more sophisticated tasks, such as confirming that two sets of input
arrays have the same size, or that attribute values are within the legal ranges the node can recognize.


Python Support
--------------
Python support comes in two forms - support for C++ nodes, and ABI definition for Python node implementations.

For any node written in C++ there is a Python accessor class created that contains properties for accessing the
attribute data on the node. The evaluation context is passed to the accessor class so it can only be created when
one is available.

The accessor is geared for getting and setting values on the attributes of a node.

.. code-block:: python

    import omni.graph.core as og
    from my.extension.ogn import OgnMyNodeDatabase

    my_node = og.get_graph_by_path("/World/PushGraph").get_node("/World/PushGraph/MyNode")
    my_db = OgnMyNodeDatabase(og.ContextHelper(), my_node)

    print(f"The current value of my float attribute is {my_db.inputs.myFloat}")
    my_db.inputs.myFloat = 5.0

.. important::

    For Python node implementations the registration process is performed automatically when you load your extension by
    looking for the ``ogn/`` subdirectory of your Python import path. The generated Python class performs all of the
    underlying management tasks such as registering the ABI methods, providing forwarding for any methods you might have
    overridden, creating attribute accessors, and initializing attributes and metadata.

    In addition, all of your nodes will be automatically deregistered when the extension is disabled.
