.. _ogn_tutorial_bundle_manipulation:

Tutorial 15 - Bundle Manipulation
=================================

Attribute bundles are a construct that packages up groups of attributes into a single entity that can be passed
around the graph. Some advantages of a bundle are that they greatly simplify graph connections, only requiring a single
connection between nodes rather than dozens or even hundreds, and they do not require static definition of the data
they contain so it can change as the evaluation of the nodes dictate. The only disadvantage is that the node writer is
responsible for analyzing the contents of the bundle and deciding what to do with them.

OgnTutorialBundles.ogn
----------------------
The *ogn* file shows the implementation of a node named "omni.graph.tutorials.BundleManipulation", which has some bundles as
inputs and outputs. It's called "manipulation" as the focus of this tutorial node is on operations applied directly
to the bundle itself, as opposed to on the data on the attributes contained within the bundles. See future tutorials
for information on how to deal with that.

.. literalinclude:: ../../../../../source/extensions/omni.graph.tutorials/tutorials/tutorial15/OgnTutorialBundles.ogn
    :linenos:
    :language: json

OgnTutorialBundles.cpp
----------------------
The *cpp* file contains the implementation of the compute method. It exercises each of the available bundle
manipulation functions.

.. literalinclude:: ../../../../../source/extensions/omni.graph.tutorials/tutorials/tutorial15/OgnTutorialBundles.cpp
    :linenos:
    :language: c++

OgnTutorialBundlesPy.py
-----------------------
The *py* file duplicates the functionality in the *cpp* file, except that it is implemented in Python.

.. literalinclude:: ../../../../../source/extensions/omni.graph.tutorials/tutorials/tutorial15/OgnTutorialBundlesPy.py
    :linenos:
    :language: python

Bundle Notes
------------
Bundles are implemented in USD as "virtual primitives". That is, while regular attributes appear in a USD file
as attributes on a primitive, a bundle appears as a nested primitive with no members.

Naming Convention
-----------------
Attributes can and do contain namespaces to make them easier to work with. For example, ``outputs:operations`` is
the namespaced name for the output attribute ``operations``. However as USD does not allow colons in the names of
the primitives used for implementing attriute bundles they will be replaced by underscores, vis ``outputs_operations``.

Bundled Attribute Manipulation Methods
--------------------------------------
There are a few methods for manipulating the bundle contents, independent of the actual data inside. The actual
implementation of these methods may change over time however the usage should remain the same.

The Bundle As A Whole
+++++++++++++++++++++
.. code-block:: cpp
    :emphasize-lines: 2,5,8,11

    // The bundle attribute is extracted from the database in exactly the same way as any other attribute.
    const auto& inputBundle = db.inputs.myBundle();

    // Output and state bundles are the same, except not const
    auto& outputBundle = db.outputs.myBundle();

    // The size of a bundle is the number of attributes it contains
    auto bundleAttributeCount = inputBundle.size();

    // Full bundles can be copied using the assignment operator
    outputBundle = inputBundle;

Accessing Attributes By Name
++++++++++++++++++++++++++++
.. code-block:: cpp
    :emphasize-lines: 2,8-9

    // The attribute names should be cached somewhere as a token for fast access.
    static const NameToken normalsName = db.stringToToken("normals");

    // Then it's a call into the bundle to find an attribute with matching name.
    // Names are unique so there is at most one match, and bundled attributes do not have the usual attribute
    // namespace prefixes "inputs:", "outputs:", or "state:"
    const auto& inputBundle = db.inputs.myBundle();
    auto normals = inputBundle.attributeByName(normalsName);
    if (normals.isValid())
    {
        // If the attribute is not found in the bundle then isValid() will return false.
    }

Putting An Attribute Into A Bundle
++++++++++++++++++++++++++++++++++
.. code-block:: cpp
    :emphasize-lines: 8,10

    // Once an attribute has been extracted from a bundle a copy of it can be added to a writable bundle.
    const auto& inputBundle = db.inputs.myBundle();
    auto& outputBundle = db.outputs.myBundle();
    auto normals = inputBundle.attributeByName(normalsToken);
    if (normals.isValid())
    {
        // Clear the contents of stale data first since it will not be reused here.
        outputBundle.clear();
        // The attribute wrapper knows how to insert a copy into a bundle
        outputBundle.insertAttribute(normals);
    }

Iterating Over Attributes
+++++++++++++++++++++++++
.. code-block:: cpp
    :emphasize-lines: 3,7

    // The range-based for loop provides a method for iterating over the bundle contents.
    const auto& inputBundle = db.inputs.myBundle();
    for (const auto& bundledAttribute : inputBundle)
    {
        // Type information is available from a bundled attribute, consisting of a structure defined in
        // include/omni/graph/core/Type.h
        auto type = bundledAttribute.type();

        // The type has four pieces, the first is the basic data type...
        assert( type.baseType == BaseDataType::eFloat );

        // .. the second is the role, if any
        assert( type.role == AttributeRole::eNormal );

        // .. the third is the number of tuple components (e.g. 3 for float[3] types)
        assert( type.componentCount == 3 );

        // .. the last is the array depth, either 0 or 1
        assert( type.arrayDepth == 0 );
    }
