// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Internationalization/Text.h" #include "MetasoundBuilderInterface.h" #include "MetasoundDataFactory.h" #include "MetasoundExecutableOperator.h" #include "MetasoundFacade.h" #include "MetasoundLog.h" #include "MetasoundNodeInterface.h" #include "MetasoundOperatorInterface.h" #include "MetasoundPrimitives.h" #include "MetasoundTrigger.h" #include "MetasoundVertex.h" #include #define LOCTEXT_NAMESPACE "MetasoundFrontend" namespace Metasound { namespace MetasoundArrayNodesPrivate { // Convenience function for make FNodeClassMetadata of array nodes. METASOUNDFRONTEND_API FNodeClassMetadata CreateArrayNodeClassMetadata(const FName& InDataTypeName, const FName& InOperatorName, const FText& InDisplayName, const FText& InDescription, const FVertexInterface& InDefaultInterface); // Retrieve the ElementType from an ArrayType template struct TArrayElementType { // Default implementation has Type. }; // ElementType specialization for TArray types. template struct TArrayElementType> { using Type = ElementType; }; } namespace ArrayNodeVertexNames { /* Input Vertex Names */ METASOUNDFRONTEND_API const FVertexName& GetInputArrayName(); METASOUNDFRONTEND_API const FVertexName& GetInputLeftArrayName(); METASOUNDFRONTEND_API const FVertexName& GetInputRightArrayName(); METASOUNDFRONTEND_API const FVertexName& GetInputTriggerName(); METASOUNDFRONTEND_API const FVertexName& GetInputStartIndexName(); METASOUNDFRONTEND_API const FVertexName& GetInputEndIndexName(); METASOUNDFRONTEND_API const FVertexName& GetInputIndexName(); METASOUNDFRONTEND_API const FVertexName& GetInputValueName(); /* Output Vertex Names */ METASOUNDFRONTEND_API const FVertexName& GetOutputNumName(); METASOUNDFRONTEND_API const FVertexName& GetOutputValueName(); METASOUNDFRONTEND_API const FVertexName& GetOutputArrayName(); }; /** TArrayNumOperator gets the number of elements in an Array. The operator * uses the FNodeFacade and defines the vertex, metadata and vertex interface * statically on the operator class. */ template class TArrayNumOperator : public TExecutableOperator> { public: using FArrayDataReadReference = TDataReadReference; // Declare the vertex interface static const FVertexInterface& GetDefaultInterface() { using namespace ArrayNodeVertexNames; static const FVertexInterface DefaultInterface( FInputVertexInterface( TInputDataVertexModel(GetInputArrayName(), LOCTEXT("ArrayOpArrayNumInput", "Array to inspect.")) ), FOutputVertexInterface( TOutputDataVertexModel(GetOutputNumName(), LOCTEXT("ArrayOpArrayNumOutput", "Number of elements in the array.")) ) ); return DefaultInterface; } static const FNodeClassMetadata& GetNodeInfo() { auto CreateNodeClassMetadata = []() -> FNodeClassMetadata { const FName DataTypeName = GetMetasoundDataTypeName(); const FName OperatorName = TEXT("Num"); const FText NodeDisplayName = FText::Format(LOCTEXT("ArrayOpArrayNumDisplayNamePattern", "Num ({0})"), GetMetasoundDataTypeDisplayText()); const FText NodeDescription = LOCTEXT("ArrayOpArrayNumDescription", "Number of elements in the array"); const FVertexInterface NodeInterface = GetDefaultInterface(); return MetasoundArrayNodesPrivate::CreateArrayNodeClassMetadata(DataTypeName, OperatorName, NodeDisplayName, NodeDescription, NodeInterface); }; static const FNodeClassMetadata Metadata = CreateNodeClassMetadata(); return Metadata; } static TUniquePtr CreateOperator(const FCreateOperatorParams& InParams, TArray>& OutErrors) { using namespace ArrayNodeVertexNames; using namespace MetasoundArrayNodesPrivate; const FInputVertexInterface& Inputs = InParams.Node.GetVertexInterface().GetInputInterface(); // Get the input array or construct an empty one. FArrayDataReadReference Array = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputArrayName(), InParams.OperatorSettings); return MakeUnique(Array); } TArrayNumOperator(FArrayDataReadReference InArray) : Array(InArray) , Num(TDataWriteReference::CreateNew()) { // Initialize value for downstream nodes. *Num = Array->Num(); } virtual ~TArrayNumOperator() = default; virtual FDataReferenceCollection GetInputs() const override { using namespace ArrayNodeVertexNames; FDataReferenceCollection Inputs; Inputs.AddDataReadReference(GetInputArrayName(), Array); return Inputs; } virtual FDataReferenceCollection GetOutputs() const override { using namespace ArrayNodeVertexNames; FDataReferenceCollection Outputs; Outputs.AddDataReadReference(GetOutputNumName(), Num); return Outputs; } void Execute() { *Num = Array->Num(); } private: FArrayDataReadReference Array; TDataWriteReference Num; }; template class TArrayNumNode : public FNodeFacade { public: TArrayNumNode(const FNodeInitData& InInitData) : FNodeFacade(InInitData.InstanceName, InInitData.InstanceID, TFacadeOperatorClass>()) { } virtual ~TArrayNumNode() = default; }; /** TArrayGetOperator copies a value from the array to the output when * a trigger occurs. Initially, the output value is default constructed and * will remain that way until until a trigger is encountered. */ template class TArrayGetOperator : public TExecutableOperator> { public: using FArrayDataReadReference = TDataReadReference; using ElementType = typename MetasoundArrayNodesPrivate::TArrayElementType::Type; static const FVertexInterface& GetDefaultInterface() { using namespace ArrayNodeVertexNames; static const FVertexInterface DefaultInterface( FInputVertexInterface( TInputDataVertexModel(GetInputTriggerName(), LOCTEXT("ArrayOpArrayGetTrigger", "Trigger to get value.")), TInputDataVertexModel(GetInputArrayName(), LOCTEXT("ArrayOpArrayGetInput", "Input Array.")), TInputDataVertexModel(GetInputIndexName(), LOCTEXT("ArrayOpArrayGetIndex", "Index in Array.")) ), FOutputVertexInterface( TOutputDataVertexModel(GetOutputValueName(), LOCTEXT("ArrayOpArrayGetOutput", "Value of element at array index.")) ) ); return DefaultInterface; } static const FNodeClassMetadata& GetNodeInfo() { auto CreateNodeClassMetadata = []() -> FNodeClassMetadata { const FName DataTypeName = GetMetasoundDataTypeName(); const FName OperatorName = TEXT("Get"); const FText NodeDisplayName = FText::Format(LOCTEXT("ArrayOpArrayGetDisplayNamePattern", "Get ({0})"), GetMetasoundDataTypeDisplayText()); const FText NodeDescription = LOCTEXT("ArrayOpArrayGetDescription", "Get element at index in array."); const FVertexInterface NodeInterface = GetDefaultInterface(); return MetasoundArrayNodesPrivate::CreateArrayNodeClassMetadata(DataTypeName, OperatorName, NodeDisplayName, NodeDescription, NodeInterface); }; static const FNodeClassMetadata Metadata = CreateNodeClassMetadata(); return Metadata; } static TUniquePtr CreateOperator(const FCreateOperatorParams& InParams, TArray>& OutErrors) { using namespace ArrayNodeVertexNames; using namespace MetasoundArrayNodesPrivate; const FInputVertexInterface& Inputs = InParams.Node.GetVertexInterface().GetInputInterface(); // Input Trigger TDataReadReference Trigger = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputTriggerName(), InParams.OperatorSettings); // Input Array FArrayDataReadReference Array = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputArrayName(), InParams.OperatorSettings); // Input Index TDataReadReference Index = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputIndexName(), InParams.OperatorSettings); return MakeUnique(InParams.OperatorSettings, Trigger, Array, Index); } TArrayGetOperator(const FOperatorSettings& InSettings, TDataReadReference InTrigger, FArrayDataReadReference InArray, TDataReadReference InIndex) : Trigger(InTrigger) , Array(InArray) , Index(InIndex) , Value(TDataWriteReferenceFactory::CreateAny(InSettings)) { } virtual ~TArrayGetOperator() = default; virtual FDataReferenceCollection GetInputs() const override { using namespace ArrayNodeVertexNames; FDataReferenceCollection Inputs; Inputs.AddDataReadReference(GetInputTriggerName(), Trigger); Inputs.AddDataReadReference(GetInputArrayName(), Array); Inputs.AddDataReadReference(GetInputIndexName(), Index); return Inputs; } virtual FDataReferenceCollection GetOutputs() const override { using namespace ArrayNodeVertexNames; FDataReferenceCollection Outputs; Outputs.AddDataReadReference(GetOutputValueName(), Value); return Outputs; } void Execute() { // Only perform get on trigger. if (*Trigger) { const int32 IndexValue = *Index; const ArrayType& ArrayRef = *Array; if ((IndexValue >= 0) && (IndexValue < ArrayRef.Num())) { *Value = ArrayRef[IndexValue]; } else { UE_LOG(LogMetaSound, Error, TEXT("Attempt to get value at invalid index [ArraySize:%d, Index:%d]"), ArrayRef.Num(), IndexValue); } } } private: TDataReadReference Trigger; FArrayDataReadReference Array; TDataReadReference Index; TDataWriteReference Value; }; template class TArrayGetNode : public FNodeFacade { public: TArrayGetNode(const FNodeInitData& InInitData) : FNodeFacade(InInitData.InstanceName, InInitData.InstanceID, TFacadeOperatorClass>()) { } virtual ~TArrayGetNode() = default; }; /** TArraySetOperator sets an element in an array to a specific value. */ template class TArraySetOperator : public TExecutableOperator> { public: using FArrayDataReadReference = TDataReadReference; using FArrayDataWriteReference = TDataWriteReference; using ElementType = typename MetasoundArrayNodesPrivate::TArrayElementType::Type; static const FVertexInterface& GetDefaultInterface() { using namespace ArrayNodeVertexNames; static const FVertexInterface DefaultInterface( FInputVertexInterface( TInputDataVertexModel(GetInputTriggerName(), LOCTEXT("ArrayOpArraySetTrigger", "Trigger to set value.")), TInputDataVertexModel(GetInputArrayName(), LOCTEXT("ArrayOpArraySetInput", "Input Array.")), TInputDataVertexModel(GetInputIndexName(), LOCTEXT("ArrayOpArraySetIndex", "Index in Array.")), TInputDataVertexModel(GetInputValueName(), LOCTEXT("ArrayOpArraySetElement", "Value to set")) ), FOutputVertexInterface( TOutputDataVertexModel(GetOutputArrayName(), LOCTEXT("ArrayOpArraySetOutput", "Array after setting.")) ) ); return DefaultInterface; } static const FNodeClassMetadata& GetNodeInfo() { auto CreateNodeClassMetadata = []() -> FNodeClassMetadata { const FName DataTypeName = GetMetasoundDataTypeName(); const FName OperatorName = TEXT("Set"); const FText NodeDisplayName = FText::Format(LOCTEXT("ArrayOpArraySetDisplayNamePattern", "Set ({0})"), GetMetasoundDataTypeDisplayText()); const FText NodeDescription = LOCTEXT("ArrayOpArraySetDescription", "Set element at index in array."); const FVertexInterface NodeInterface = GetDefaultInterface(); return MetasoundArrayNodesPrivate::CreateArrayNodeClassMetadata(DataTypeName, OperatorName, NodeDisplayName, NodeDescription, NodeInterface); }; static const FNodeClassMetadata Metadata = CreateNodeClassMetadata(); return Metadata; } static TUniquePtr CreateOperator(const FCreateOperatorParams& InParams, TArray>& OutErrors) { using namespace ArrayNodeVertexNames; using namespace MetasoundArrayNodesPrivate; const FInputVertexInterface& Inputs = InParams.Node.GetVertexInterface().GetInputInterface(); TDataReadReference Trigger = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputTriggerName(), InParams.OperatorSettings); FArrayDataReadReference InitArray = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputArrayName(), InParams.OperatorSettings); FArrayDataWriteReference Array = TDataWriteReferenceFactory::CreateExplicitArgs(InParams.OperatorSettings, *InitArray); TDataReadReference Index = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputIndexName(), InParams.OperatorSettings); TDataReadReference Value = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputValueName(), InParams.OperatorSettings); return MakeUnique(InParams.OperatorSettings, Trigger, InitArray, Array, Index, Value); } TArraySetOperator(const FOperatorSettings& InSettings, TDataReadReference InTrigger, FArrayDataReadReference InInitArray, FArrayDataWriteReference InArray, TDataReadReference InIndex, TDataReadReference InValue) : OperatorSettings(InSettings) , Trigger(InTrigger) , InitArray(InInitArray) , Array(InArray) , Index(InIndex) , Value(InValue) { } virtual ~TArraySetOperator() = default; virtual FDataReferenceCollection GetInputs() const override { using namespace ArrayNodeVertexNames; FDataReferenceCollection Inputs; Inputs.AddDataReadReference(GetInputTriggerName(), Trigger); Inputs.AddDataReadReference(GetInputArrayName(), InitArray); Inputs.AddDataReadReference(GetInputIndexName(), Index); Inputs.AddDataReadReference(GetInputValueName(), Value); return Inputs; } virtual FDataReferenceCollection GetOutputs() const override { using namespace ArrayNodeVertexNames; FDataReferenceCollection Outputs; Outputs.AddDataReadReference(GetOutputArrayName(), Array); return Outputs; } void Execute() { if (*Trigger) { const int32 IndexValue = *Index; ArrayType& ArrayRef = *Array; if ((IndexValue >= 0) && (IndexValue < ArrayRef.Num())) { ArrayRef[IndexValue] = *Value; } else { UE_LOG(LogMetaSound, Error, TEXT("Attempt to set value at invalid index [ArraySize:%d, Index:%d]"), ArrayRef.Num(), IndexValue); } } } private: FOperatorSettings OperatorSettings; TDataReadReference Trigger; FArrayDataReadReference InitArray; FArrayDataWriteReference Array; TDataReadReference Index; TDataReadReference Value; }; template class TArraySetNode : public FNodeFacade { public: TArraySetNode(const FNodeInitData& InInitData) : FNodeFacade(InInitData.InstanceName, InInitData.InstanceID, TFacadeOperatorClass>()) { } virtual ~TArraySetNode() = default; }; /** TArrayConcatOperator concatenates two arrays on trigger. */ template class TArrayConcatOperator : public TExecutableOperator> { public: using FArrayDataReadReference = TDataReadReference; using FArrayDataWriteReference = TDataWriteReference; using ElementType = typename MetasoundArrayNodesPrivate::TArrayElementType::Type; static const FVertexInterface& GetDefaultInterface() { using namespace ArrayNodeVertexNames; static const FVertexInterface DefaultInterface( FInputVertexInterface( TInputDataVertexModel(GetInputTriggerName(), LOCTEXT("ArrayOpArrayConcatTrigger", "Trigger to set value.")), TInputDataVertexModel(GetInputLeftArrayName(), LOCTEXT("ArrayOpArrayConcatInputLeft", "Input Left Array.")), TInputDataVertexModel(GetInputRightArrayName(), LOCTEXT("ArrayOpArrayConcatInputRight", "Input Left Array.")) ), FOutputVertexInterface( TOutputDataVertexModel(GetOutputArrayName(), LOCTEXT("ArrayOpArrayConcatOutput", "Array after setting.")) ) ); return DefaultInterface; } static const FNodeClassMetadata& GetNodeInfo() { auto CreateNodeClassMetadata = []() -> FNodeClassMetadata { const FName DataTypeName = GetMetasoundDataTypeName(); const FName OperatorName = TEXT("Concat"); const FText NodeDisplayName = FText::Format(LOCTEXT("ArrayOpArrayConcatDisplayNamePattern", "Concatenate ({0})"), GetMetasoundDataTypeDisplayText()); const FText NodeDescription = LOCTEXT("ArrayOpArrayConcatDescription", "Concatenates two arrays on trigger."); const FVertexInterface NodeInterface = GetDefaultInterface(); return MetasoundArrayNodesPrivate::CreateArrayNodeClassMetadata(DataTypeName, OperatorName, NodeDisplayName, NodeDescription, NodeInterface); }; static const FNodeClassMetadata Metadata = CreateNodeClassMetadata(); return Metadata; } static TUniquePtr CreateOperator(const FCreateOperatorParams& InParams, TArray>& OutErrors) { using namespace ArrayNodeVertexNames; using namespace MetasoundArrayNodesPrivate; const FInputVertexInterface& Inputs = InParams.Node.GetVertexInterface().GetInputInterface(); TDataReadReference Trigger = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputTriggerName(), InParams.OperatorSettings); FArrayDataReadReference LeftArray = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputLeftArrayName(), InParams.OperatorSettings); FArrayDataReadReference RightArray = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputRightArrayName(), InParams.OperatorSettings); FArrayDataWriteReference OutArray = TDataWriteReferenceFactory::CreateExplicitArgs(InParams.OperatorSettings); return MakeUnique(Trigger, LeftArray, RightArray, OutArray); } TArrayConcatOperator(TDataReadReference InTrigger, FArrayDataReadReference InLeftArray, FArrayDataReadReference InRightArray, FArrayDataWriteReference InOutArray) : Trigger(InTrigger) , LeftArray(InLeftArray) , RightArray(InRightArray) , OutArray(InOutArray) { } virtual ~TArrayConcatOperator() = default; virtual FDataReferenceCollection GetInputs() const override { using namespace ArrayNodeVertexNames; FDataReferenceCollection Inputs; Inputs.AddDataReadReference(GetInputTriggerName(), Trigger); Inputs.AddDataReadReference(GetInputLeftArrayName(), LeftArray); Inputs.AddDataReadReference(GetInputRightArrayName(), RightArray); return Inputs; } virtual FDataReferenceCollection GetOutputs() const override { using namespace ArrayNodeVertexNames; FDataReferenceCollection Outputs; Outputs.AddDataReadReference(GetOutputArrayName(), OutArray); return Outputs; } void Execute() { if (*Trigger) { *OutArray = *LeftArray; OutArray->Append(*RightArray); } } private: TDataReadReference Trigger; FArrayDataReadReference LeftArray; FArrayDataReadReference RightArray; FArrayDataWriteReference OutArray; }; template class TArrayConcatNode : public FNodeFacade { public: TArrayConcatNode(const FNodeInitData& InInitData) : FNodeFacade(InInitData.InstanceName, InInitData.InstanceID, TFacadeOperatorClass>()) { } virtual ~TArrayConcatNode() = default; }; /** TArraySubsetOperator slices an array on trigger. */ template class TArraySubsetOperator : public TExecutableOperator> { public: using FArrayDataReadReference = TDataReadReference; using FArrayDataWriteReference = TDataWriteReference; using ElementType = typename MetasoundArrayNodesPrivate::TArrayElementType::Type; static const FVertexInterface& GetDefaultInterface() { using namespace ArrayNodeVertexNames; static const FVertexInterface DefaultInterface( FInputVertexInterface( TInputDataVertexModel(GetInputTriggerName(), LOCTEXT("ArrayOpArraySubsetTrigger", "Trigger to set value.")), TInputDataVertexModel(GetInputArrayName(), LOCTEXT("ArrayOpArraySubsetInputLeft", "Input Array.")), TInputDataVertexModel(GetInputStartIndexName(), LOCTEXT("ArrayOpArraySubsetStartIndex", "First index to include.")), TInputDataVertexModel(GetInputEndIndexName(), LOCTEXT("ArrayOpArraySubsetEndIndex", "Last index to include.")) ), FOutputVertexInterface( TOutputDataVertexModel(GetOutputArrayName(), LOCTEXT("ArrayOpArraySubsetOutput", "Subset of input array.")) ) ); return DefaultInterface; } static const FNodeClassMetadata& GetNodeInfo() { auto CreateNodeClassMetadata = []() -> FNodeClassMetadata { const FName DataTypeName = GetMetasoundDataTypeName(); const FName OperatorName = TEXT("Subset"); const FText NodeDisplayName = FText::Format(LOCTEXT("ArrayOpArraySubsetDisplayNamePattern", "Subset ({0})"), GetMetasoundDataTypeDisplayText()); const FText NodeDescription = LOCTEXT("ArrayOpArraySubsetDescription", "Subset array on trigger."); const FVertexInterface NodeInterface = GetDefaultInterface(); return MetasoundArrayNodesPrivate::CreateArrayNodeClassMetadata(DataTypeName, OperatorName, NodeDisplayName, NodeDescription, NodeInterface); }; static const FNodeClassMetadata Metadata = CreateNodeClassMetadata(); return Metadata; } static TUniquePtr CreateOperator(const FCreateOperatorParams& InParams, TArray>& OutErrors) { using namespace ArrayNodeVertexNames; using namespace MetasoundArrayNodesPrivate; const FInputVertexInterface& Inputs = InParams.Node.GetVertexInterface().GetInputInterface(); TDataReadReference Trigger = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputTriggerName(), InParams.OperatorSettings); FArrayDataReadReference InArray = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputArrayName(), InParams.OperatorSettings); TDataReadReference StartIndex = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputStartIndexName(), InParams.OperatorSettings); TDataReadReference EndIndex = InParams.InputDataReferences.GetDataReadReferenceOrConstructWithVertexDefault(Inputs, GetInputEndIndexName(), InParams.OperatorSettings); FArrayDataWriteReference OutArray = TDataWriteReferenceFactory::CreateExplicitArgs(InParams.OperatorSettings); return MakeUnique(Trigger, InArray, StartIndex, EndIndex, OutArray); } TArraySubsetOperator(TDataReadReference InTrigger, FArrayDataReadReference InInputArray, TDataReadReference InStartIndex, TDataReadReference InEndIndex, FArrayDataWriteReference InOutputArray) : Trigger(InTrigger) , InputArray(InInputArray) , StartIndex(InStartIndex) , EndIndex(InEndIndex) , OutputArray(InOutputArray) { } virtual ~TArraySubsetOperator() = default; virtual FDataReferenceCollection GetInputs() const override { using namespace ArrayNodeVertexNames; FDataReferenceCollection Inputs; Inputs.AddDataReadReference(GetInputTriggerName(), Trigger); Inputs.AddDataReadReference(GetInputArrayName(), InputArray); Inputs.AddDataReadReference(GetInputStartIndexName(), StartIndex); Inputs.AddDataReadReference(GetInputEndIndexName(), EndIndex); return Inputs; } virtual FDataReferenceCollection GetOutputs() const override { using namespace ArrayNodeVertexNames; FDataReferenceCollection Outputs; Outputs.AddDataReadReference(GetOutputArrayName(), OutputArray); return Outputs; } void Execute() { if (*Trigger) { OutputArray->Reset(); const ArrayType& InputArrayRef = *InputArray; const int32 StartIndexValue = FMath::Max(0, *StartIndex); const int32 EndIndexValue = FMath::Min(InputArrayRef.Num(), *EndIndex + 1); if (StartIndexValue < EndIndexValue) { const int32 Num = EndIndexValue - StartIndexValue; OutputArray->Append(&InputArrayRef[StartIndexValue], Num); } } } private: TDataReadReference Trigger; FArrayDataReadReference InputArray; TDataReadReference StartIndex; TDataReadReference EndIndex; FArrayDataWriteReference OutputArray; }; template class TArraySubsetNode : public FNodeFacade { public: TArraySubsetNode(const FNodeInitData& InInitData) : FNodeFacade(InInitData.InstanceName, InInitData.InstanceID, TFacadeOperatorClass>()) { } virtual ~TArraySubsetNode() = default; }; } // namespace Metasound #undef LOCTEXT_NAMESPACE