// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Internationalization/Text.h" #include "MetasoundDataReference.h" #include "MetasoundNodeInterface.h" #include "MetasoundOperatorInterface.h" #include "MetasoundTrigger.h" #define LOCTEXT_NAMESPACE "MetasoundFrontend" namespace Metasound { /** A writable input and a readable output. */ template class TInputOperator : public IOperator { public: using FDataWriteReference = TDataWriteReference; TInputOperator(const FVertexName& InDataReferenceName, FDataWriteReference InDataReference) : DataReferenceName(InDataReferenceName) // Executable DataTypes require a copy of the output to operate on whereas non-executable // types do not. Avoid copy by assigning to reference for non-executable types. , InputValue(InDataReference) , OutputValue(TExecutableDataType::bIsExecutable ? FDataWriteReference::CreateNew(*InDataReference) : InDataReference) { } virtual ~TInputOperator() = default; virtual FDataReferenceCollection GetInputs() const override { // TODO: Expose a readable reference instead of a writable reference. // // If data needs to be written to, outside entities should create // it and pass it in as a readable reference. Currently, the workflow // is to have the input node create a writable reference which is then // queried by the outside world. Exposing writable references causes // code maintainability issues where TInputNode<> specializations need // to handle multiple situations which can happen in an input node. // // The only reason that this code is not removed immediately is because // of the `TExecutableDataType<>` which primarily supports the FTrigger. // The TExecutableDataType<> advances the trigger within the graph. But, // with graph composition, the owner of the data type becomes more // complicated and hence triggers advancing should be managed by a // different object. Preferably the graph operator itself, or an // explicit trigger manager tied to the environment. FDataReferenceCollection Inputs; Inputs.AddDataWriteReference(DataReferenceName, InputValue); return Inputs; } virtual FDataReferenceCollection GetOutputs() const override { FDataReferenceCollection Outputs; Outputs.AddDataReadReference(DataReferenceName, OutputValue); return Outputs; } void Execute() { TExecutableDataType::Execute(*InputValue, *OutputValue); } void ExecutPassThrough() { if (TExecutableDataType::bIsExecutable) { *OutputValue = *InputValue; } } static void ExecuteFunction(IOperator* InOperator) { static_cast*>(InOperator)->Execute(); } virtual FExecuteFunction GetExecuteFunction() override { if (TExecutableDataType::bIsExecutable) { return &TInputOperator::ExecuteFunction; } return nullptr; } protected: FVertexName DataReferenceName; FDataWriteReference InputValue; FDataWriteReference OutputValue; }; /** TPassThroughOperator supplies a readable input and a readable output. * * It does *not* invoke executable data types (see `TExecutableDataType<>`). */ template class TPassThroughOperator : public TInputOperator { public: using FDataReadReference = TDataReadReference; using Super = TInputOperator; TPassThroughOperator(const FVertexName& InDataReferenceName, FDataReadReference InDataReference) : TInputOperator(InDataReferenceName, WriteCast(InDataReference)) // Write cast is safe because `GetExecuteFunction() and GetInputs() are overridden, ensuring that data is not written. , DataReferenceName(InDataReferenceName) { } virtual ~TPassThroughOperator() = default; virtual FDataReferenceCollection GetInputs() const override { FDataReferenceCollection Inputs; ensure(Inputs.AddDataReadReferenceFrom(DataReferenceName, Super::GetInputs(), DataReferenceName, GetMetasoundDataTypeName())); return Inputs; } static void ExecuteFunction(IOperator* InOperator) { static_cast*>(InOperator)->ExecutPassThrough(); } virtual IOperator::FExecuteFunction GetExecuteFunction() override { // TODO: this is a hack until we can remove TExecutableOperator<>. // // The primary contention is that we would like to allow developers // to specialize `TInputNode<>` as in `TInputNode`. // `TExecutableOperator<>` adds in a level of complexity that makes it // difficult to allow specialization of TInputNode and to derive from // TInputNode to create the TPassThroughOperator. Particularly because // TExecutableOperator<> alters which output data reference is used. // Specializations of TInputNode also tend to alter the output data // references. Supporting both is likely to cause issues. // // We may need to ensure that input nodes do not provide execution // functions. Or we may need a more explicit way of only allowing // outputs to be modified. Likely a mix of the `final` keyword // and disabling template specialization of a base class. // // namespace Private // { // class TInputNodePrivate<> // { // GetInputs() final // GetExecutionFunction() final // GetOutputs() // } // } // // template // using TInputNodeBase = TInputNodePrivate; // Do not allow specialization of TInputNodePrivate<> or TInputNodeBase<> (this works because you can't specialize a template alias) // // // DO ALLOW specialization of TInputNode // template // class TInputNode : public TInputNodeBase // { // }; // // template<> // class TInputNode : public TInputNodeBase // { // GetOutputs() <-- OK to override // } // if (TExecutableDataType::bIsExecutable) { return &TPassThroughOperator::ExecuteFunction; } return nullptr; } private: FVertexName DataReferenceName; }; /** Data type creation policy to create by copy construction. */ template struct FCreateDataReferenceWithCopy { template FCreateDataReferenceWithCopy(ArgTypes&&... Args) : Data(Forward(Args)...) { } TDataWriteReference CreateDataReference(const FOperatorSettings& InOperatorSettings) const { return TDataWriteReferenceFactory::CreateExplicitArgs(InOperatorSettings, Data); } private: DataType Data; }; /** Data type creation policy to create by literal construction. */ template struct FCreateDataReferenceWithLiteral { // If the data type is parsable from a literal type, then the data type // can be registered as an input type with the frontend. To make a // DataType registrable, either create a constructor for the data type // which accepts the one of the supported literal types with an optional // FOperatorSettings argument, or create a default constructor, or specialize // this factory with an implementation for that specific data type. static constexpr bool bCanCreateWithLiteral = TLiteralTraits::bIsParsableFromAnyLiteralType; FCreateDataReferenceWithLiteral(FLiteral&& InLiteral) : Literal(MoveTemp(InLiteral)) { } TDataWriteReference CreateDataReference(const FOperatorSettings& InOperatorSettings) const { return TDataWriteReferenceLiteralFactory::CreateExplicitArgs(InOperatorSettings, Literal); } private: FLiteral Literal; }; /** TInputOperatorFactory initializes the DataType at construction. It uses * the ReferenceCreatorType to create a data reference if one is not passed in. */ template class TInputOperatorFactory : public IOperatorFactory { public: using FDataWriteReference = TDataWriteReference; using FDataReadReference = TDataReadReference; TInputOperatorFactory(ReferenceCreatorType&& InReferenceCreator) : ReferenceCreator(MoveTemp(InReferenceCreator)) { } virtual TUniquePtr CreateOperator(const FCreateOperatorParams& InParams, FBuildErrorArray& OutErrors) override; private: ReferenceCreatorType ReferenceCreator; }; /** TInputNode represents an input to a metasound graph. */ template class TInputNode : public FNode { public: // If true, this node can be instantiated by the FrontEnd. static constexpr bool bCanRegister = FCreateDataReferenceWithLiteral::bCanCreateWithLiteral; static FVertexInterface DeclareVertexInterface(const FVertexName& InVertexName) { return FVertexInterface( FInputVertexInterface( TInputDataVertexModel(InVertexName, FText::GetEmpty()) ), FOutputVertexInterface( TOutputDataVertexModel(InVertexName, FText::GetEmpty()) ) ); } static FNodeClassMetadata GetNodeInfo(const FVertexName& InVertexName) { FNodeClassMetadata Info; Info.ClassName = { "Input", GetMetasoundDataTypeName(), FName() }; Info.MajorVersion = 1; Info.MinorVersion = 0; Info.Description = LOCTEXT("Metasound_InputNodeDescription", "Input into the parent Metasound graph."); Info.Author = PluginAuthor; Info.PromptIfMissing = PluginNodeMissingPrompt; Info.DefaultInterface = DeclareVertexInterface(InVertexName); return Info; } template static FOperatorFactorySharedRef CreateOperatorFactoryWithArgs(ArgTypes&&... Args) { using FCreatorType = FCreateDataReferenceWithCopy; using FFactoryType = TInputOperatorFactory; return MakeOperatorFactoryRef(FCreatorType(Forward(Args)...)); } static FOperatorFactorySharedRef CreateOperatorFactoryWithLiteral(FLiteral&& InLiteral) { using FCreatorType = FCreateDataReferenceWithLiteral; using FFactoryType = TInputOperatorFactory; return MakeOperatorFactoryRef(FCreatorType(MoveTemp(InLiteral))); } /* Construct a TInputNode using the TInputOperatorFactory<> and forwarding * Args to the TInputOperatorFactory constructor.*/ template TInputNode(const FVertexName& InInstanceName, const FGuid& InInstanceID, const FVertexName& InVertexName, ArgTypes&&... Args) : FNode(InInstanceName, InInstanceID, GetNodeInfo(InVertexName)) , VertexName(InVertexName) , Interface(DeclareVertexInterface(InVertexName)) , Factory(CreateOperatorFactoryWithArgs(Forward(Args)...)) { } /* Construct a TInputNode using the TInputOperatorLiteralFactory<> and moving * InParam to the TInputOperatorLiteralFactory constructor.*/ explicit TInputNode(const FVertexName& InNodeName, const FGuid& InInstanceID, const FVertexName& InVertexName, FLiteral&& InParam) : FNode(InNodeName, InInstanceID, GetNodeInfo(InVertexName)) , VertexName(InVertexName) , Interface(DeclareVertexInterface(InVertexName)) , Factory(CreateOperatorFactoryWithLiteral(MoveTemp(InParam))) { } const FVertexName& GetVertexName() const { return VertexName; } virtual const FVertexInterface& GetVertexInterface() const override { return Interface; } virtual bool SetVertexInterface(const FVertexInterface& InInterface) override { return Interface == InInterface; } virtual bool IsVertexInterfaceSupported(const FVertexInterface& InInterface) const override { return Interface == InInterface; } virtual TSharedRef GetDefaultOperatorFactory() const override { return Factory; } private: FVertexName VertexName; FVertexInterface Interface; FOperatorFactorySharedRef Factory; }; template TUniquePtr TInputOperatorFactory::CreateOperator(const FCreateOperatorParams& InParams, FBuildErrorArray& OutErrors) { using FInputNodeType = TInputNode; const FInputNodeType& InputNode = static_cast(InParams.Node); const FVertexName& VertexKey = InputNode.GetVertexName(); if (InParams.InputDataReferences.ContainsDataWriteReference(VertexKey)) { // Data is externally owned. Use pass through operator FDataWriteReference DataRef = InParams.InputDataReferences.GetDataWriteReference(VertexKey); return MakeUnique>(InputNode.GetVertexName(), DataRef); } else if (InParams.InputDataReferences.ContainsDataReadReference(VertexKey)) { // Data is externally owned. Use pass through operator FDataReadReference DataRef = InParams.InputDataReferences.GetDataReadReference(VertexKey); return MakeUnique>(InputNode.GetVertexName(), DataRef); } else { // Create write reference by calling compatible constructor with literal. FDataWriteReference DataRef = ReferenceCreator.CreateDataReference(InParams.OperatorSettings); return MakeUnique>(InputNode.GetVertexName(), DataRef); } } } // namespace Metasound #undef LOCTEXT_NAMESPACE // MetasoundFrontend