//
// Copyright 2025 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://openusd.org/license.
//
#ifndef PXR_EXEC_EXEC_COMPUTATION_BUILDERS_H
#define PXR_EXEC_EXEC_COMPUTATION_BUILDERS_H

/// \file
///
/// This is a public header, but many of the symbols have private names because
/// they are not intended for direct use by client code. The public API here is
/// accessed by client code via the 'self' parameter generated by the
/// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA macro. The documentation is set up to
/// highlight all relevant public details.
///

#include "pxr/pxr.h"

#include "pxr/exec/exec/api.h"
#include "pxr/exec/exec/builtinComputations.h"
#include "pxr/exec/exec/providerResolution.h"
#include "pxr/exec/exec/typeRegistry.h"
#include "pxr/exec/exec/types.h"

#include "pxr/base/tf/token.h"
#include "pxr/base/tf/type.h"
#include "pxr/base/vt/traits.h"
#include "pxr/base/vt/value.h"
#include "pxr/exec/vdf/context.h"
#include "pxr/exec/vdf/traits.h"
#include "pxr/usd/sdf/path.h"

#include <memory>
#include <string>
#include <type_traits>
#include <utility>

PXR_NAMESPACE_OPEN_SCOPE

struct Exec_InputKey;


/// \defgroup group_Exec_ComputationDefinitionLanguage Computation Definition Language
///
/// Plugin computations are defined using the domain-specific **Computation
/// Definition Language**.
///
/// Each plugin computation is registered for a particular schema, either typed
/// or applied. When a computation is requested on a provider prim or attribute,
/// if the requested computation name is not a [builtin
/// computation](#group_Exec_Builtin_Computations) name, exec compilation
/// considers the computations registered for the typed schema for the prim, the
/// ancestor schema types, and API schemas applied to the prim, and looks for a
/// computation of the requested name.
///
/// To define computations for a schema, the plugin code must invoke the
/// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA() macro. The macro invocation is
/// immediately followed by a block of code that uses [computation
/// registrations](#group_Exec_ComputationRegistrations) that registers the
/// associated plugin computations. Most of the language is dedicated to
/// expressing [input registrations](#group_Exec_InputRegistrations), which
/// provide exec compilation with the information it needs to compile the input
/// connections that supply input values when the network is evaluated.
///
/// # Example
///
/// The following cpp file could be used in a plugin library to define the
/// `computeMyAttributeValue` prim computation for the `MySchemaType` schema:
///
/// ```{.cpp}
/// #include "pxr/exec/exec/registerSchema.h"
/// #include "pxr/exec/vdf/context.h"
///
/// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
/// {
///     // Register a prim computation that returns the computed value of an
///     // attribute.
///     self.PrimComputation(_tokens->computeMyAttributeValue)
///         .Callback<double>(+[](const VdfContext &ctx) {
///             ctx->SetOutput(ctx.GetInputValue<double>(_tokens->myAttribute));
///         })
///         .Inputs(
///             AttributeValue<double>(_tokens->myAttribute).Required());
/// }
/// ```
///
/// The library's `plugInfo.json` must contain the following data in the `Info`
/// block in order for the execution system to load the library when
/// computations are requested on a prim that uses `MySchemaType`:
///
/// ```
///     "Info": {
///         "Exec": {
///             "Schemas": {
///                 "MyComputationalSchema1": {
///                     "allowsPluginComputations": true
///                 },
///                 "MyComputationalSchema2": {
///                 },
///                 "MyNonComputationalSchema": {
///                     "allowsPluginComputations": false
///                 }
///             }
///         }
///     }
/// ```
///
/// The boolean `allowsPluginComputations` is used to declare schemas for which
/// computations _cannot_ be registered. If `allowsPluginComputations` isn't
/// present in the plugInfo, its value defaults to true. I.e., schemas that
/// appear in the Exec/Schemas plugInfo allow plugin computations by default.

/// \defgroup group_Exec_ComputationRegistrations Computation Registrations
///
/// Computation registrations initiate the process of defining computations. The
/// object returned by a computation registration has methods that are used to
/// specify the callback that implements the computation and the inputs that are
/// provided to the callback at evaluation time.
/// 
/// \ingroup group_Exec_ComputationDefinitionLanguage

/// \defgroup group_Exec_InputRegistrations Input Registrations
///
/// An **input registration** is a specification of how an input value will be
/// provided to a computation callback at evaluation time.
///
/// An input registration is a sequence of:
/// - zero or more [object accessors](#group_Exec_Accessors), which provide
///   access to one or more scene objects that act as computation providers, and
///   which _must_ be followed by:
/// - exactly one [value specifier](#group_Exec_ValueSpecifiers), which request
///   a value from the computation provider(s), and which _may_ be followed
///   by:
/// - zero or more [input options](#group_Exec_InputOptions), which modify the
///   behavior of the resulting input registration.
/// 
/// For convenience, certain object accessor/value specifier/input option
/// sequences may be replaced by an [alias](#group_Exec_Aliases), which
/// compactly represents a compound input registration.
///
/// \ingroup group_Exec_ComputationDefinitionLanguage

/// \defgroup group_Exec_Accessors Object Accessors
///
/// **Object accessors** provide access to computation providers, the scene
/// objects from which input values are requested. An [input
/// registration](#group_Exec_InputRegistrations) starts with a sequence of zero
/// or more accessors. If no accessor is present, the origin object, the object
/// that owns the consuming computation, is the provider. Otherwise, starting
/// from that object, the sequence of accessors describes hops through namespace
/// that end at the computation provider.
///
/// A sequence of object accessors does not fully specify an input, however. The
/// sequence _must_ be followed by exactly one [value
/// specifier](#group_Exec_ValueSpecifiers) to fully specify an input
/// registration.
///
/// \ingroup group_Exec_InputRegistrations

/// \defgroup group_Exec_ValueSpecifiers Value Specifiers
/// 
/// A **value specifier** is an element of an [input
/// registration](#group_Exec_InputRegistrations) that identifies the value that
/// is requested from a given computation provider.
///
/// Each computation input registration must contain exactly one value
/// specifier. A value specifier comes after a sequence of zero or more [object
/// accessors](#group_Exec_Accessors), which determine the provider. A value
/// specifier may be followed by one or more [input
/// options](#group_Exec_InputOptions).
///
/// \ingroup group_Exec_InputRegistrations

/// \defgroup group_Exec_InputOptions Input Options
///
/// An **input option** is an element of an [input
/// registration](#group_Exec_InputRegistrations) that applies to a [value
/// specifier](#group_Exec_ValueSpecifiers), modifying its behavior.
///
/// A value specifier may be followed by zero or more input options.
///
/// \ingroup group_Exec_InputRegistrations

/// \defgroup group_Exec_Aliases Aliases
///
/// Aliases are compact representations of compound [input
/// registrations](#group_Exec_InputRegistrations), combining one or more
/// [object accessors](#group_Exec_Accessors) with a [value
/// specifier](#group_Exec_ValueSpecifiers) into a single input registration.
///
/// \ingroup group_Exec_InputRegistrations


/// An enum that is used as a template parameter to specify which kinds of
/// providers a given input registration is allowed to be used on.
/// 
enum class Exec_ComputationBuilderProviderTypes: unsigned char
{
    Prim = 1 << 0,
    Attribute = 1 << 1,
    Any = 0xff
};

constexpr bool operator&(
    const Exec_ComputationBuilderProviderTypes a,
    const Exec_ComputationBuilderProviderTypes b)
{
    return static_cast<unsigned char>(a) & static_cast<unsigned char>(b);
}

template <Exec_ComputationBuilderProviderTypes allowed>
struct Exec_ComputationBuilderComputationValueSpecifier;

// Common base class for value specifiers and object accessors.
class Exec_ComputationBuilderCommonBase
{
protected:
    // Returns a value specifier for computing a metadata value.
    template <Exec_ComputationBuilderProviderTypes allowed>
    EXEC_API
    static Exec_ComputationBuilderComputationValueSpecifier<allowed>
    _GetMetadataValueSpecifier(
        const TfType resultType,
        const SdfPath &localTraversal,
        const TfToken &metadataKey);
};

// Untemplated value specifier base class.
//
// This class builds up an Exec_InputKey that specifies how to source an input
// value at exec compilation time.
//
class Exec_ComputationBuilderValueSpecifierBase
    : public Exec_ComputationBuilderCommonBase
{
public:
    EXEC_API
    Exec_ComputationBuilderValueSpecifierBase(
        const TfToken &computationName,
        TfType resultType,
        ExecProviderResolution &&providerResolution,
        const TfToken &inputName,
        const TfToken &disambiguatingId);

    EXEC_API
    Exec_ComputationBuilderValueSpecifierBase(
        const Exec_ComputationBuilderValueSpecifierBase&);

    EXEC_API
    ~Exec_ComputationBuilderValueSpecifierBase();

protected:
    EXEC_API
    void _SetInputName(const TfToken &inputName);

    EXEC_API
    void _SetOptional (const bool optional);

    EXEC_API
    void _SetFallsBackToDispatched(bool fallsBackToDispatched);

private:
    // Only computation builders can get the input key.
    friend class Exec_ComputationBuilderBase;

    EXEC_API
    void _GetInputKey(Exec_InputKey *inputKey) const;

private:
    // We PIMPL the data for this class to avoid exposing more private details
    // in this public header.
    struct _Data;
    const std::unique_ptr<_Data> _data;
};

// A value specifier that requests a constant value, valid on a prim or
// attribute computation
//
struct Exec_ComputationBuilderConstantValueSpecifier final
    : public Exec_ComputationBuilderValueSpecifierBase
{
    static constexpr Exec_ComputationBuilderProviderTypes allowedProviders =
        Exec_ComputationBuilderProviderTypes::Any;

    EXEC_API
    Exec_ComputationBuilderConstantValueSpecifier(
        const TfType resultType,
        const SdfPath &localTraversal,
        const TfToken &inputName,
        VtValue &&constantValue);
};

// A value specifier that requests the value of a computation.
//
// The template parameter determines which types of providers the input
// registration is allowed to be used on.
// 
template <Exec_ComputationBuilderProviderTypes allowed>
struct Exec_ComputationBuilderComputationValueSpecifier
    : public Exec_ComputationBuilderValueSpecifierBase
{
    Exec_ComputationBuilderComputationValueSpecifier(
        const TfToken &computationName,
        const TfType resultType,
        ExecProviderResolution &&providerResolution,
        const TfToken &disambiguatingId = TfToken())
        : Exec_ComputationBuilderValueSpecifierBase(
            computationName, resultType,
            std::move(providerResolution),
            computationName /* inputName */,
            disambiguatingId)
    {
    }
    
    using This = Exec_ComputationBuilderComputationValueSpecifier<allowed>;

    static constexpr Exec_ComputationBuilderProviderTypes allowedProviders =
        allowed;

    /// \addtogroup group_Exec_InputOptions
    /// @{

    /// Overrides the default input name, setting it to \p inputName.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a prim computation that returns the value of another
    ///     // prim computation, using a non-default input name.
    ///     self.PrimComputation(_tokens->myComputation)
    ///         .Callback(+[](const VdfContext &ctx) -> double {
    ///             const double *const valuePtr =
    ///                 ctx.GetInputValuePtr<double>(_tokens->myInputName);
    ///             return valuePtr ? *valuePtr : 0.0;
    ///         })
    ///         .Inputs(
    ///             Computation<double>(_tokens->anotherComputation)
    ///                 .InputName(_tokens->myInputName));
    /// }
    /// ```
    ///
    This&
    InputName(const TfToken &inputName)
    { 
        _SetInputName(inputName);
        return *this;
    }

    /// Declares the input is required, i.e., that the computation expects an
    /// input value always to be provided at evaluation time.
    ///
    /// If exec compilation is unable to compile input connections for a
    /// required input, an error will be emitted.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a prim computation that returns the value of another
    ///     // prim computation, using a non-default input name.
    ///     self.PrimComputation(_tokens->myComputation)
    ///         .Callback(+[](const VdfContext &ctx) -> int {
    ///             return ctx.GetInputValue<int>(_tokens->myInputName);
    ///         })
    ///         .Inputs(
    ///             Computation<int>(_tokens->anotherComputation).Required());
    /// }
    /// ```
    ///
    This&
    Required()
    {
        _SetOptional(false);
        return *this;
    }

    /// Declares the input can find dispatched computations *if* the requested
    /// computation name doesn't match a local computation on the provider.
    ///
    /// \sa
    /// [DispatchedPrimComputation](#ExecComputationBuilder::DispatchedPrimComputation)
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a dispatched prim computation.
    ///     self.DispatchedPrimComputation(_tokens->myDispatchedComputation)
    ///         .Callback(+[](const VdfContext &) -> double { return 11.0; })
    ///
    ///     // Register a prim computation that requests the above dispatched
    ///     // computation via uses relationship targets.
    ///     self.PrimComputation(_tokens->myComputation)
    ///         .Callback(+[](const VdfContext &ctx) -> double {
    ///             const double *const valuePtr =
    ///                 ctx.GetInputValuePtr<double>(
    ///                     _tokens->myDispatchedComputation);
    ///             return valuePtr ? *valuePtr : -1.0;
    ///         })
    ///         .Inputs(
    ///             Relationship(_tokens->myRelationship)
    ///                 .TargetedObjects<double>(
    ///                     _tokens->myDispatchedComputation)
    ///                 .FallsBackToDispatched())
    /// }
    /// ```
    ///
    This&
    FallsBackToDispatched()
    {
        _SetFallsBackToDispatched(true);
        return *this;
    }

    /// @}
};


// Untemplated object accessor base class.
struct Exec_ComputationBuilderAccessorBase
    : public Exec_ComputationBuilderCommonBase
{

    Exec_ComputationBuilderAccessorBase(const SdfPath &localTraversal)
        : _localTraversal(localTraversal)
    {
    }

protected:
    const SdfPath &_GetLocalTraversal() const {
        return _localTraversal;
    }

private:
    // The relative path used for the first phase of provider resolution.
    SdfPath _localTraversal;
};

// Untemplated base class for accessors used to provide constant values as
// computation inputs.
//
struct Exec_ComputationBuilderConstantAccessorBase
    : public Exec_ComputationBuilderAccessorBase
{
    // We specialize the InputName() accessor because it is required for
    // constant values. I.e., Constant() returns an accessor, and the
    // InputName() option must be used to generate a value specifier.
    //
    Exec_ComputationBuilderConstantValueSpecifier
    InputName(const TfToken &inputName) &&
    { 
        return Exec_ComputationBuilderConstantValueSpecifier(
            _valueType,
            _GetLocalTraversal(),
            inputName,
            std::move(_constantValue));
    }

protected:
    EXEC_API
    Exec_ComputationBuilderConstantAccessorBase(
        VtValue &&constantValue,
        TfType valueType);

private:
    VtValue _constantValue;
    const TfType _valueType;
};

/// Accessor common to all scene object types that support requesting
/// computations on the object.
///
/// This class is templated in order to classify accessors that are allowed as
/// inputs for prim computations vs attribute computations.
///
template <Exec_ComputationBuilderProviderTypes allowed>
struct Exec_ComputationBuilderAccessor
    : public Exec_ComputationBuilderAccessorBase
{
    Exec_ComputationBuilderAccessor(const SdfPath &localTraversal)
        : Exec_ComputationBuilderAccessorBase(localTraversal)
    {
    }

    using ValueSpecifier =
        Exec_ComputationBuilderComputationValueSpecifier<allowed>;

    /// \addtogroup group_Exec_ValueSpecifiers
    /// @{

    /// See [Computation()](#exec_registration::Computation::Computation)
    template <typename ResultType>
    ValueSpecifier
    Computation(const TfToken &computationName)
    {
        static_assert(!VtIsArray<ResultType>::value,
                      "VtArray is not a supported result type.");

        return ValueSpecifier(
            computationName,
            ExecTypeRegistry::GetInstance().CheckForRegistration<ResultType>(),
            {_GetLocalTraversal(),
             ExecProviderResolution::DynamicTraversal::Local});
    }

    /// See [IncomingConnections()](#exec_registration::IncomingConnections)
    template <typename ResultType>
    ValueSpecifier
    IncomingConnections(const TfToken &computationName)
    {
        return ValueSpecifier(
            computationName,
            ExecTypeRegistry::GetInstance().CheckForRegistration<ResultType>(),
            {Exec_ComputationBuilderAccessorBase::_GetLocalTraversal(),
             ExecProviderResolution::DynamicTraversal::
                 IncomingConnectionOwningAttributes});
    }

    /// See [Metadata()](#exec_registration::Metadata)
    template <typename ResultType>
    ValueSpecifier
    Metadata(const TfToken &metadataKey)
    {
        static_assert(!VtIsArray<ResultType>::value,
                      "VtArray is not a supported result type.");

        return _GetMetadataValueSpecifier<allowed>(
            ExecTypeRegistry::GetInstance().CheckForRegistration<ResultType>(),
            _GetLocalTraversal(),
            metadataKey);
    }

    /// @} // Value specifiers
};

/// Property accessor
template <Exec_ComputationBuilderProviderTypes allowed>
struct Exec_ComputationBuilderPropertyAccessor
    : public Exec_ComputationBuilderAccessor<allowed>
{
    Exec_ComputationBuilderPropertyAccessor(const SdfPath &localTraversal)
        : Exec_ComputationBuilderAccessor<allowed>(localTraversal)
    {
    }
};

/// Attribute accessor
template <Exec_ComputationBuilderProviderTypes allowed>
struct Exec_ComputationBuilderAttributeAccessor
    : public Exec_ComputationBuilderPropertyAccessor<allowed>
{
    Exec_ComputationBuilderAttributeAccessor(const SdfPath &localTraversal)
    : Exec_ComputationBuilderPropertyAccessor<allowed>(localTraversal)
    {
    }

    using ValueSpecifier =
        Exec_ComputationBuilderComputationValueSpecifier<allowed>;

    /// \addtogroup group_Exec_ValueSpecifiers
    /// @{

    /// See [Connections()](#exec_registration::Connections)
    template <typename ResultType>
    ValueSpecifier
    Connections(const TfToken &computationName)
    {
        return ValueSpecifier(
            computationName,
            ExecTypeRegistry::GetInstance().CheckForRegistration<ResultType>(),
            {Exec_ComputationBuilderAccessorBase::_GetLocalTraversal(),
             ExecProviderResolution::DynamicTraversal::
                 ConnectionTargetedObjects});
    }

    /// @}

    // XXX:TODO
    // Accessors for AnimSpline, etc.
};

/// Relationship accessor
template <Exec_ComputationBuilderProviderTypes allowed>
struct Exec_ComputationBuilderRelationshipAccessor
    : public Exec_ComputationBuilderPropertyAccessor<allowed>
{
    Exec_ComputationBuilderRelationshipAccessor(const SdfPath &localTraversal)
    : Exec_ComputationBuilderPropertyAccessor<allowed>(localTraversal)
    {
    }

    using ValueSpecifier =
        Exec_ComputationBuilderComputationValueSpecifier<allowed>;

    /// \addtogroup group_Exec_ValueSpecifiers
    /// @{

    /// After a [Relationship()](#exec_registration::Relationship::Relationship)
    /// accessor, requests input values from the computation \p computationName
    /// of type \p ResultType on the objects targeted by the relationship.
    ///
    /// Relationship forwarding is applied, so if the relationship targets
    /// another relationship, the targets are transitively expanded, resulting
    /// in the ultimately targeted, non-relationship objects.
    ///
    /// The default input name is \p computationName; use `InputName` to specify
    /// a different input name.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a prim computation that looks for the computation
    ///     // 'sourceComputation' on all targeted objects of the relationship
    ///     // 'myRel' and returns the number of matching targets.
    ///     self.PrimComputation(_tokens->myComputation)
    ///         .Callback(+[](const VdfContext &ctx) -> int {
    ///             VdfReadIterator<int> it(_tokens->sourceComputation);
    ///             return static_cast<int>(it.ComputeSize());
    ///         })
    ///         .Inputs(
    ///             Relationship(_tokens->myRel)
    ///             .TargetedObjects<int>(_tokens->sourceComputation));
    /// }
    /// ```
    ///
    template <typename ResultType>
    ValueSpecifier
    TargetedObjects(const TfToken &computationName)
    {
        return ValueSpecifier(
            computationName,
            ExecTypeRegistry::GetInstance().CheckForRegistration<ResultType>(),
            {Exec_ComputationBuilderAccessorBase::_GetLocalTraversal(),
             ExecProviderResolution::DynamicTraversal::
                 RelationshipTargetedObjects});
    }

    /// @}
};


// The following registrations are in the exec_registration namespace so that
// the registration macro can make them available (without the namespace) as
// arguments to registrations methods (i.e., Inputs()).
namespace exec_registration {


/// Attribute accessor, valid for providing input to a prim computation.
struct Attribute final
    : public Exec_ComputationBuilderAttributeAccessor<
        Exec_ComputationBuilderProviderTypes::Prim>
{
    /// \addtogroup group_Exec_Accessors
    /// @{

    /// On a prim computation, provides access to the attribute named
    /// \p attributeName.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a prim computation that returns the value of an
    ///     // attribute owned by the prim.
    ///     self.PrimComputation(_tokens->myComputation)
    ///         .Callback(+[](const VdfContext &ctx) -> double {
    ///             return ctx.GetInputValue<double>(
    ///                 ExecBuiltinComputations->computeValue));
    ///         })
    ///         .Inputs(
    ///             Attribute(_tokens->doubleAttribute)
    ///                 .Computation<double>(
    ///                     ExecBuiltinComputations->computeValue).Required());
    /// }
    /// ```
    ///
    Attribute(const TfToken &attributeName)
        : Exec_ComputationBuilderAttributeAccessor<
            Exec_ComputationBuilderProviderTypes::Prim>(
                SdfPath::ReflexiveRelativePath().AppendProperty(attributeName))
    {
    }

    /// @} // Accessors
};


/// Relationship accessor, valid for providing input to a prim computation.
struct Relationship final
    : public Exec_ComputationBuilderRelationshipAccessor<
        Exec_ComputationBuilderProviderTypes::Prim>
{
    /// \addtogroup group_Exec_Accessors
    /// @{

    /// On a prim computation, provides access to the relationship named
    /// \p relationshipName.
    ///
    /// \sa
    /// [TargetedObjects()](#Exec_ComputationBuilderRelationshipAccessor::TargetedObjects)
    ///
    Relationship(const TfToken &relationshipName)
        : Exec_ComputationBuilderRelationshipAccessor<
            Exec_ComputationBuilderProviderTypes::Prim>(
                SdfPath::ReflexiveRelativePath().AppendProperty(
                    relationshipName))
    {
    }

    /// @} // Accessors
};


/// Prim accessor, valid for providing input to an attribute computation.
struct Prim final
    : public Exec_ComputationBuilderAccessor<
        Exec_ComputationBuilderProviderTypes::Attribute>
{
    /// \addtogroup group_Exec_Accessors
    /// @{

    /// On an attribute computation, provides access to the owning prim.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register an attribute computation on 'attr' that yields the
    ///     // value of a sibling attribute 'otherAttr'.
    ///     self.AttributeComputation(
    ///         _tokens->attr,
    ///         _tokens->myComputation)
    ///         .Callback(+[](const VdfContext &ctx) -> int {
    ///             return ctx.GetInputValue<int>(
    ///                 ExecBuiltinComputations->computeValue));
    ///         })
    ///         .Inputs(
    ///             Prim().AttributeValue<int>(_tokens->otherAttr).Required());
    /// }
    /// ```
    ///
    Prim()
        : Exec_ComputationBuilderAccessor<
            Exec_ComputationBuilderProviderTypes::Attribute>(
                SdfPath(".."))
    {
    }

    /// See [Attribute()](#Attribute::Attribute)
    Exec_ComputationBuilderAttributeAccessor<
        Exec_ComputationBuilderProviderTypes::Attribute>
    Attribute(const TfToken &attributeName)
    {
        return Exec_ComputationBuilderAttributeAccessor<
            Exec_ComputationBuilderProviderTypes::Attribute>(
                SdfPath("..").AppendProperty(attributeName));
    }

    /// See [Relationship()](#Relationship::Relationship)
    Exec_ComputationBuilderRelationshipAccessor<
        Exec_ComputationBuilderProviderTypes::Attribute>
    Relationship(const TfToken &relationshipName)
    {
        return Exec_ComputationBuilderRelationshipAccessor<
            Exec_ComputationBuilderProviderTypes::Attribute>(
                SdfPath("..").AppendProperty(relationshipName));
    }

    /// @} // Accessors

    /// \addtogroup group_Exec_Aliases
    /// @{

    /// See [AttributeValue()](#exec_registration::AttributeValue)
    template <typename ValueType>
    auto
    AttributeValue(const TfToken &attributeName)
    {
        return Attribute(attributeName)
            .Computation<ValueType>(ExecBuiltinComputations->computeValue)
            .InputName(attributeName);
    }

    /// @} // Aliases
};


/// Provides access to the stage, valid for providing input to any computation.
struct Stage final
    : public Exec_ComputationBuilderAccessor<
        Exec_ComputationBuilderProviderTypes::Any>
{
    /// \addtogroup group_Exec_Accessors
    /// @{

    /// On any computation, provides access to the stage. This accessor can be
    /// used to access stage-level builtin computations.
    ///
    /// > **Note:**
    /// > The Stage() accessor must be the sole accessor in any input
    /// > registration in which it appears.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a prim computation that returns the current time.
    ///     self.PrimComputation(_tokens->myComputation)
    ///         .Callback(+[](const VdfContext &ctx) -> EfTime {
    ///             return ctx.GetInputValue<EfTime>(
    ///                 ExecBuiltinComputations->computeTime));
    ///         })
    ///         .Inputs(
    ///             Stage().Computation<EfTime>(
    ///                 ExecBuiltinComputations->computeTime).Required());
    /// }
    /// ```
    ///
    Stage()
        : Exec_ComputationBuilderAccessor<
            Exec_ComputationBuilderProviderTypes::Any>(
                SdfPath::AbsoluteRootPath())
    {
    }

    /// @} // Accessors
};

// XXX:TODO
// Property, NamespaceParent, NamespaceChildren, etc.


/// Computation value specifier, valid for providing input to any computation.
template <typename ResultType>
struct Computation final
    : public Exec_ComputationBuilderComputationValueSpecifier<
        Exec_ComputationBuilderProviderTypes::Any>
{
    /// \addtogroup group_Exec_ValueSpecifiers
    /// @{

    /// Requests an input value from the computation \p computationName of type
    /// \p ResultType.
    ///
    /// The default input name is \p computationName; use `InputName` to specify
    /// a different input name.
    /// 
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a prim computation that returns the value of another
    ///     // prim computation.
    ///     self.PrimComputation(_tokens->myComputation)
    ///         .Callback(+[](const VdfContext &ctx) -> double {
    ///             const double *const valuePtr =
    ///                 ctx.GetInputValuePtr<double>(_tokens->sourceComputation);
    ///             return valuePtr ? *valuePtr : 0.0;
    ///         })
    ///         .Inputs(
    ///             Computation<double>(_tokens->sourceComputation)
    /// }
    /// ```
    ///
    Computation(const TfToken &computationName)
        : Exec_ComputationBuilderComputationValueSpecifier<
            Exec_ComputationBuilderProviderTypes::Any>(
                computationName, 
                ExecTypeRegistry::GetInstance().
                    CheckForRegistration<ResultType>(),
                {SdfPath::ReflexiveRelativePath(),
                 ExecProviderResolution::DynamicTraversal::Local})
    {
    }

    /// @}
};

/// Metadata value specifier, valid on a prim or attribute computation
template <typename ValueType>
struct Metadata final
    : public Exec_ComputationBuilderComputationValueSpecifier<
        Exec_ComputationBuilderProviderTypes::Any>
{
    /// \addtogroup group_Exec_ValueSpecifiers
    /// @{

    /// Requests an input value from the metadata field indicated by \p
    /// metadataKey, of type \p ResultType.
    ///
    /// The default input name is \p metadataKey; use InputName to specify a
    /// different input name.
    /// 
    /// # Example
    /// 
    /// ```{.cpp}
    /// self.PrimComputation(_tokens->computeDocMetadata)
    ///     .Callback(+[](const VdfContext &ctx) -> std::string {
    ///          return ctx.GetInputValue<std::string>(
    ///              SdfFieldKeys->Documentation);
    ///     })
    ///     .Inputs(
    ///         Metadata<std::string>(SdfFieldKeys->Documentation).Required()
    ///     );
    /// ```
    ///
    Metadata(const TfToken &metadataKey)
        : Exec_ComputationBuilderComputationValueSpecifier<
            Exec_ComputationBuilderProviderTypes::Any>(
                _GetMetadataValueSpecifier<allowedProviders>(
                    ExecTypeRegistry::GetInstance()
                        .CheckForRegistration<ValueType>(),
                    SdfPath::ReflexiveRelativePath(),
                    metadataKey))
    {
        static_assert(!VtIsArray<ValueType>::value,
                      "VtArray is not a supported result type.");

        InputName(metadataKey);
    }

    /// @}
};

// Constant accessor
template <typename ValueType>
struct Constant final
    : public Exec_ComputationBuilderConstantAccessorBase
{
    /// \addtogroup group_Exec_ValueSpecifiers
    /// @{

    /// Requests a constant input value of type \p ValueType.
    ///
    /// \note
    /// No default input name is assigned. `Constant(value)` *must* be
    /// followed by `.InputName(name)`.
    /// 
    /// This kind of input isn't necessarily useful when used with a
    /// self-contained computation definition. But it becomes useful for more
    /// complicated registrations, where one piece of code registers a callback
    /// that configures its evaluation-time behavior based on an input value and
    /// a separate piece of code registers a constant input that selects the
    /// desired behavior.
    /// 
    /// This can happen:
    /// - When computation definitions are assembled programatically by
    ///   parameterized registration code that is called to register various
    ///   versions of a computation, possibly for multiple schemas
    /// - When computation definitions are composed from registrations made for
    ///   different schemas on the same prim (support for composed computation
    ///   definitions is still TBD in OpenExec)
    /// - When computation registration is configured (also TBD), allowing
    ///   registrations for a single schema to be dynamic, depending on metadata
    ///   values that drive the configuration process
    ///
    /// # Value Types
    ///
    /// All computation input value types, including value types used to provide
    /// constant inputs, must be known to the execution system. All types that
    /// can be used to author attribute and metadata values in USD are known to
    /// exec by default. User-defined types must be registered by calling
    /// ExecTypeRegistry::RegisterType.
    ///
    /// \note
    /// Types that are used for constant inputs must be hashable (see
    /// VtIsHashable()).
    /// 
    /// # Simple Example
    ///
    /// This simple example shows the mechanics of using a constant input,
    /// without being suggestive of how it might be useful.
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a prim computation that returns the value of its
    ///     // constant input.
    ///     self.PrimComputation(_tokens->myComputation)
    ///         .Callback(+[](const VdfContext &ctx) -> double {
    ///             return ctx.GetInputValue<double>(_tokens->myConstant);
    ///         })
    ///         .Inputs(
    ///             Constant(42.0).InputName(_tokens->myConstant));
    /// }
    /// ```
    ///
    /// # Complex Example
    ///
    /// This example demonstrate how more complicated registration code might
    /// make use of constant inputs to configure the behavior of a callback at
    /// evaluation time.
    ///
    /// ```{.cpp}
    /// template <typename RegistrationType>
    /// void RegisterCallback(RegistrationType &reg)
    /// {
    ///     reg.Callback(+[](const VdfContext &ctx) -> std::string {
    ///         const TfToken &mode = ctx.GetInputValue<TfToken>(_tokens->mode);
    ///         if (mode == _tokens->mode1) {
    ///             return "Mode 1 selected";
    ///         else if (mode == _tokens->mode2) {
    ///             return "Mode 2 selected";
    ///         }
    /// }
    ///
    /// template <typename RegistrationType>
    /// void RegisterInput(RegistrationType &reg, const int mode)
    /// {
    ///     if (mode == 1) {
    ///         reg.Inputs(Constant(_tokens->mode1).InputName(_tokens->mode));
    ///     } else if (mode == 2) {
    ///         reg.Inputs(Constant(_tokens->mode2).InputName(_tokens->mode));
    ///     }
    /// }
    ///
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     auto reg = self.PrimComputation(_tokens->myComputation);
    /// 
    ///     // ...
    /// 
    ///     RegisterCallback(reg);
    /// 
    ///     // ...
    /// 
    ///     RegisterInput(reg, mode);
    /// }
    /// ```
    ///
    Constant(
        ValueType &&constantValue)
        : Exec_ComputationBuilderConstantAccessorBase(
            VtValue(std::move(constantValue)),
            ExecTypeRegistry::GetInstance().
                CheckForRegistration<ValueType>())
    {
        static_assert(
            !std::is_same_v<std::decay_t<ValueType>, char*> &&
            !std::is_same_v<std::decay_t<ValueType>, const char*>,
            "Must use std::string to represent string literal types.");
        static_assert(
            VtIsHashable<ValueType>(),
            "Types used to provide constant input values must be hashable.");
    }

    Constant(
        const ValueType &constantValue)
        : Exec_ComputationBuilderConstantAccessorBase(
            VtValue(constantValue),
            ExecTypeRegistry::GetInstance().
                CheckForRegistration<ValueType>())
    {
        static_assert(
            !std::is_same_v<std::decay_t<ValueType>, char*> &&
            !std::is_same_v<std::decay_t<ValueType>, const char*>,
            "Must use std::string to represent string literal types.");
        static_assert(
            VtIsHashable<ValueType>(),
            "Types used to provide constant input values must be hashable.");
    }

    /// @}
};

// Deduction guides that ensure std::string is the value type used to store
// character string literals.
Constant(const char *) -> Constant<std::string>;
Constant(char *) -> Constant<std::string>;


// XXX:TODO
// This should be implemented as an alias for an accessor that takes a predicate
// plus .Compute(), but that requires implementing predicates plus having a way
// to express the computation name and result type as computation parameters.
// Therefore, for now, this is implemented as a value specifier.
template <typename ResultType>
struct NamespaceAncestor final
    : public Exec_ComputationBuilderComputationValueSpecifier<
        Exec_ComputationBuilderProviderTypes::Prim>
{
    /// \addtogroup group_Exec_ValueSpecifiers
    /// @{

    /// On a prim computation, requests an input value from the computation
    /// \p computationName of type \p ResultType on the nearest namespace
    /// ancestor prim.
    ///
    /// The default input name is \p computationName; use `InputName` to specify
    /// a different input name.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a prim computation that finds the nearest namespace
    ///     // ancestor that defines a computation 'sourceComputation' with
    ///     // an `int` result type. If found, the result is the value of the
    ///     // ancestor compuation; otherwise, returns 0.
    ///     self.PrimComputation(_tokens->myComputation)
    ///         .Callback(+[](const VdfContext &ctx) -> int {
    ///             const int *const valuePtr =
    ///                 ctx.GetInputValuePtr<int>(_tokens->sourceComputation);
    ///             return valuePtr ? *valuePtr : 0;
    ///         })
    ///         .Inputs(
    ///             NamespaceAncestor<int>(_tokens->sourceComputation));
    /// }
    /// ```
    ///
    NamespaceAncestor(const TfToken &computationName)
        : Exec_ComputationBuilderComputationValueSpecifier<
            Exec_ComputationBuilderProviderTypes::Prim>(
                computationName,
                ExecTypeRegistry::GetInstance().
                    CheckForRegistration<ResultType>(),
                {SdfPath::ReflexiveRelativePath(),
                 ExecProviderResolution::DynamicTraversal::NamespaceAncestor})
    {
    }

    /// @}
};

// XXX:TODO
// AnimSpline


/// \addtogroup group_Exec_Aliases
/// @{

// Note:
// Aliases are implemented as generator functions, rather than as structs,
// because that way they can simply be expressed as registrations.

/// Input alias that yields the value of the named attribute.
/// 
/// This registration must follow a
/// [PrimComputation](#ExecComputationBuilder::PrimComputation) registration or
/// a function that returns a prim computation registration.
///
/// > **Note:**  
/// > ```{.cpp}
/// > AttributeValue<T>(attrToken)
/// > ```
/// >
/// > is equivalent to:
/// >
/// > ```{.cpp}
/// > Attribute(attrToken)
/// >     .Compute<T>(ExecBuiltinComputations->computeValue)
/// >     .InputName(attrToken)
/// > ```
///
/// # Example
///
/// ```{.cpp}
/// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
/// {
///     // Register a prim computation that returns the computed value of an
///     // attribute.
///     self.PrimComputation(_tokens->eleven)
///         .Callback<double>(+[](const VdfContext &ctx) {
///             ctx->SetOutput(ctx.GetInputValue<double>(_tokens->myAttribute));
///         })
///         .Inputs(
///             AttributeValue<double>(_tokens->myAttribute).Required());
/// }
/// ```
///
template <typename ValueType>
auto
AttributeValue(const TfToken &attributeName)
{
    return Attribute(attributeName)
        .Computation<ValueType>(ExecBuiltinComputations->computeValue)
        .InputName(attributeName);
}

/// @} // Aliases

/// \addtogroup group_Exec_ValueSpecifiers
/// @{

/// As a direct input to an attribute computation or after an
/// [Attribute()](#exec_registration::Attribute::Attribute) accessor, requests
/// input values from the computation \p computationName of type \p ResultType
/// on the objects targeted by the attribute's connections.
///
/// \note
/// Conceptually, this input registration provides access to the owning
/// attribute's connections, but as outlined in the paragraph above, in practice
/// it requests the named computation from the objects that are targeted by
/// those connections. The reason we choose "Connections" as the name, rather
/// than "ConnectionTargetedObjects," is to allow for future expansion of USD to
/// allow for value-transforming behaviors on attribute connections themselves.
///
/// The default input name is \p computationName; use `InputName` to specify a
/// different input name.
///
/// # Example
///
/// ```{.cpp}
/// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
/// {
///     // Register an attribute computation on the attribute 'myAttr' that
///     // sums the results of the integer values flowing over myAttr's
///     // connections from the objects targeted by those connections.
///     self.AttributeComputation(_tokens->myAttr, _tokens->computeSum)
///         .Callback(+[](const VdfContext &ctx) -> int {
///             VdfReadIteratorRange<int> range(
///                 ctx, ExecBuiltinComputations->computeValue);
///             return std::accumulate(range.begin(), range.end(), 0);
///         })
///         .Inputs(
///             Connections<int>(ExecBuiltinComputations->computeValue));
/// }
/// ```
///
template <typename ResultType>
auto
Connections(const TfToken &computationName)
{
    return Exec_ComputationBuilderComputationValueSpecifier<
        Exec_ComputationBuilderProviderTypes::Attribute>(
            computationName,
            ExecTypeRegistry::GetInstance().CheckForRegistration<ResultType>(),
            {SdfPath::ReflexiveRelativePath(),
             ExecProviderResolution::DynamicTraversal::
                 ConnectionTargetedObjects});
}

/// On any provider, requests input values from the computation \p
/// computationName of type \p ResultType on the attributes that own any
/// attribute connections that target the provider object.
///
/// When this input parameter produces multiple input values, there is no
/// deterministic ordering.
///
/// \note
/// Conceptually, this input registration provides access to the connections
/// that target the provider, but as outlined in the paragraph above, in
/// practice it requests the named computation from the attributes that own
/// those connections. The reason we choose "IncomingConnections" as the name,
/// rather than "IncomingConnectionOwningAttributes," is to allow for future
/// expansion of USD to allow for value-transforming behaviors on attribute
/// connections themselves.
///
/// The default input name is \p computationName; use `InputName` to specify a
/// different input name.
///
/// # Example
///
/// ```{.cpp}
/// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
/// {
///     // Register a prim computation that sums the values of the
///     // integer-valued attributes that own connections that target the
///     // provider prim.
///     self.PrimComputation(_tokens->computeSum)
///         .Callback(+[](const VdfContext &ctx) -> int {
///             VdfReadIteratorRange<int> range(
///                 ctx, ExecBuiltinComputations->computeValue);
///             return std::accumulate(range.begin(), range.end(), 0);
///         })
///         .Inputs(
///             IncomingConnections<int>(ExecBuiltinComputations->computeValue));
/// }
/// ```
///
template <typename ResultType>
auto
IncomingConnections(const TfToken &computationName)
{
    return Exec_ComputationBuilderComputationValueSpecifier<
        Exec_ComputationBuilderProviderTypes::Any>(
            computationName,
            ExecTypeRegistry::GetInstance().CheckForRegistration<ResultType>(),
            {SdfPath::ReflexiveRelativePath(),
             ExecProviderResolution::DynamicTraversal::
                 IncomingConnectionOwningAttributes});
}

/// @} // Value Specifiers

} // namespace exec_registration


// We forward declare these classes so the generated documentation for
// PrimComputation(), AttributeComputation(), and AttributeExpression() comes
// before the Callback() and Inputs() docs.
// 
class ExecPrimComputationBuilder;
class ExecAttributeComputationBuilder;
class ExecAttributeExpressionBuilder;

/// The top-level builder object (aka, the `self` variable generated by
/// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA()).
/// 
class ExecComputationBuilder
{
    EXEC_API
    ExecComputationBuilder(TfType schemaType);

public:
    EXEC_API
    ~ExecComputationBuilder();

    // Allows access to the constructor.
    //
    // Only schema computation registration functions should create computation
    // builders.
    struct ConstructionAccess {
        EXEC_API
        static ExecComputationBuilder
        Construct(const std::string &schemaTypeName);
    };

    /// \addtogroup group_Exec_ComputationRegistrations
    ///  @{

    /// Registers a prim computation named \p computationName.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a trivial prim computation.
    ///     self.PrimComputation(_tokens->eleven)
    ///         .Callback(+[](const VdfContext &) -> double { return 11.0; })
    /// }
    /// ```
    ///
    EXEC_API
    ExecPrimComputationBuilder 
    PrimComputation(const TfToken &computationName);

    /// Registers an attribute computation named \p computationName on
    /// attributes named \p attributeName.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a trivial attribute computation.
    ///     self.AttributeComputation(
    ///         _tokens->attr,   // attributeName
    ///         _tokens->eleven) // computationName
    ///         .Callback(+[](const VdfContext &) -> double { return 11.0; })
    /// }
    /// ```
    ///
    EXEC_API
    ExecAttributeComputationBuilder 
    AttributeComputation(
        const TfToken &attributeName,
        const TfToken &computationName);

    /// Registers an attribute expression for attributes named \p attributeName.
    ///
    /// All attributes have a *computed value* that can be consumed by
    /// computation inputs, either by explicitly requesting the built-in
    /// computation [computeValue](#Exec_BuiltinComputationTokens::computeValue)
    /// or by using [AttributeValue](#exec_registration::AttributeValue).
    /// The *attribute expression* allows plugin-writers to customize this
    /// computed value. If no attribute expression is defined, then
    /// [computeValue](#Exec_BuiltinComputationTokens::computeValue) simply
    /// provides the resolved value of the attribute.
    ///
    /// When defining an attribute expression, it is often desired that it
    /// consume the attribute's resolved value. The expression can obtain the
    /// resolved value by registering an input from the computation
    /// [computeResolvedValue](#Exec_BuiltinComputationTokens::computeResolvedValue)
    /// on the provider attribute.
    ///
    /// \note
    /// The attribute expression may produce a different type from the attribute
    /// on which it has been registered. Though allowed, this can lead to
    /// confusion, and this may become restricted in the future.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register an attribute expression for the string-valued attribute
    ///     // 'myString', such that its computed value is its resolved value
    ///     // in upper-case.
    ///     self.AttributeExpression(_tokens->myString)
    ///         .Inputs(
    ///             Computation<std::string>(
    ///                 ExecBuiltinComputations->computeResolvedValue))
    ///         .Callback(+[](const VdfContext &ctx) -> std::string {
    ///             return TfStringToUpper(ctx.GetInputValue<std::string>(
    ///                 ExecBuiltinComputations->computeResolvedValue));
    ///         });
    /// }
    /// ```
    ///
    EXEC_API
    ExecAttributeExpressionBuilder
    AttributeExpression(const TfToken &attributeName);

    /// Registers a dispatched prim computation named \p computationName.
    ///
    /// A dispatched prim computation is only visible to computations on the
    /// prim that does the dispatching. I.e., the computation registrations for
    /// a schema can include dispatched computations and inputs to computations
    /// registered on the same schema can request the dispatched computations,
    /// using the input option FallsBackToDispatched(), from *other provider
    /// prims* and find them there. *Other schema computation registrations*
    /// will not be able to find the dispatched computations, however.
    ///
    /// Dispatched computations can be restricted as to which prims they can
    /// dispatch onto, based on the typed and applied schemas of a given target
    /// prim. The second parameter to the DispatchedPrimComputation registration
    /// function can be used to specify zero or more schema types (as
    /// TfType%s). If any types are given, the dispatched computation will only
    /// be found on a target prim if that prim's typed schema type (or one of
    /// its base type) is among the given schema types or if the fully expanded
    /// list of API schemas applied to the prim includes a schema that is among
    /// the given schema types.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a dispatched prim computation that can be found on
    ///     // scopes.
    ///     const TfType scopeType = TfType::FindByName("UsdGeomScope");
    ///     self.DispatchedPrimComputation(_tokens->eleven, scopeType)
    ///         .Callback(+[](const VdfContext &) -> double { return 11.0; })
    ///
    ///     // Register a prim computation that requests the above dispatched
    ///     // computation via uses relationship targets. Any targeted prim
    ///     // whose type is UsdGeomScope will find the requested computation.
    ///     self.PrimComputation(_tokens->myComputation)
    ///         .Callback(+[](const VdfContext &ctx) -> double {
    ///             const double *const valuePtr =
    ///                 ctx.GetInputValuePtr<double>(_tokens->eleven);
    ///             return valuePtr ? *valuePtr : -1.0;
    ///         })
    ///         .Inputs(
    ///
    ///             // This input opts-in to finding dispatched computations.
    ///             Relationship(_tokens->myRelationship)
    ///                 .TargetedObjects<double>(_tokens->eleven)
    ///                 .FallsBackToDispatched())
    /// }
    /// ```
    ///
    template <class... DispatchedOntoSchemaTypes>
    ExecPrimComputationBuilder
    DispatchedPrimComputation(
        const TfToken &computationName,
        DispatchedOntoSchemaTypes &&...schemaTypes);

    // overload that takes a vector of TfTypes
    EXEC_API
    ExecPrimComputationBuilder 
    DispatchedPrimComputation(
        const TfToken &computationName,
        ExecDispatchesOntoSchemas &&ontoSchemas);

    /// Registers a dispatched attribute computation named \p computationName.
    ///
    /// \sa DispatchedPrimComputation
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a dispatched attribute computation that can be found on
    ///     // attributes on scopes.
    ///     const TfType scopeType = TfType::FindByName("UsdGeomScope");
    ///     self.DispatchedAttributeComputation(_tokens->eleven, scopeType)
    ///         .Callback(+[](const VdfContext &) -> double { return 11.0; })
    ///
    ///     // Register a prim computation that requests the above dispatched
    ///     // computation on an attribute on the owning prim named 'attr'.
    ///     self.PrimComputation(_tokens->myComputation)
    ///         .Callback(+[](const VdfContext &ctx) -> double {
    ///             const double *const valuePtr =
    ///                 ctx.GetInputValuePtr<double>(_tokens->eleven);
    ///             return valuePtr ? *valuePtr : -1.0;
    ///         })
    ///         .Inputs(
    ///
    ///             // This input opts-in to finding dispatched computations.
    ///             Attribute(_tokens->attr)
    ///                 .Computation<double>(_tokens->eleven)
    ///                 .FallsBackToDispatched())
    /// }
    /// ```
    ///
    template <class... DispatchedOntoSchemaTypes>
    ExecAttributeComputationBuilder
    DispatchedAttributeComputation(
        const TfToken &computationName,
        DispatchedOntoSchemaTypes &&...schemaTypes);

    // overload that takes a vector of TfTypes
    EXEC_API
    ExecAttributeComputationBuilder 
    DispatchedAttributeComputation(
        const TfToken &computationName,
        ExecDispatchesOntoSchemas &&ontoSchemas);

    ///  @}

private:
    // The type of the schema for which this builder defines computations.
    TfType _schemaType;
};


// Untemplated base class for classes used to build computation definitions.
class Exec_ComputationBuilderBase
{
protected:
    EXEC_API
    Exec_ComputationBuilderBase(
        const TfToken &attributeName,
        TfType schemaType,
        const TfToken &computationName,
        bool dispatched,
        ExecDispatchesOntoSchemas &&dispatchesOntoSchemas);

    ~Exec_ComputationBuilderBase();

    // Adds the callback with result type.
    EXEC_API
    void _AddCallback(ExecCallbackFn &&calback, TfType resultType);

    // Validates that all inputs are allowed to be registered on computations of
    // the \p allowed provider types.
    // 
    template <Exec_ComputationBuilderProviderTypes allowed, typename T>
    static void _ValidateInputs();

    // Adds an input key from the given value specifier.
    //
    // This extra level of indirection helps keep Exec_InputKey out of the
    // header so that type can remain private.
    // 
    EXEC_API
    void _AddInputKey(
        const Exec_ComputationBuilderValueSpecifierBase *valueSpecifier);

    // Returns a pointer to the dispatches-onto schemas if the computation is
    // dispatched, or a null pointer, otherwise.
    //
    std::unique_ptr<ExecDispatchesOntoSchemas>
    _GetDispatchesOntoSchemas();

    // We PIMPL the data for this class to avoid exposing more private details
    // in this public header.
    //
    struct _Data;
    _Data &_GetData();

private:
    const std::unique_ptr<_Data> _data;
};


// CRTP base class for classes used to build computation definitions.
template <typename Derived>
class Exec_ComputationBuilderCRTPBase : public Exec_ComputationBuilderBase
{
    // Type used as a default template parameter type for metaprogramming.
    struct _UnspecifiedType {};

protected:
    EXEC_API
    Exec_ComputationBuilderCRTPBase(
        const TfToken &attributeName,
        TfType schemaType,
        const TfToken &computationName,
        bool dispatched,
        ExecDispatchesOntoSchemas &&dispatchesOntoSchemas);

    EXEC_API
    ~Exec_ComputationBuilderCRTPBase();

public:
    /// \ingroup group_Exec_ComputationRegistrations
    ///
    /// Registers a callback function that implements the evaluation logic for a
    /// computation.
    /// 
    /// This registration must follow a [computation
    /// registration](#group_Exec_ComputationRegistrations) or a function that
    /// returns a computation registration. This function returns a computation
    /// registration.
    ///
    /// Accepts a callback function that is a function pointer with a signature
    /// of `ReturnType (*)(const VdfContext &)`. \p ReturnType can be any of
    /// the following:
    ///
    /// - The result type of the computation, in which case `ResultType` can be
    ///   deduced from the callback type.
    /// - A type that is convertible to the result type of the computaion, in
    ///   which case \p ResultType must be explicitly specified as a template
    ///   parameter.
    /// - `void` in which case \p ResultType must be explicitly specified as a
    ///   template parameter *and* the callback must call VdfContext::SetOutput
    ///   to provide the output value.
    ///
    /// # Thread and Cache Safety
    /// 
    /// Computation callbacks must be pure functions. They must be thread safe
    /// in the sense that multiple instances of a given computation callback may
    /// be invoked simultaneously in multiple threads. They must be "cache
    /// safe," meaning that all input values must be known to the exec system so
    /// that it can invalidate cached computation results when input values
    /// change.
    ///
    /// # Result Types
    ///
    /// Note that the types used as computation result types (and as computation
    /// input value types) must be known to the execution system. All types that
    /// can be used to author attribute and metadata values in USD are known to
    /// exec by default. User-defined types must be registered by calling
    /// ExecTypeRegistry::RegisterType.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a prim computation with a callback where the result type
    ///     // is deduced to be `float`.
    ///     self.PrimComputation(_tokens->doubleValuedComputation)
    ///         .Callback(
    ///             +[](const VdfContext &) { return 11.0f; });
    ///
    ///     // Register a prim computation with a callback where the explicit
    ///     // result type is `std::string`.
    ///     self.PrimComputation(_tokens->stringValuedComputation)
    ///         .Callback<std::string>(
    ///             +[](const VdfContext &) { return "a string value"; });
    ///
    ///     // Register a prim computation with a callback that explicitly calls
    ///     // SetValue.
    ///     self.PrimComputation(_tokens->stringValuedComputation)
    ///         .Callback<int>(
    ///             +[](const VdfContext &) { ctx.SetValue(42); });
    /// }
    /// ```
    ///
    template<
        typename ResultType = _UnspecifiedType,
        typename ReturnType = _UnspecifiedType>
    Derived&
    Callback(ReturnType (*callback)(const VdfContext &));

    /// \ingroup group_Exec_ComputationRegistrations
    ///
    /// Registers a callback function that implements the evaluation logic for a
    /// computation; this overload accepts a function object that isn't a
    /// function pointer (e.g., a capturing lambda).
    /// 
    /// This registration must follow a [computation
    /// registration](#group_Exec_ComputationRegistrations) or a function that
    /// returns a computation registration. This function returns a computation
    /// registration.
    ///
    /// Accepts a function object that takes a single argument of type
    /// `const VdfContext &` and returns `void`. The \p ResultType must be
    /// explicitly specified as a template parameter. The callback must provide
    /// the output value by calling VdfContext::SetOutput,
    /// VdfContext::SetEmptyOutput, or using a write iterator.
    ///
    /// # Thread and Cache Safety
    /// 
    /// Computation callbacks must be pure functions. They must be thread safe
    /// in the sense that multiple instances of a given computation callback may
    /// be invoked simultaneously in multiple threads. They must be "cache
    /// safe," meaning that all input values must be known to the exec system so
    /// that it can invalidate cached computation results when input values
    /// change.
    ///
    /// When passing a function object as a callback, it is therefore important
    /// to be careful not to run afoul of these requirements for callbacks. E.g.,
    /// be careful with lambda capture, or shared state that is reachable from
    /// function object data members.
    /// 
    /// # Result Types
    ///
    /// Note that the types used as computation result types (and as computation
    /// input value types) must be known to the execution system. All types that
    /// can be used to author attribute and metadata values in USD are known to
    /// exec by default. User-defined types must be registered by calling
    /// ExecTypeRegistry::RegisterType.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     const TfToken inputName("anInputName");
    ///     
    ///     // Register a prim computation with a capturing lambda as the
    ///     // callback.
    ///     self.PrimComputation(_tokens->stringValuedComputation)
    ///         .Callback<int>([inputName](const VdfContext &) {
    ///             int i = ctx.GetInputValue(inputName);
    ///             ctx.SetValue(i+1);
    ///         });
    /// }
    /// ```
    ///
    template<typename ResultType, typename FuncType>
    Derived&
    Callback(FuncType &&callback);
};


/// Class used to build prim computation definitions.
class ExecPrimComputationBuilder final
    : public Exec_ComputationBuilderCRTPBase<ExecPrimComputationBuilder>
{
    // Only ExecComputationBuilder can create instances.
    friend class ExecComputationBuilder;

    EXEC_API
    ExecPrimComputationBuilder(
        TfType schemaType,
        const TfToken &computationName,
        bool dispatched = false,
        ExecDispatchesOntoSchemas &&dispatchesOntoSchemas = {});

public:
    EXEC_API
    ~ExecPrimComputationBuilder();

    /// \ingroup group_Exec_ComputationRegistrations
    ///
    /// Takes one or more [input registrations](#group_Exec_InputRegistrations)
    /// that specify how to source input values for a prim computation.
    /// 
    /// This registration must follow a [computation
    /// registration](#group_Exec_ComputationRegistrations) or a function that
    /// returns a computation registration. This function returns a computation
    /// registration.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register a prim computation that reads from two inputs.
    ///     self.PrimComputation(_tokens->myPrimComputation)
    ///         .Callback(&_MyCallbackFn)
    ///         .Inputs(
    ///             AttributeValue<int>(_tokens->myAttribute),
    ///             NamespaceAncestor<double>(_tokens->anotherPrimComputation));
    /// }
    /// ```
    ///
    template <typename... Args>
    ExecPrimComputationBuilder&
    Inputs(Args && ... args);
};


/// Class used to build attribute computation definitions.
class ExecAttributeComputationBuilder final
    : public Exec_ComputationBuilderCRTPBase<ExecAttributeComputationBuilder>
{
    // Only ExecComputationBuilder can create instances.
    friend class ExecComputationBuilder;

    EXEC_API
    ExecAttributeComputationBuilder(
        const TfToken &attributeName,
        TfType schemaType,
        const TfToken &computationName,
        bool dispatched = false,
        ExecDispatchesOntoSchemas &&dispatchesOntoSchemas = {});

public:
    EXEC_API
    ~ExecAttributeComputationBuilder();

    /// \ingroup group_Exec_ComputationRegistrations
    ///
    /// Takes one or more [input registrations](#group_Exec_InputRegistrations)
    /// that specify how to source input values for an attribute computation.
    /// 
    /// This registration must follow a [computation
    /// registration](#group_Exec_ComputationRegistrations) or a function that
    /// returns a computation registration. This function returns a computation
    /// registration.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register an attribute computation that reads from another
    ///     // computation on the same attribute, and from a sibling attribute's
    ///     // computed value.
    ///     self.AttributeComputation(
    ///         _tokens->attr,
    ///         _tokens->myAttrComputation)
    ///         .Callback(&_MyCallbackFn)
    ///         .Inputs(
    ///             Computation<double>(_tokens->anotherAttrComputation),
    ///             Prim().AttributeValue<double>(_tokens->anotherAttr));
    /// }
    /// ```
    ///
    template <typename... Args>
    ExecAttributeComputationBuilder&
    Inputs(Args && ... args);
};

/// Class used to build attribute expression definitions.
class ExecAttributeExpressionBuilder final
    : public Exec_ComputationBuilderCRTPBase<ExecAttributeExpressionBuilder>
{
    // Only ExecComputationBuilder can create instances.
    friend class ExecComputationBuilder;

    EXEC_API
    ExecAttributeExpressionBuilder(
        const TfToken &attributeName,
        TfType schemaType);

public:
    EXEC_API
    ~ExecAttributeExpressionBuilder();

    /// \ingroup group_Exec_ComputationRegistrations
    ///
    /// Takes one or more [input registrations](#group_Exec_InputRegistrations)
    /// that specify how to source input values for an attribute expression.
    /// 
    /// This registration must follow a [computation
    /// registration](#group_Exec_ComputationRegistrations) or a function that
    /// returns a computation registration. This function returns a computation
    /// registration.
    ///
    /// # Example
    ///
    /// ```{.cpp}
    /// EXEC_REGISTER_COMPUTATIONS_FOR_SCHEMA(MySchemaType)
    /// {
    ///     // Register an attribute expression that uses the attribute's
    ///     // resolved value, and the computed value of all connected
    ///     // attributes.
    ///     self.AttributeExpression(_tokens->attr)
    ///         .Callback(&_MyCallbackFn)
    ///         .Inputs(
    ///             Computation<double>(
    ///                 ExecBuiltinComputations->computeResolvedValue),
    ///             Connections<double>(ExecBuiltinComputations->computeValue));
    /// }
    /// ```
    ///
    template <typename... Args>
    ExecAttributeExpressionBuilder&
    Inputs(Args && ... args);
};


//
// Exec_ComputationBuilderBase
//

template <Exec_ComputationBuilderProviderTypes allowed, typename T>
void
Exec_ComputationBuilderBase::_ValidateInputs() {
    using regType = std::decay_t<T>;
    static_assert(
        !std::is_base_of_v<Exec_ComputationBuilderAccessorBase, regType>,
        "Accessor can't provide an input value.");
    static_assert(
        !std::is_same_v<Exec_ComputationBuilderConstantAccessorBase, regType>,
        "Constant(value) must be followed by .InputName(inputNameToken)");
    static_assert(
        std::is_base_of_v<Exec_ComputationBuilderValueSpecifierBase, regType>,
        "Invalid type used as an input registration.");
    static_assert(
        regType::allowedProviders & allowed,
        "This input registration is not allowed on a provider of this type.");
    static_assert(
        (regType::allowedProviders & allowed) ||
        (allowed ==
         Exec_ComputationBuilderProviderTypes::Attribute),
        "This input registration is only allowed on a provider attribute; "
        "there may be a missing Attribute(attributeName) registration.");
    static_assert(
        (regType::allowedProviders & allowed) ||
        (allowed ==
         Exec_ComputationBuilderProviderTypes::Prim),
        "This input registration is only allowed on a provider prim; "
        "there may be a missing Prim() registration.");
}

//
// Exec_ComputationBuilderCRTPBase
//

template <typename Derived>
template <typename InputResultType, typename ReturnType>
Derived&
Exec_ComputationBuilderCRTPBase<Derived>::Callback(
    ReturnType (*callback)(const VdfContext &))
{
    // In order to allow the return type of the callback to be different from
    // the computation result type in some cases AND be able to deduce the
    // result type from the return type in others, we have to default both
    // template parameters to _UnspecifiedType and use metaprogramming to get
    // the actual result type.
    using ResultType =
        std::conditional_t<
            std::is_same_v<InputResultType, _UnspecifiedType>,
            ReturnType,
            InputResultType>;

    static_assert(
        !std::is_void_v<ResultType> ||
        std::is_convertible_v<ReturnType, ResultType>,
        "Callback return type must be convertible to the computation result "
        "type.");
    static_assert(
        !std::is_reference_v<ResultType>,
        "Callback functions must return by value.");
    static_assert(
        !VtIsArray<ResultType>::value,
        "VtArray is not a supported result type.");

    const TfType resultType =
        ExecTypeRegistry::GetInstance().CheckForRegistration<ResultType>();

    // If the return type is void, the callback is on the hook to call
    // VdfContext::SetOutput; otherwise, we wrap it in a lambda that passes
    // the callback return value to SetOutput.
    if constexpr (std::is_void_v<ReturnType>) {
        _AddCallback(callback, resultType);
    } else {
        _AddCallback(
            [callback](const VdfContext& ctx) {
                ctx.SetOutput<ResultType>(callback(ctx));
            },
            resultType);
    }

    return *static_cast<Derived*>(this);
}

template <typename Derived>
template<typename ResultType, typename FuncType>
Derived&
Exec_ComputationBuilderCRTPBase<Derived>::Callback(
    FuncType &&callback)
{
    // This condition will also be caught by the check for void return below,
    // but this produces a much clearer compile error, and therefore we do this
    // check first.
    static_assert(
        std::is_invocable_v<
            const std::remove_reference_t<FuncType>, const VdfContext&>,
        "Callback function object must have const function call operator.");

    static_assert(
        !std::is_void_v<ResultType>,
        "The ResultType template parameter cannot be void, as this indicates "
        "the value type of the result produced by the computation.");

    // The callback's return type must be void because any returned value
    // will be ignored.
    static_assert(
        std::is_void_v<std::invoke_result_t<const FuncType, const VdfContext&>>,
        "Callback return type must be void. Use VdfContext::SetOutput to "
        "set a result value.");

    static_assert(
        !std::is_reference_v<ResultType>,
        "Callback function must return by value.");
    static_assert(
        !VtIsArray<ResultType>::value,
        "VtArray is not a supported result type.");

    const TfType resultType =
        ExecTypeRegistry::GetInstance().CheckForRegistration<ResultType>();

    _AddCallback(callback, resultType);

    return *static_cast<Derived*>(this);
}

//
// ExecPrimComputationBuilder
//

template <typename... Args>
ExecPrimComputationBuilder&
ExecPrimComputationBuilder::Inputs(
    Args && ... args)
{
    // Validate inputs
    (_ValidateInputs<
         Exec_ComputationBuilderProviderTypes::Prim, Args>(), ...);

    // Add inputs
    (_AddInputKey(&args), ...);

    return *this;
}

//
// ExecAttributeComputationBuilder
//

template <typename... Args>
ExecAttributeComputationBuilder&
ExecAttributeComputationBuilder::Inputs(
    Args && ... args)
{
    // Validate inputs
    (_ValidateInputs<
         Exec_ComputationBuilderProviderTypes::Attribute, Args>(), ...);

    // Add inputs
    (_AddInputKey(&args), ...);

    return *this;
}

//
// ExecAttributeExpressionBuilder
//

template <typename... Args>
ExecAttributeExpressionBuilder&
ExecAttributeExpressionBuilder::Inputs(
    Args && ... args)
{
    // Validate inputs
    (_ValidateInputs<
         Exec_ComputationBuilderProviderTypes::Attribute, Args>(), ...);

    // Add inputs
    (_AddInputKey(&args), ...);

    return *this;
}

//
// ExecComputationBuilder
//

template <class... DispatchedOntoSchemaTypes>
ExecPrimComputationBuilder
ExecComputationBuilder::DispatchedPrimComputation(
    const TfToken &computationName,
    DispatchedOntoSchemaTypes &&...schemaTypes)
{
    static_assert(
        (std::is_same_v<
             std::decay_t<DispatchedOntoSchemaTypes>, TfType> && ...));

    return DispatchedPrimComputation(
        computationName,
        {std::forward<DispatchedOntoSchemaTypes>(schemaTypes)...});
}

template <class... DispatchedOntoSchemaTypes>
ExecAttributeComputationBuilder
ExecComputationBuilder::DispatchedAttributeComputation(
    const TfToken &computationName,
    DispatchedOntoSchemaTypes &&...schemaTypes)
{
    static_assert(
        (std::is_same_v<
             std::decay_t<DispatchedOntoSchemaTypes>, TfType> && ...));

    return DispatchedAttributeComputation(
        computationName,
        {std::forward<DispatchedOntoSchemaTypes>(schemaTypes)...});
}

PXR_NAMESPACE_CLOSE_SCOPE

#endif
