// This file contains snippets of example C++ code to be imported by the OGN documentation.
// It's not actual running code itself, merely sections of code that illustrate a point
// It uses the reStructuredText code import with begin/end markings to cherry-pick relevant portions.
// The sections of code being referenced are demarqued by "begin-XX" and "end-XX".

// begin-minimal
#include <OgnNoOpDatabase.h>

class OgnNoOp:
{
public:
    static bool compute(OgnNoOpDatabase& db)
    {
        // This logs a warning to the console, once
        db.logWarning("This node does nothing");

        // These methods provide direct access to the ABI objects, should you need to drop down to direct ABI calls
        // for any reason. (You should let us know if this is necessary as we've tried to minimize the cases in which
        // it becomes necessary.)
        const auto& contextObj = db.abi_context();
        const auto& nodeObj = db.abi_node();

        if (! contextObj.iAttributeData || ! nodeObj.iNode)
        {
            // This logs an error to the console and should only be used for compute failure
            db.logError("Could not retrieve the ABI interfaces");
            return false;
        }
        return true;
    }
};
REGISTER_OGN_NODE()
// end-minimal

// begin-node-metadata
#include <OgnNodeMetadataDatabase.h>
#include <alloca.h>
class OgnNodeMetadata:
{
public:
    static bool compute(OgnNodeMetadataDatabase& db)
    {
        auto nodeTypeObj = db.abi_node().getNodeTypeObj(db.abi_node());
        // Specifically defined metadata can be accessed by name
        std::cout << "The author of this node is " << nodeTypeObj.iNodeType->getMetadata(nodeTypeObj, "author") << std::endl;

        // Some metadata is automatically added; you can see it by iterating over all of the existing metadata
        size_t metadataCount = nodeTypeObj.iNodeType->getMetadataCount(nodeTypeObj);
        char const** metadataKeyBuffer = reinterpret_cast<char const**>(alloca(sizeof(char*) * metadataCount));
        char const** metadataValueBuffer = reinterpret_cast<char const**>(alloca(sizeof(char*) * metadataCount));
        size_t found = nodeTypeObj.iNodeType->getAllMetadata(nodeTypeObj, metadataKeyBuffer, metadataValueBuffer, metadataCount);
        for (size_t i=0; i<found; ++i)
        {
            std:: cout << "Metadata for " << metadataKeyBuffer[i] << " = " << metadataValueBuffer[i] << std::endl;
        }
        return true;
    }
};
REGISTER_OGN_NODE()
// end-node-metadata

// begin-node-icon
#include <OgnNodeWithIconDatabase.h>
class OgnNodeWithIcon:
{
public:
    static bool compute(OgnNodeWithIconDatabase& db)
    {
        auto nodeTypeObj = db.abi_node().getNodeTypeObj(db.abi_node());
        // The icon properties are just special cases of metadata. The metadata name is made available with the node type
        auto path = nodeTypeObj.iNodeType->getMetadata(nodeTypeObj, kOgnMetadataIconPath);
        auto color = nodeTypeObj.iNodeType->getMetadata(nodeTypeObj, kOgnMetadataIconColor);
        auto backgroundColor = nodeTypeObj.iNodeType->getMetadata(nodeTypeObj, kOgnMetadataIconBackgroundColor);
        auto borderColor = nodeTypeObj.iNodeType->getMetadata(nodeTypeObj, kOgnMetadataIconBorderColor);
        if (path)
        {
            std::cout << "Icon found at " << path << std::endl;
            if (color)
            {
                std::cout << "...color override is " << color << std::endl;
            }
            else
            {
                std::cout << "...using default color" << std::endl;
            }
            if (backgroundColor)
            {
                std::cout << "...backgroundColor override is " << backgroundColor << std::endl;
            }
            else
            {
                std::cout << "...using default backgroundColor" << std::endl;
            }
            if (borderColor)
            {
                std::cout << "...borderColor override is " << borderColor << std::endl;
            }
            else
            {
                std::cout << "...using default borderColor" << std::endl;
            }
        }
        return true;
    }
};
REGISTER_OGN_NODE()
// end-node-icon

// begin-node-scheduling
#include <OgnNodeSchedulingHintsDatabase.h>
class OgnNodeSchedulingHints:
{
public:
    static bool compute(OgnNodeSchedulingHintsDatabase& db)
    {
        auto nodeTypeObj = db.abi_node().getNodeTypeObj(db.abi_node());

        // Ordinarily you would not need to access this scheduling information as it is mainly for OmniGraph's use,
        // however it is available through the ABI so you can access it at runtime if you wish.
        auto schedulingHintsObj = nodeTypeObj.getSchedulingHints(nodeTypeObj);
        std::string safety;
        switch (schedulingHintsObj.getThreadSafety(schedulingHintsObj))
        {
            case eThreadSafety::eSafe:
                safety = "Safe";
                break;
            case eThreadSafety::eUnsafe:
                safety = "Unsafe";
                break;
            case eThreadSafety::eUnknown:
            default:
                safety = "Unknown";
                break;
        }
        std::cout << "Is this node threadsafe? " << safety << std::endl;

        return true;
    }
};
REGISTER_OGN_NODE()
// end-node-scheduling

// begin-node-singleton
#include <OgnNodeSingletonDatabase.h>
class OgnNodeSingleton:
{
public:
    static bool compute(OgnNodeSingletonDatabase& db)
    {
        auto nodeTypeObj = db.abi_node().getNodeTypeObj(db.abi_node());
        // The singleton value is just a special case of metadata. The metadata name is made available with the node type
        auto singletonValue = nodeTypeObj.iNodeType->getMetadata(nodeTypeObj, kOgnMetadataSingleton);
        if (singletonValue && singletonValue[0] != '1')
        {
            std::cout << "I am a singleton" << std::endl;
        }

        return true;
    }
};
REGISTER_OGN_NODE()
// end-node-singleton

// begin-node-categories
#include <OgnNodeCategoriesDatabase.h>
class OgnNodeCategories:
{
public:
    static bool compute(OgnNodeCategoriesDatabase& db)
    {
        auto nodeTypeObj = db.abi_node().getNodeTypeObj(db.abi_node());
        // The categories value is just a special case of metadata. The metadata name is made available with the node type
        auto categoriesValue = nodeTypeObj.iNodeType->getMetadata(nodeTypeObj, kOgnMetadataCategories);
        if (categoriesValue)
        {
            std::cout << "I have these categories:";
            char const* delimiters = ",";
            char *token = std::strtok(categoriesValue, delimiters);
            while (token)
            {
                std::cout << ' ' << std::quoted(token);
                token = std::strtok(nullptr, delimiters);
            }
            std::cout << std::endl;
        }

        return true;
    }
};
REGISTER_OGN_NODE()
// end-node-categories

// begin-node-tags
#include <OgnNodeTagsDatabase.h>
class OgnNodeTags:
{
public:
    static bool compute(OgnNodeTagsDatabase& db)
    {
        auto nodeTypeObj = db.abi_node().getNodeTypeObj(db.abi_node());
        // The tags value is just a special case of metadata. The metadata name is made available with the node type
        std::cout << "Tagged as " << nodeTypeObj.iNodeType->getMetadata(nodeTypeObj, kOgnMetadataTags) << std::endl;

        return true;
    }
};
REGISTER_OGN_NODE()
// end-node-tags

// begin-tokens
#include <OgnTokensDatabase.h>
class OgnTokens:
{
public:
    static bool compute(OgnTokensDatabase& db)
    {
        // Tokens are members of the database, by name. When the dictionary-style definition is used in the .ogn
        // file the names are the dictionary keys. These members were created, of type omni::graph::core::NameToken:
        //   db.tokens.red
        //   db.tokens.green
        //   db.tokens.blue

        // As tokens are just IDs in order to print their values you have to use the utility method "tokenToString"
        // on the database to convert them to strings
        std::cout << "The name for red is " << db.tokenToString(db.tokens.red) << std::endl;
        std::cout << "The name for green is " << db.tokenToString(db.tokens.green) << std::endl;
        std::cout << "The name for blue is " << db.tokenToString(db.tokens.blue) << std::endl;

        // This confirms that tokens are uniquely assigned and the same for all types.
        auto redToken = db.stringToToken("red");
        if (redToken != db.tokens.red)
        {
            std::cout << "ERROR: red token is not consistent" << std::endl;
            return false;
        }

        return true;
    }
};
REGISTER_OGN_NODE()
// end-tokens

// begin-node-uiName
#include <OgnNodeUiNameDatabase.h>
class OgnNodeUiName:
{
public:
    static bool compute(OgnNodeUiNameDatabase& db)
    {
        // The uiName value is just a special case of metadata. The special metadata name is available from the ABI
        auto nodeTypeObj = db.abi_node().getNodeTypeObj(db.abi_node());
        std::cout << "Call me " << nodeTypeObj.iNodeType->getMetadata(nodeTypeObj, kOgnMetadataUiName) << std::endl;

        return true;
    }
};
REGISTER_OGN_NODE()
// end-node-uiName

// begin-simple
#include <OgnTokenStringLengthDatabase.h>
class OgnTokenStringLength:
{
public:
    static bool compute(OgnTokenStringLengthDatabase& db)
    {
        // Access pattern is "db", the database, "inputs", the attribute's access type, and "token", the name the
        // attribute was given in the .ogn file
        // Inputs should always be const-references, outputs and state access types can be just plain references.
        const auto& tokenToMeasure = db.inputs.token();
        // The database has typedefs for all attributes, which can be used if you prefer explicit types
        db.outputs.length_t& stringLength = db.outputs.length();

        // Use the utility to convert the token to a string first
        std::string tokenString{ db.tokenToString(tokenToMeasure) };

        // Simple assignment to the output attribute's accessor is all you need to do to set the value, as it is
        // pointing directly to that data.
        stringLength = tokenString.length();

        return true;
    }
};
REGISTER_OGN_NODE()
// end-simple

// begin-tuple
#include <OgnVectorMultiplyDatabase.h>
class OgnVectorMultiply:
{
public:
    static bool compute(OgnVectorMultiplyDatabase& db)
    {
        const auto& vector1 = db.inputs.vector1();
        const auto& vector2 = db.inputs.vector2();
        auto& product = db.outputs.product();
        // Use the functionality in the GfVec classes to perform the multiplication.
        // You can use your own library functions by casting to appropriate types first.
        for (int row=0; row<4; ++row)
        {
            product.SetRow(row, vector1 * vector2[row]);
        }

        return true;
    }
};
REGISTER_OGN_NODE()
// end-tuple

// begin-role
#include <OgnPointsToVectorDatabase.h>
// Being explicit about the namespaced types you are using is a good balance between namespace pollution and long names
using omni::graph::core::AttributeRole;
class OgnPointsToVector:
{
public:
    static bool compute(OgnPointsToVectorDatabase& db)
    {
        // Validate the roles for the computation. They aren't used in the actual math, though they could be used
        // to do something useful (e.g. cast data with a "color" role to a class that handles color spaces)
        if (db.inputs.point1.role() != AttributeRole::ePosition)
        {
            db.logError("The attribute point1 does not have the point role");
            return false;
        }
        if (db.inputs.point2.role() != AttributeRole::ePosition)
        {
            db.logError("The attribute point2 does not have the point role");
            return false;
        }
        if (db.outputs.vector.role() != AttributeRole::eVector)
        {
            db.logError("The attribute vector does not have the vector role");
            return false;
        }

        // The GfVec3f type supports simple subtraction for vector calculation
        db.outputs.vector() = db.inputs.point2() - db.inputs.point2();

        return true;
    }
};
REGISTER_OGN_NODE()
// end-role

// begin-array
#include <OgnPartialSumsDatabase.h>
#include <numeric>
class OgnPartialSums:
{
public:
    static bool compute(OgnPartialSumsDatabase& db)
    {
        const auto& inputArray = db.inputs.array();
        auto& outputArray = db.outputs.partialSums();

        // This is a critical step, setting the size of the output array. Without this the array has no memory in
        // which to write.
        //
        // For convenience the size() and resize() methods are available at the database level and the wrapper level,
        // with the latter method only available for writable attributes. This makes initializing an output with the
        // same size as an input a one-liner in either of these two forms:
        //       db.outputs.partialSum.resize( db.inputs.array.size() );
        outputArray.resize(inputArray.size());

        // The wrapper to arrays behaves like a std::span<>, where the external memory they manage comes from Fabric.
        // The wrapper handles the synchronization, and is set up to behave mostly like
        // a normal std::array.
        //
        //     for (const auto& inputValue : inputArray)
        //     inputArray[index]
        //     inputArray.at(index)
        //     inputArray.empty()
        //     rawData = inputArray.data()

        // Since the standard range-based for-loop and general iteration is supported you can make use of the wide
        // variety of STL algorithms as well.
        //
        std::partial_sum(inputArray.begin(), inputArray.end(), outputArray.begin());

        return true;
    }
};
REGISTER_OGN_NODE()
// end-array

// begin-tuple-array
#include <OgnCrossProductDatabase.h>
#include <algorithm>
class OgnCrossProduct:
{
public:
    static bool compute(OgnCrossProductDatabase& db)
    {
        // It usually keeps your code cleaner if you put your attribute wrappers into local variables, avoiding
        // the constant use of the "db.inputs" or "db.outputs" namespaces.
        const auto& a = db.inputs.a();
        const auto& b = db.inputs.b();
        auto& crossProduct = db.outputs.crossProduct();

        // This node chooses to make mismatched array lengths an error. You could also make it a warning, or just
        // simply calculate the result for the minimum number of available values.
        if (a.size() != b.size())
        {
            db.logError("Input array lengths do not match - '%zu' vs. '%zu'", a.size(), b.size());
            return false;
        }

        // As with simple arrays, the size of the output tuple-array must be set first to allocate Fabric memory
        crossProduct.resize(a.size());

        // Edge case is easily handled
        if (a.size() == 0)
        {
            return true;
        }

        // Simple cross product - your math library may have a built-in one you can use
        auto crossProduct = [](const GfVec3d& a, const GfVec3d& b) -> GfVec3d {
            GfVec3d result;
            result[0] = a[1] * b[2] - a[2] * b[1];
            result[1] = -(a[0] * b[2] - a[2] * b[0]);
            result[2] = a[0] * b[1] - a[1] * b[0];
            return result;
        };

        // STL support makes walking the parallel arrays and computing a breeze
        std::transform(a.begin(), a.end(), b.begin(), crossProduct.begin(), crossProduct);

        return true;
    }
};
REGISTER_OGN_NODE()
// end-tuple-array

// begin-string
#include <OgnReverseStringDatabase.h>
class OgnReverseString:
{
public:
    static bool compute(OgnReverseStringDatabase& db)
    {
        // The attribute wrapper for string types provides a conversion to a std::string with pre-allocated memory.
        // Constructing a new string based on that gives you a copy that can be modified locally and assigned after.
        std::string result(db.inputs.original());

        // Other functions provided by the string wrapper include:
        //    size() - how many characters in the string?
        //    empty() - is the string empty?
        //    data() - the raw char* pointer - note that it is not necessarily null-terminated
        //    comparison operators for sorting
        //    iterators
        // For writable strings (output and state) there are also modification functions:
        //    resize() - required before writing, unless you use an assigment operator
        //    clear() - empty out the string
        //    assignment operators for char const*, std::string, and string wrappers

        // The local copy can be reversed in place.
        std::reverse(std::begin(result), std::end(result));
        // The assignment operator handles resizing the string as well
        db.outputs.result() = result;

        // Since in this case the length of the output string is known up front the extra copy can be avoided by
        // making use of the iterator feature.
        //    auto& resultString = db.outputs.result();
        //    resultString.resize(db.inputs.original.size());
        //    std::reverse(std::begin(resultString), std::end(resultString));

        return true;
    }
};
REGISTER_OGN_NODE()
// end-string

// begin-any
#include <OgnAddDatabase.h>
class OgnAdd:
{
public:
    static bool compute(OgnAddDatabase& db)
    {
        // The extra work in handling extended types such as "any" or "union" is in checking the resolved types to
        // first ensure they are handled and second select appropriate code paths if they differ from one type to
        // another.

        // Rather than clutter up the C++ code with a lot of type checking to follow the matrix of code paths
        // required to handle all types this example will just perform addition on floats or doubles and report
        // an error if any other type is encountered. (In practice this type of node is better implemented in Python
        // where such flexible types are easily handled.)

        const auto& a = db.inputs.a();
        const auto& b = db.inputs.b();
        auto& sum = db.outputs.sum();

        // Unlike the normal type, a resolved type must be retrieved at runtime. Extra functionality is provided on
        // the extended attribute type wrappers for this purpose.
        //
        //      resolved() - Does the attribute currently have a resolved type?
        //      type() - omni::graph::core::Type information describing the resolved type
        //      typeName() - String identifying the type()
        //      get<X>() - Templated function to attempt conversion to the resolved type X
        //
        if (! a.resolved() || ! b.resolved())
        {
            db.logWarning("Cannot compute with an input type unresolved")
            return false;
        }

        // For now the output type must be resolved by the graph as well, to ensure consistency.
        // In future the type UNKNOWN may be acceptable for outputs, and the compute method can provide the
        // type resolution itself.
        if (! sum.resolved())
        {
            db.logWarning("Cannot compute with the output type unresolved")
            return false;
        }

        // There are two ways of access the resolved type data. The first is explicit, where you check the type
        // and then explicitly access the data, knowing it will correctly resolve. In this case the add will be
        // really fussy and not even allow float+double, so the resolved types need to match exactly.
        if ((a.type() != b.type()) || (a.type() != sum.type()))
        {
            db.logError("Can only add identical types - float or double. Got '%s'+'%s'='%s'",
                         a.typeName().c_str(), b.typeName().c_str(), sum.typeName().c_str());
            return false;
        }

        // The second way is the Pythonic "ask forgiveness, not permission" method, where the get<X>() method is used
        // to attempt to cast to a specific type and it moves on if it fails. This is possible because the get<X>()
        // methods do not return direct references to the Fabric data, they return a wrapper around the data that
        // provides the same types of access that the normal attribute type wrappers provide, plus a cast operator to
        // a bool so that you can quickly check if a cast to a resolved type succeeded.
        //
        // Note the use of the single "=" assignment to keep the code cleaner.
        // Also not the return type is just "auto", not "auto&" or "const auto&". That is because the wrapper must be
        // created at runtime so it cannot be cached. The wrapper types are simple so copying is fast.
        //
        if (auto aValue = a.get<float>())
        {
            // We've guaranteed above that the resolved types are the same so their retrieval can be assumed to succeed
            auto sumValue = sum.get<float>();
            auto bValue = b.get<float>();
            // The tricky part here is that the actual value still has to be retrieved from the wrapper, and that is
            // done by dereferencing it.
            *sumValue = *aValue + *bValue;
            // The type of "sum" is a wrapper class, the type of "*sum" (or "sum->") is a float&
        }
        else if (auto aValue = a.get<double>())
        {
            // You can shorten the operation if you're careful about the placement of the dereferences
            *(sum.get<double>()) = *aValue + *(b.get<double>());
        }
        else
        {
            // If you want a complete node then you'd continue this cascading type check, even including the
            // array and tuple types, such as get<double[]>(), get<double[3]>() and get<double[][3]>(). The types
            // supported by the get<X>() method are limited to raw data types, not explicit attribute types.
            // For example if you wanted a colord[3] attribute you'd have to do a two-step check for both underlying
            // type and attribute role like this:
            //     if ((auto colorValue = a.get<double[3]>()) && (a.role() == AttributeRole::eColor))

            db.logError("Input type '%s' not currently supported by the Add node", a.typeName().c_str());
            return false;
        }

        return true;
    }
};
REGISTER_OGN_NODE()
// end-any

// begin-union
#include <OgnMultiplyNumbersDatabase.h>
class OgnMultiplyNumbers:
{
public:
    static bool compute(OgnMultiplyNumbersDatabase& db)
    {
        // Full details on handling extended types can be seen in the example for the "any" type. This example
        // shows only the necessary parts to handle the two types accepted for this union type (float and double).
        // The underlying code is all the same, the main difference is in the fact that the graph only allows
        // resolving to types explicitly mentioned in the union, rather than any type at all.

        const auto& a = db.inputs.a();
        const auto& b = db.inputs.b();
        auto& product = db.outputs.product();

        bool handledType{ false };
        if (auto aValue = a.get<float>())
        {
            if (auto bValue = b.get<float>())
            {
                if (auto productValue = product.get<float>())
                {
                    handledType = true;
                    *productValue = *aValue * *bValue;
                }
            }
        }
        else if (auto aValue = a.get<double>())
        {
            if (auto bValue = b.get<double>())
            {
                if (auto productValue = product.get<double>())
                {
                    handledType = true;
                    *productValue = *aValue * *bValue;
                }
            }
        }

        if (! handledType)
        {
            db.logError("Types were not resolved ('%s'*'%s'='%s')",
                        a.typeName().c_str(), b.typeName().c_str(), product.typeName().c_str());
            return false;
        }

        return true;
    }
};
REGISTER_OGN_NODE()
// end-union

// begin-compute-helpers
#include <OgnAddDatabase.h>
#include <omni/graph/core/ogn/ComputeHelpers.h>
#include <carb/logging/Log.h>

namespace omni {
namespace graph {
namespace nodes {
// unnamed namespace to avoid multiple declaration when linking
namespace {
template<typename T>
bool tryComputeAssumingType(OgnAddDatabase& db)
{
    auto functor = [](auto const& a, auto const& b, auto& result)
    {
        result = a + b;
    };
    return ogn::compute::tryComputeWithArrayBroadcasting<T>(db.inputs.a(), db.inputs.b(), db.outputs.sum(), functor);
}
template<typename T, size_t N>
bool tryComputeAssumingType(OgnAddDatabase& db)
{
    auto functor = [](auto const& a, auto const& b, auto& result)
    {
        result[i] = a[i] + b[i];
    };
    return ogn::compute::tryComputeWithTupleBroadcasting<T, N>(db.inputs.a(), db.inputs.b(), db.outputs.sum(), functor);
}
} // namespace

class OgnAdd
{
public:
    static bool compute(OgnAddDatabase& db)
    {
        try
        {
            // All possible types excluding ogn::string and bool
            // scalers
            auto& aType = db.inputs.a().type();
            switch (aType.baseType)
            {
                case BaseDataType::eDouble:
                    switch (aType.componentCount)
                    {
                        case 1:  return tryComputeAssumingType<double>(db);
                        case 2:  return tryComputeAssumingType<double, 2>(db);
                        case 3:  return tryComputeAssumingType<double, 3>(db);
                        case 4:  return tryComputeAssumingType<double, 4>(db);
                        case 9:  return tryComputeAssumingType<double, 9>(db);
                        case 16: return tryComputeAssumingType<double, 16>(db);
                    }
                case BaseDataType::eFloat:
                    switch (aType.componentCount)
                    {
                        case 1:  return tryComputeAssumingType<float>(db);
                        case 2:  return tryComputeAssumingType<float, 2>(db);
                        case 3:  return tryComputeAssumingType<float, 3>(db);
                        case 4:  return tryComputeAssumingType<float, 4>(db);
                    }
                case BaseDataType::eHalf:
                    switch (aType.componentCount)
                    {
                        case 1:  return tryComputeAssumingType<pxr::GfHalf>(db);
                        case 2:  return tryComputeAssumingType<pxr::GfHalf, 2>(db);
                        case 3:  return tryComputeAssumingType<pxr::GfHalf, 3>(db);
                        case 4:  return tryComputeAssumingType<pxr::GfHalf, 4>(db);
                    }
                case BaseDataType::eInt:
                    switch (aType.componentCount)
                    {
                        case 1:  return tryComputeAssumingType<int32_t>(db);
                        case 2:  return tryComputeAssumingType<int32_t, 2>(db);
                        case 3:  return tryComputeAssumingType<int32_t, 3>(db);
                        case 4:  return tryComputeAssumingType<int32_t, 4>(db);
                    }
                case BaseDataType::eInt64:
                    return tryComputeAssumingType<int64_t>(db);
                case BaseDataType::eUChar:
                    return tryComputeAssumingType<unsigned char>(db);
                case BaseDataType::eUInt:
                    return tryComputeAssumingType<uint32_t>(db);
                case BaseDataType::eUInt64:
                    return tryComputeAssumingType<uint64_t>(db);
            }
        }
        catch (ogn::compute::InputError &error)
        {
            db.logWarning(error.what());
        }
        return false;
    }

    static void onConnectionTypeResolve(const NodeObj& node){
        auto a = node.iNode->getAttributeByToken(node, inputs::a.token())
        auto b = node.iNode->getAttributeByToken(node, inputs::b.token())
        auto sum = node.iNode->getAttributeByToken(node, outputs::sum.token())

        auto aType = a.iAttribute->getResolvedType(a);
        auto bType = b.iAttribute->getResolvedType(b);

        // Require inputs to be resolved before determining sum's type
        if (aType.baseType != BaseDataType::eUnknown && bType.baseType != BaseDataType::eUnknown)
        {
            std::array<AttributeObj, 3> attrs { a, b, sum };
            // a, b, and sum should all have the same tuple count
            std::array<uint8_t, 3> tupleCounts {
                aType.componentCount,
                bType.componentCount,
                std::max(aType.componentCount, bType.componentCount)
            };
            std::array<uint8_t, 3> arrayDepths {
                aType.arrayDepth,
                bType.arrayDepth,
                // Allow for a mix of singular and array inputs. If any input is an array, the output must be an array
                std::max(aType.arrayDepth, bType.arrayDepth)
            };
            std::array<AttributeRole, 3> rolesBuf {
                aType.role,
                bType.role,
                // Copy the attribute role from the resolved type to the output type
                AttributeRole::eUnknown
            };
            node.iNode->resolvePartiallyCoupledAttributes(node, attrs.data(), tupleCounts.data(),
                                                        arrayDepths.data(), rolesBuf.data(), attrs.size());
        }
    }
};

REGISTER_OGN_NODE()

} // namespace nodes
} // namespace graph
} // namespace omni
// end-compute-helpers

// begin-bundle
#include <OgnMergeBundlesDatabase.h>
class OgnMergeBundle:
{
public:
    static bool compute(OgnMergeBundlesDatabase& db)
    {
        const auto& bundleA = db.inputs.bundleA();
        const auto& bundleB = db.inputs.bundleB();
        auto& mergedBundle = db.outputs.bundle();

        // Bundle assignment means "assign all of the members of the RHS bundle to the LHS bundle". It doesn't
        // do a deep copy of the bundle members.
        mergedBundle = bundleA;

        // Bundle insertion adds the contents of a bundle to an existing bundle. The bundles may not have members
        // with the same names
        mergedBundle.insertBundle( bundleB );

        return true;
    }
};
REGISTER_OGN_NODE()
// end-bundle

// begin-bundle-data
#include <OgnCalculateBrightnessDatabase.h>
class OgnCalculateBrightness:
{
public:
    // The actual algorithm to run using a well-defined conversion
    static float brightnessFromRGB(float r, float g, float b)
    {
        return (r * (299.f) + (g * 587.f) + (b * 114.f)) / 256.f;
    }
    static bool compute(OgnCalculateBrightnessDatabase& db)
    {
        // Retrieve the bundle accessor
        const auto& color = db.inputs.color();
        // Using the bundle accessor, try to retrieve the RGB color members. In this case the types have to be
        // float, though in a more general purpose node you might also allow for double, half, and int types.
        const auto r = color.attributeByName(db.tokens.r).get<float>();
        const auto g = color.attributeByName(db.tokens.g).get<float>();
        const auto b = color.attributeByName(db.tokens.b).get<float>();
        // Validity of a member is a boolean
        if (r && g && b)
        {
            db.outputs.brightness() = brightnessFromRGB(r, g, b);
            return true;
        }
        // Having failed to extract RGB members, do the same check for CMYK members
        const auto c = color.attributeByName(db.tokens.c).get<float>();
        const auto m = color.attributeByName(db.tokens.m).get<float>();
        const auto y = color.attributeByName(db.tokens.y).get<float>();
        const auto k = color.attributeByName(db.tokens.k).get<float>();
        if (c && m && y && k)
        {
            db.outputs.brightness() = brightnessFromRGB(
                (1.f - c/100.f) * (1.f - k/100.f),
                (1.f - m/100.f) * (1.f - k/100.f),
                (1.f - y/100.f) * (1.f - k/100.f) );
            return true;
        }

        // You could be more verbose about the reason for the problem as there are a few different scenarios:
        //    - some but not all of r,g,b or c,m,y,k were in the bundle
        //    - none of the color components were in the bundle
        //    - some or all of the color components were found but were of the wrong data type
        db.logError("Neither the groups (r, g, b) nor (c, m, y, k) are in the color bundle. Cannot compute brightness");
        return false;
    }
};
REGISTER_OGN_NODE()
// end-bundle-data

// begin-memory-type
#include <OgnMemoryTypeDatabase.h>
class OgnMemoryType:
{
public:
    static bool compute(OgnMemoryTypeDatabase& db)
    {
        // The operation specifies moving the points data onto the GPU for further computation if the size of
        // the input data reaches a threshold where that will make the computation more efficient.
        // (This particular node just moves data; in practice you would perform an expensive calculation on it.)
        if (db.inputs.points.size() > db.inputs.sizeThreshold())
        {
            // The gpu() methods force the data onto the GPU. They may or may not perform CPU->GPU copies under the
            // covers. Fabric handles all of those details so that you don't have to.
            db.outputs.points.gpu() = db.inputs.points.gpu();
        }
        else
        {
            // The gpu() methods force the data onto the CPU. They may or may not perform GPU->CPU copies under the
            // covers. Fabric handles all of those details so that you don't have to.
            db.outputs.points.cpu() = db.inputs.points.cpu();
        }
        return true;
    }
};
REGISTER_OGN_NODE()
// end-memory-type

// begin-cuda-pointers
#include <OgnCudaPointersDatabase.h>
extern "C" callCudaFunction(inputs::cudaPoints_t, outputs::cudaPoints_t);
class OgnCudaPointers:
{
public:
    static bool compute(OgnCudaPointersDatabase& db)
    {
        // When the *cudaPointers* keyword is set to *cpu* this wrapped array will contain a CPU pointer that
        // references the GPU array data. If not, it would have contained a GPU pointer that references the GPU
        // array data and not been able to be dereferenced on the CPU side.
        callCudaFunction(db.inputs.cudaPoints(), db.outputs.cudaPoints());
        return true;
    }
};
REGISTER_OGN_NODE()
// end-cuda-pointers


// begin-attribute-metadata
#include <OgnStarWarsCharactersDatabase.h>
#include <alloca.h>
class OgnStarWarsCharacters:
{
public:
    static bool compute(OgnStarWarsCharactersDatabase& db)
    {
        auto nodeTypeObj = db.abi_node().getNodeTypeObj(db.abi_node());
        auto anakinObj = db.abi_node().getAttribute(db.abi_node(), inputs::anakin.token());
        // Specifically defined metadata can be accessed by name
        std::cout << "Anakin's secret is " << anakinObj->getMetadata(anakinObj, "secret") << std::endl;

        // Some metadata is automatically added; you can see it by iterating over all of the existing metadata
        size_t metadataCount = anakinObj->getMetadataCount(anakinObj);
        char const** metadataKeyBuffer = reinterpret_cast<char const**>(alloca(sizeof(char*) * metadataCount));
        char const** metadataValueBuffer = reinterpret_cast<char const**>(alloca(sizeof(char*) * metadataCount));
        size_t found = anakinObj->getAllMetadata(anakinObj, metadataKeyBuffer, metadataValueBuffer, metadataCount);
        for (size_t i=0; i<found; ++i)
        {
            std:: cout << "Metadata for " << metadataKeyBuffer[i] << " = " << metadataValueBuffer[i] << std::endl;
        }
        return true;
    }
};
REGISTER_OGN_NODE()
// end-attribute-metadata

// begin-optional
#include <OgnShoesDatabase.h>
#include <string>
#include <stdlib.h>
class OgnShoes:
{
public:
    static bool compute(OgnShoesDatabase& db)
    {
        static std::string[] _shoeTypes{ "Runners", "Slippers", "Oxfords" };
        auto shoeIndex = rand() % 3;
        std::string shoeTypeName{ _shoeTypes[shoeIndex] };

        // If the shoe is a type that has laces then append the lace type name
        if (shoeIndex != 1)
        {
            // As this is an optional value it may or may not be valid at this point.
            const auto& shoelaceStyle = db.inputs.shoelaceStyle();
            // This happens automatically with required attributes. With optional ones it has to be done when used.
            if (db.inputs.shoeLaceStyle.isValid())
            {
                shoeTypeName += " with ";
                shoeTypeName += db.tokenToString(shoelaceStyle);
                shoeTypeName += " laces";
            }
        }
        db.outputs.shoeType() = shoeTypeName;
        return true;
    }
};
REGISTER_OGN_NODE()
// end-optional

// begin-attribute-uiName
#include <OgnAttributeUiNameDatabase.h>
class OgnAttributeUiName:
{
public:
    static bool compute(OgnAttributeUiNameDatabase& db)
    {
        // The uiName value is just a special case of metadata.
        // Note the use of the namespace-enabled "inputs" value that provides access to an attribute's static name.
        auto attributeObj = db.abi_node().iNode->getAttribute(db.abi_node(), inputs::x.token());
        std::cout << "Call me " << attributeObj.iAttribute->getMetadata(attributeObj, kOgnMetadataUiName) << std::endl;

        return true;
    }
};
REGISTER_OGN_NODE()
// end-attribute-uiName

// begin-attribute-uiType
#include <OgnAttributeUiTypeDatabase.h>
class OgnAttributeUiType:
{
public:
    static bool compute(OgnAttributeUiTypeDatabase& db)
    {
        // The uiType value is just a special case of metadata.
        auto attributeObj = db.abi_node().iNode->getAttribute(db.abi_node(), inputs::x.token());
        std::cout << "The property panel ui type is " << attributeObj.iAttribute->getMetadata(attributeObj, kOgnMetadataUiType) << std::endl;

        return true;
    }
};
REGISTER_OGN_NODE()
// end-attribute-uiType

// begin-unvalidated
#include <OgnShoesDatabase.h>
#include <string>
#include <stdlib.h>
class OgnABTest:
{
public:
    static bool compute(OgnABTestDatabase& db)
    {
        auto choice = db.outputs.choice();
        auto outType = choice.type();
        // Check to see which input is selected and verify that its data type matches the output resolved type
        if (db.inputs.selectA())
        {
            const auto inputA = db.inputs.a();
            if (! inputA.isValid() || (inputA.type() != outType))
            {
                db.logError("Mismatched types at input a - '%s' versus '%s'", inputA.type().getOgnTypeName(), outType.getOgnTypeName());
                return false;
            }
            choice = inputA;
        }
        else
        {
            const auto inputF = db.inputs.b();
            if (! inputB.isValid() || (inputB.type() != outType))
            {
                db.logError("Mismatched types at input b - '%s' versus '%s'", inputB.type().getOgnTypeName(), outType.getOgnTypeName());
                return false;
            }
            choice = inputB;
        }
        return true;
    }
};
REGISTER_OGN_NODE()
// end-unvalidated

// begin-state-node
#include <OgnCounterDatabase.h>
class OgnCounter:
{
    // Simple state information that counts how many times this node has been evaluated
    int m_evaluationCount{ 0 };
public:
    static bool compute(OgnCounterDatabase& db)
    {
        // The state information is on the node so it is the template parameter
        auto& state = db.internalState<OgnCounter>();
        // This prints the message and updates the state information
        std::cout << "This node has been evaluated " << state.m_evaluationCount++ << " times" << std::endl;
        return true;
    }
};
REGISTER_OGN_NODE()
// end-state-node

// begin-versioned-node
#include <OgnMultiplyDatabase.h>
class OgnMultiply:
{
public:
    static bool compute(OgnMultiplyDatabase& db)
    {
        db.outputs.result() = db.inputs.a() * db.inputs.b() + db.inputs.offset();
        return true;
    }
    // Simply declaring the function is enough to register it as an override to the normal ABI function
    static bool updateNodeVersion(const GraphContextObj&, const NodeObj& nodeObj, int oldVersion, int newVersion)
    {
        if ((oldVersion == 1) && (newVersion == 2))
        {
            // Version upgrade manually adds the new attribute to the node.
            constexpr float zero = 0.0f;
            nodeObj.iNode->createAttribute(nodeObj, "inputs:offset", Type(BaseDataType::eFloat), &zero, nullptr, kAttributePortType_Input, kExtendedAttributeType_Regular, nullptr);
            return true;
        }
        // Always good practice to flag unknown version changes so that they are not forgotten
        db.logError("Do not know how to upgrade Multiply from version %d to %d", oldVersion, newVersion);
        return false;
    }
};
REGISTER_OGN_NODE()
// end-versioned-node
