// Copyright Epic Games, Inc. All Rights Reserved. #include "EngineTestMetaSoundAutomatedNodeTest.h" #include "Algo/AllOf.h" #include "AudioDevice.h" #include "AudioDeviceManager.h" #include "AudioMixerDevice.h" #include "Containers/Array.h" #include "Containers/UnrealString.h" #include "Interfaces/MetasoundFrontendSourceInterface.h" #include "Math/NumericLimits.h" #include "Misc/AutomationTest.h" #include "MetasoundAudioBuffer.h" #include "MetasoundFrontendDataTypeRegistry.h" #include "MetasoundFrontendRegistries.h" #include "MetasoundFrontendSearchEngine.h" #include "MetasoundLog.h" #include "MetasoundOperatorBuilder.h" #include "MetasoundPrimitives.h" #include "MetasoundVariableNodes.h" #include "MetasoundVertex.h" #include "MetasoundVertexData.h" #include "Templates/UniquePtr.h" #include "Tests/AutomationCommon.h" #if WITH_DEV_AUTOMATION_TESTS #if WITH_EDITORONLY_DATA namespace Metasound::EngineTest{ // Return audio mixer device if one is available Audio::FMixerDevice* GetAudioMixerDevice() { if (FAudioDeviceManager* DeviceManager = FAudioDeviceManager::Get()) { if (FAudioDevice* AudioDevice = DeviceManager->GetMainAudioDeviceRaw()) { return static_cast(AudioDevice); } } return nullptr; } // Create an example environment that generally exists for a UMetaSoundSoruce FMetasoundEnvironment GetSourceEnvironmentForTest() { using namespace Frontend; FMetasoundEnvironment Environment; Environment.SetValue(CoreInterface::Environment::InstanceID, 0); Environment.SetValue>(CoreInterface::Environment::GraphHierarchy, TArray({ FGuid() })); Environment.SetValue(SourceInterface::Environment::SoundUniqueID, 0); Environment.SetValue(SourceInterface::Environment::IsPreview, false); Environment.SetValue(SourceInterface::Environment::TransmitterID, 0); Environment.SetValue(SourceInterface::Environment::GraphName, TEXT("ENGINE_TEST_REGISTERED_NODES")); if (Audio::FMixerDevice* MixerDevice = GetAudioMixerDevice()) { Environment.SetValue(SourceInterface::Environment::DeviceID, MixerDevice->DeviceID); Environment.SetValue(SourceInterface::Environment::AudioMixerNumOutputFrames, MixerDevice->GetNumOutputFrames()); } return Environment; } FString GetPrettyName(const Frontend::FNodeClassRegistryKey& InRegistryKey) { const FTopLevelAssetPath ObjectPath = Frontend::IMetaSoundAssetManager::GetChecked().FindAssetPath(InRegistryKey); if (ObjectPath.IsValid()) { return ObjectPath.ToString(); } FMetasoundFrontendRegistryContainer* NodeRegistry = FMetasoundFrontendRegistryContainer::Get(); if (ensure(NodeRegistry)) { FMetasoundFrontendClass NodeClass; if (NodeRegistry->FindFrontendClassFromRegistered(InRegistryKey, NodeClass)) { return FString::Printf(TEXT("%s %s"), *NodeClass.Metadata.GetClassName().ToString(), *NodeClass.Metadata.GetVersion().ToString()); } } return TEXT(""); } // TTestTypeInfo converts test types to strings template struct TTestTypeInfo { static FString ToString(const DataType& InData) { return ::LexToString(InData); } }; template struct TTestTypeInfo> { static FString ToString(TArrayView InData) { return FString::Printf(TEXT("[%s]"), *FString::JoinBy(InData, TEXT(","), &TTestTypeInfo::ToString)); } }; template<> struct TTestTypeInfo : TTestTypeInfo> { }; template<> struct TTestTypeInfo { static FString ToString(const FTime& InData) { return ::LexToString(InData.GetSeconds()); } }; // TTestTypeAnalysis performs analysis operator on data types. template struct TTestTypeAnalysis { static bool IsValid(const DataType& InArray) { return true; } static bool IsEqual(const DataType& InLHS, const DataType& InRHS) { return InLHS == InRHS; } }; // Specialize type analysis for TArrays template struct TTestTypeAnalysis> { static bool IsValid(TArrayView InArray) { return Algo::AllOf(InArray, &TTestTypeAnalysis::IsValid); } static bool IsEqual(TArrayView InLHS, TArrayView InRHS) { const int32 Num = InLHS.Num(); if (Num == InRHS.Num()) { for (int32 i = 0; i < Num; i++) { if (InLHS[i] != InRHS[i]) { return false; } } return true; } return false; } }; // Specialize type analysis for audio buffers. template<> struct TTestTypeAnalysis : TTestTypeAnalysis> { }; // Specialize type analysis for float template<> struct TTestTypeAnalysis { static bool IsValid(float InValue) { return !FMath::IsNaN(InValue); } static bool IsEqual(float InLHS, float InRHS) { return InLHS == InRHS; } }; // Specialize type analysis for time template<> struct TTestTypeAnalysis { static bool IsValid(const FTime& InValue) { return FMath::IsFinite(InValue.GetSeconds()); } static bool IsEqual(const FTime& InLHS, const FTime& InRHS) { return InLHS == InRHS; } }; // TDataReferenceAnalyzer fulfils the IDataReferenceAnalyzer interface and uses // various templates to implement methods. template struct TDataReferenceAnalyzer : public IDataReferenceAnalyzer { virtual FAnyDataReference Copy(const FAnyDataReference& InDataRef) const override { if (const DataType* InValue = GetDataOrLogError(InDataRef)) { return FAnyDataReference{TDataValueReference::CreateNew(*InValue)}; } return InDataRef; } virtual bool IsEqual(const FAnyDataReference& InLHS, const FAnyDataReference& InRHS) const override { if (const DataType* InLHSValue = GetDataOrLogError(InLHS)) { if (const DataType* InRHSValue = GetDataOrLogError(InRHS)) { return TTestTypeAnalysis::IsEqual(*InLHSValue, *InRHSValue); } } return false; } virtual bool IsValid(const FAnyDataReference& InDataRef) const override { if (const DataType* InValue = GetDataOrLogError(InDataRef)) { return TTestTypeAnalysis::IsValid(*InValue); } return false; } virtual FString ToString(const FAnyDataReference& InDataRef) const override { if (const DataType* InValue = GetDataOrLogError(InDataRef)) { return TTestTypeInfo::ToString(*InValue); } return TEXT(""); } private: const DataType* GetDataOrLogError(const FAnyDataReference& InDataRef) const { const DataType* Value = InDataRef.GetValue(); if (nullptr == Value) { // Data references should never be null UE_LOG(LogMetaSound, Error, TEXT("Failed to get data type value of type %s"), *GetMetasoundDataTypeString()); } return Value; } }; // Register a data reference analyzer to support analyzing output of metasound nodes. template void AddDataReferenceAnalyzerToMap(TMap>& InMap) { InMap.Add(GetMetasoundDataTypeName(), MakeShared>()); } // A static registry of IDataReferenceAnalyzers const TMap>& GetDataTypeAnalyzerMap() { static TMap> Map; AddDataReferenceAnalyzerToMap(Map); AddDataReferenceAnalyzerToMap(Map); AddDataReferenceAnalyzerToMap(Map); AddDataReferenceAnalyzerToMap(Map); AddDataReferenceAnalyzerToMap(Map); AddDataReferenceAnalyzerToMap(Map); AddDataReferenceAnalyzerToMap(Map); AddDataReferenceAnalyzerToMap>(Map); AddDataReferenceAnalyzerToMap>(Map); AddDataReferenceAnalyzerToMap>(Map); AddDataReferenceAnalyzerToMap>(Map); AddDataReferenceAnalyzerToMap>(Map); return Map; } void FOutputVertexDataTestController::FAnalyzableOutput::CaptureValue() { CapturedValue = DataReferenceAnalyzer->Copy(DataReference); } bool FOutputVertexDataTestController::FAnalyzableOutput::IsDataReferenceValid() const { return DataReferenceAnalyzer->IsValid(DataReference); } bool FOutputVertexDataTestController::FAnalyzableOutput::IsDataReferenceEqualToCapturedValue() const { return DataReferenceAnalyzer->IsEqual(CapturedValue, DataReference); } FString FOutputVertexDataTestController::FAnalyzableOutput::DataReferenceToString() const { return DataReferenceAnalyzer->ToString(DataReference); } FString FOutputVertexDataTestController::FAnalyzableOutput::CapturedValueToString() const { return DataReferenceAnalyzer->ToString(CapturedValue); } FOutputVertexDataTestController::FOutputVertexDataTestController( const FOutputVertexInterface& InOutputInterface, const FOutputVertexInterfaceData& InOutputData) { const TMap>& AnalyzerMap = GetDataTypeAnalyzerMap(); for (const FOutputDataVertex& Vertex : InOutputInterface) { if (AnalyzerMap.Contains(Vertex.DataTypeName)) { if (const FAnyDataReference* Ref = InOutputData.FindDataReference(Vertex.VertexName)) { AnalyzableOutputs.Add(FAnalyzableOutput{*Ref, *Ref, Vertex.VertexName, AnalyzerMap[Vertex.DataTypeName]}); } } } } int32 FOutputVertexDataTestController::GetNumAnalyzableOutputs() const { return AnalyzableOutputs.Num(); } bool FOutputVertexDataTestController::AreAllAnalyzableOutputsValid() const { bool bAllAreValid = true; for (const FAnalyzableOutput& AnalyzableOutput : AnalyzableOutputs) { if (!AnalyzableOutput.IsDataReferenceValid()) { UE_LOG(LogMetaSound, Warning, TEXT("Invalid output encountered %s %s"), *AnalyzableOutput.VertexName.ToString(), *AnalyzableOutput.DataReferenceToString()); bAllAreValid = false; } } return bAllAreValid; } void FOutputVertexDataTestController::CaptureCurrentOutputValues() { for (FAnalyzableOutput& AnalyzableOutput : AnalyzableOutputs) { AnalyzableOutput.CaptureValue(); } } bool FOutputVertexDataTestController::AreAllOutputValuesEqualToCapturedValues() const { bool bAllAreEqual = true; for (const FAnalyzableOutput& AnalyzableOutput : AnalyzableOutputs) { if (!AnalyzableOutput.IsDataReferenceEqualToCapturedValue()) { UE_LOG(LogMetaSound, Warning, TEXT("Unequal output encountered %s found: %s expected: %s"), *AnalyzableOutput.VertexName.ToString(), *AnalyzableOutput.DataReferenceToString(), *AnalyzableOutput.CapturedValueToString()); bAllAreEqual = false; } } return bAllAreEqual; } // TTestTypeValues should return basic bounds for tested input data types. // Similar to TNumericLimits<> template struct TTestTypeValues {}; // TArray specialization to defer to a single element array with array element's values template struct TTestTypeValues> { static TArray Min(const FOperatorSettings& InSettings) { return TArray({TTestTypeValues::Min(InSettings)}); } static TArray Max(const FOperatorSettings& InSettings) { return TArray({TTestTypeValues::Max(InSettings)}); } static TArray Default(const FOperatorSettings& InSettings) { return TArray({TTestTypeValues::Default(InSettings)}); } static TArray Random(const FOperatorSettings& InSettings) { return TArray({TTestTypeValues::Random(InSettings)}); } }; template<> struct TTestTypeValues { static bool Min(const FOperatorSettings& InSettings) { return false; } static bool Max(const FOperatorSettings& InSettings) { return true; } static bool Default(const FOperatorSettings& InSettings) { return true; } static bool Random(const FOperatorSettings& InSettings) { return FMath::RandRange(0.f, 1.f) > 0.5; } }; template<> struct TTestTypeValues { static int32 Min(const FOperatorSettings& InSettings) { return TNumericLimits::Min(); } static int32 Max(const FOperatorSettings& InSettings) { return TNumericLimits::Max(); } static int32 Default(const FOperatorSettings& InSettings) { return 0; } static int32 Random(const FOperatorSettings& InSettings) { return FMath::RandRange(TNumericLimits::Min(), TNumericLimits::Max()); } }; template<> struct TTestTypeValues { static float Min(const FOperatorSettings& InSettings) { return TNumericLimits::Min(); } static float Max(const FOperatorSettings& InSettings) { return TNumericLimits::Max(); } static float Default(const FOperatorSettings& InSettings) { return 0.f; } static float Random(const FOperatorSettings& InSettings) { return FMath::RandRange(TNumericLimits::Min(), TNumericLimits::Max()); } }; template<> struct TTestTypeValues { static FTime Min(const FOperatorSettings& InSettings) { return FTime{TNumericLimits::Min()}; } static FTime Max(const FOperatorSettings& InSettings) { return FTime{TNumericLimits::Max()}; } static FTime Default(const FOperatorSettings& InSettings) { return FTime{0.f}; } static FTime Random(const FOperatorSettings& InSettings) { return FTime{FMath::RandRange(TNumericLimits::Min(), TNumericLimits::Max())}; } }; template<> struct TTestTypeValues { static FTrigger Min(const FOperatorSettings& InSettings) { return FTrigger{InSettings, false}; } static FTrigger Max(const FOperatorSettings& InSettings) { FTrigger Trigger{InSettings, false}; for (int32 i = 0; i < InSettings.GetNumFramesPerBlock(); i++) { Trigger.TriggerFrame(i); } return Trigger; } static FTrigger Default(const FOperatorSettings& InSettings) { return FTrigger{InSettings, true}; } static FTrigger Random(const FOperatorSettings& InSettings) { FTrigger Trigger{InSettings, false}; int32 NumTriggers = FMath::RandRange(0, InSettings.GetNumFramesPerBlock()); while (NumTriggers > 0) { Trigger.TriggerFrame(FMath::RandRange(0, InSettings.GetNumFramesPerBlock())); NumTriggers--; } return Trigger; } }; template<> struct TTestTypeValues { static FString Min(const FOperatorSettings& InSettings) { return TEXT(""); } static FString Max(const FOperatorSettings& InSettings) { return TEXT("THIS IS SUPPOSED TO REPRESENT A MAXIMUM STRING BUT THERE IS NO SUCH THING SO?"); } static FString Default(const FOperatorSettings& InSettings) { return TEXT("TestString"); } static FString Random(const FOperatorSettings& InSettings) { return TEXT("We should probably implement a random string."); } }; template struct TDataReferenceMutator : IDataReferenceMutator { virtual void SetDefault(const FOperatorSettings& InSettings, const FAnyDataReference& InDataRef) const override { *InDataRef.GetDataWriteReference() = TTestTypeValues::Default(InSettings); } virtual void SetMax(const FOperatorSettings& InSettings, const FAnyDataReference& InDataRef) const override { *InDataRef.GetDataWriteReference() = TTestTypeValues::Max(InSettings); } virtual void SetMin(const FOperatorSettings& InSettings, const FAnyDataReference& InDataRef) const override { *InDataRef.GetDataWriteReference() = TTestTypeValues::Min(InSettings); } virtual void SetRandom(const FOperatorSettings& InSettings, const FAnyDataReference& InDataRef) const override { *InDataRef.GetDataWriteReference() = TTestTypeValues::Random(InSettings); } virtual FString ToString(const FAnyDataReference& InDataRef) const override { if (const DataType* Data = InDataRef.GetValue()) { return FString::Printf(TEXT("%s:%s"), *GetMetasoundDataTypeString(), *TTestTypeInfo::ToString(*Data)); } else { // Data references should never be null UE_LOG(LogMetaSound, Error, TEXT("Failed to get data type value of type %s"), *GetMetasoundDataTypeString()); return TEXT(""); } } virtual void SetValue(const FAnyDataReference& InSrcDataRef, const FAnyDataReference& InDstDataRef) const override { *InDstDataRef.GetDataWriteReference() = *InSrcDataRef.GetValue(); } virtual FAnyDataReference Copy(const FAnyDataReference& InDataRef) const override { if (const DataType* Data = InDataRef.GetValue()) { return FAnyDataReference{TDataValueReference::CreateNew(*Data)}; } else { // Data references should never be null UE_LOG(LogMetaSound, Error, TEXT("Failed to get data type value of type %s"), *GetMetasoundDataTypeString()); return FAnyDataReference{TDataValueReference::CreateNew()}; // we are going to crash soon. } } }; template void AddDataReferenceMutatorEntryToMap(TMap>& InMap) { InMap.Add(GetMetasoundDataTypeName(), MakeShared>()); } // Returns map of mutable input types const TMap>& GetDataTypeGeneratorMap() { static TMap> Map; AddDataReferenceMutatorEntryToMap(Map); AddDataReferenceMutatorEntryToMap(Map); AddDataReferenceMutatorEntryToMap(Map); AddDataReferenceMutatorEntryToMap(Map); AddDataReferenceMutatorEntryToMap(Map); AddDataReferenceMutatorEntryToMap(Map); AddDataReferenceMutatorEntryToMap>(Map); AddDataReferenceMutatorEntryToMap>(Map); AddDataReferenceMutatorEntryToMap>(Map); AddDataReferenceMutatorEntryToMap>(Map); AddDataReferenceMutatorEntryToMap>(Map); AddDataReferenceMutatorEntryToMap>(Map); return Map; } // Convenience class for setting node input data reference values to default, min, max or random values. FInputVertexDataTestController::FInputVertexDataTestController(const FOperatorSettings& InSettings, const FInputVertexInterface& InInputInterface, const FInputVertexInterfaceData& InInputData) : Settings(InSettings) { const TMap>& GeneratorMap = GetDataTypeGeneratorMap(); for (const FInputDataVertex& Vertex : InInputInterface) { if (GeneratorMap.Contains(Vertex.DataTypeName)) { if (const FAnyDataReference* Ref = InInputData.FindDataReference(Vertex.VertexName)) { if (EDataReferenceAccessType::Write == Ref->GetAccessType()) { MutableInputs.Add(FMutableInput{*Ref, Vertex.VertexName, GeneratorMap[Vertex.DataTypeName]}); } } } } } int32 FInputVertexDataTestController::GetNumMutableInputs() const { return MutableInputs.Num(); } FInterfaceState FInputVertexDataTestController::GetInterfaceState() const { FInterfaceState State; for (const FMutableInput& MutableInput : MutableInputs) { State.Add(MutableInput.VertexName, MutableInput.DataReferenceMutator->Copy(MutableInput.DataReference)); } return State; } void FInputVertexDataTestController::SetMutableInputsToState(const FInterfaceState& InState) { for (const FMutableInput& MutableInput : MutableInputs) { if (const FAnyDataReference* Value = InState.Find(MutableInput.VertexName)) { MutableInput.DataReferenceMutator->SetValue(*Value, MutableInput.DataReference); } } UE_LOG(LogMetaSound, Verbose, TEXT("Setting operator input values:%s%s"), LINE_TERMINATOR, *FString::Join(GetInputValueStrings(), LINE_TERMINATOR)); } void FInputVertexDataTestController::SetMutableInputsToMin() { for (const FMutableInput& MutableInput : MutableInputs) { MutableInput.DataReferenceMutator->SetMin(Settings, MutableInput.DataReference); } UE_LOG(LogMetaSound, Verbose, TEXT("Setting operator input values:%s%s"), LINE_TERMINATOR, *FString::Join(GetInputValueStrings(), LINE_TERMINATOR)); } void FInputVertexDataTestController::SetMutableInputsToMax() { for (const FMutableInput& MutableInput : MutableInputs) { MutableInput.DataReferenceMutator->SetMax(Settings, MutableInput.DataReference); } UE_LOG(LogMetaSound, Verbose, TEXT("Setting operator input values:%s%s"), LINE_TERMINATOR, *FString::Join(GetInputValueStrings(), LINE_TERMINATOR)); } void FInputVertexDataTestController::SetMutableInputsToDefault() { for (const FMutableInput& MutableInput : MutableInputs) { MutableInput.DataReferenceMutator->SetDefault(Settings, MutableInput.DataReference); } UE_LOG(LogMetaSound, Verbose, TEXT("Setting operator input values:%s%s"), LINE_TERMINATOR, *FString::Join(GetInputValueStrings(), LINE_TERMINATOR)); } void FInputVertexDataTestController::SetMutableInputsToRandom() { for (const FMutableInput& MutableInput : MutableInputs) { MutableInput.DataReferenceMutator->SetRandom(Settings, MutableInput.DataReference); } UE_LOG(LogMetaSound, Verbose, TEXT("Setting operator input values:%s%s"), LINE_TERMINATOR, *FString::Join(GetInputValueStrings(), LINE_TERMINATOR)); } TArray FInputVertexDataTestController::GetInputValueStrings() const { TArray ValueStrings; for (const FMutableInput& MutableInput : MutableInputs) { ValueStrings.Add(FString::Printf(TEXT("%s %s"), *MutableInput.VertexName.ToString(), *MutableInput.DataReferenceMutator->ToString(MutableInput.DataReference))); } return ValueStrings; } static const FLazyName TestNodeName{"TEST_NODE"}; static const FLazyName TestVertexName{"TEXT_VERTEX"}; static const FGuid TestNodeID{0xA5A5A5A5, 0xA5A5A5A5, 0xA5A5A5A5, 0xA5A5A5A5}; // Create a node from a node registry key TUniquePtr CreateNodeFromRegistry(const Frontend::FNodeRegistryKey& InNodeRegistryKey) { using namespace Frontend; TUniquePtr Node; FMetasoundFrontendRegistryContainer* NodeRegistry = FMetasoundFrontendRegistryContainer::Get(); check(nullptr != NodeRegistry); IDataTypeRegistry& DataTypeRegistry = IDataTypeRegistry::Get(); // Lookup node class metadata to determine how to create this node. FMetasoundFrontendClass NodeClass; if (!NodeRegistry->FindFrontendClassFromRegistered(InNodeRegistryKey, NodeClass)) { UE_LOG(LogMetaSound, Error, TEXT("Failed to find registered class with registry key %s"), *InNodeRegistryKey.ToString()); return MoveTemp(Node); } // Build node differently dependent upon node type switch (NodeClass.Metadata.GetType()) { case EMetasoundFrontendClassType::VariableDeferredAccessor: case EMetasoundFrontendClassType::VariableAccessor: case EMetasoundFrontendClassType::VariableMutator: case EMetasoundFrontendClassType::External: case EMetasoundFrontendClassType::Graph: { FVertexInterface VertexInterface; NodeRegistry->FindDefaultVertexInterface(InNodeRegistryKey, VertexInterface); Node = NodeRegistry->CreateNode(InNodeRegistryKey, FNodeData{ TestNodeName, TestNodeID , VertexInterface }); } break; case EMetasoundFrontendClassType::Input: { FName DataTypeName = NodeClass.Metadata.GetClassName().Name; TSharedPtr InputClassMetadata = DataTypeRegistry.GetInputClassMetadata(DataTypeName); checkf(InputClassMetadata, TEXT("Input class metadata could not be found from data type registry")); FVertexInterface InputNodeInterface = InputClassMetadata->DefaultInterface; // Vertex names must be set for input nodes InputNodeInterface.GetInputInterface().At(0).VertexName = TestVertexName; InputNodeInterface.GetOutputInterface().At(0).VertexName = TestVertexName; InputNodeInterface.GetInputInterface().At(0).SetDefaultLiteral(NodeClass.GetDefaultInterface().Inputs[0].FindConstDefaultChecked(Frontend::DefaultPageID).ToLiteral(DataTypeName)); Node = DataTypeRegistry.CreateInputNode(DataTypeName, FNodeData{ TestNodeName, TestNodeID, InputNodeInterface }); } break; case EMetasoundFrontendClassType::Variable: { FName DataTypeName = NodeClass.Metadata.GetClassName().Name; TSharedPtr VariableMetadata = DataTypeRegistry.GetVariableClassMetadata(DataTypeName); checkf(VariableMetadata, TEXT("Variable class metadata could not be found from data type registry")); Node = DataTypeRegistry.CreateVariableNode(DataTypeName, DataTypeRegistry.CreateDefaultLiteral(DataTypeName), FNodeData{ TestNodeName, TestNodeID, VariableMetadata->DefaultInterface }); } break; case EMetasoundFrontendClassType::Literal: { FName DataTypeName = NodeClass.Metadata.GetClassName().Name; PRAGMA_DISABLE_DEPRECATION_WARNINGS FDefaultLiteralNodeConstructorParams NodeInitData{TestNodeName, TestNodeID, DataTypeRegistry.CreateDefaultLiteral(DataTypeName)}; Node = DataTypeRegistry.CreateLiteralNode(DataTypeName, MoveTemp(NodeInitData)); PRAGMA_ENABLE_DEPRECATION_WARNINGS } break; case EMetasoundFrontendClassType::Output: { FName DataTypeName = NodeClass.Metadata.GetClassName().Name; TSharedPtr OutputClassMetadata = DataTypeRegistry.GetOutputClassMetadata(DataTypeName); checkf(OutputClassMetadata, TEXT("Output class metadata could not be found from data type registry")); FVertexInterface OutputNodeInterface = OutputClassMetadata->DefaultInterface; // Vertex names must be set for output nodes OutputNodeInterface.GetInputInterface().At(0).VertexName = TestVertexName; OutputNodeInterface.GetOutputInterface().At(0).VertexName = TestVertexName; Node = DataTypeRegistry.CreateOutputNode(DataTypeName, FNodeData{ TestNodeName, TestNodeID, OutputNodeInterface }); } break; case EMetasoundFrontendClassType::Template: default: static_assert(static_cast(EMetasoundFrontendClassType::Invalid) == 10, "Possible missed EMetasoundFrontendClassType case coverage"); } return MoveTemp(Node); } void GetAllRegisteredNodes(TArray& OutBeautifiedNames, TArray & OutTestCommands, bool bInIncludeDeprecatedNodes) { using namespace Metasound; // Get all the classes that have been registered Frontend::ISearchEngine& NodeSearchEngine = Frontend::ISearchEngine::Get(); TArray AllClasses = NodeSearchEngine.FindAllClasses(true /* IncludeAllVersions */); for (const FMetasoundFrontendClass& NodeClass : AllClasses) { // Exclude template classes because they cannot be created directly from the node registry if (NodeClass.Metadata.GetType() == EMetasoundFrontendClassType::Template) { continue; } else if (!bInIncludeDeprecatedNodes && NodeClass.Metadata.GetIsDeprecated()) { // Optionally skip deprecated classes. continue; } OutBeautifiedNames.Add(FString::Printf(TEXT("%s %s"), *NodeClass.Metadata.GetClassName().ToString(), *NodeClass.Metadata.GetVersion().ToString())); // Test commands are node registry keys Frontend::FNodeRegistryKey NodeRegistryKey(NodeClass.Metadata); OutTestCommands.Add(NodeRegistryKey.ToString()); } } void GetAllRegisteredNativeNodes(TArray& OutBeautifiedNames, TArray & OutTestCommands, bool bInIncludeDeprecatedNodes) { using namespace Metasound; // Get all the classes that have been registered Frontend::ISearchEngine& NodeSearchEngine = Frontend::ISearchEngine::Get(); TArray AllClasses = NodeSearchEngine.FindAllClasses(true /* IncludeAllVersions */); FMetasoundFrontendRegistryContainer* NodeRegistry = FMetasoundFrontendRegistryContainer::Get(); check(nullptr != NodeRegistry); Frontend::IMetaSoundAssetManager* AssetManager = Frontend::IMetaSoundAssetManager::Get(); for (const FMetasoundFrontendClass& NodeClass : AllClasses) { // Exclude template classes because they cannot be created directly from the node registry if (NodeClass.Metadata.GetType() == EMetasoundFrontendClassType::Template) { continue; } else if (!bInIncludeDeprecatedNodes && NodeClass.Metadata.GetIsDeprecated()) { // Optionally skip deprecated classes. continue; } Frontend::FNodeRegistryKey NodeRegistryKey(NodeClass.Metadata); // Exclude asset-defined node classes if (AssetManager && AssetManager->ContainsKey(NodeRegistryKey)) { continue; } OutBeautifiedNames.Add(FString::Printf(TEXT("%s %s"), *NodeClass.Metadata.GetClassName().ToString(), *NodeClass.Metadata.GetVersion().ToString())); // Test commands are node registry keys OutTestCommands.Add(NodeRegistryKey.ToString()); } } void CreateVariables(const FOperatorSettings& InOperatorSettings, FInputVertexInterfaceData& OutVertexData) { using namespace VertexDataPrivate; // There is currently no easy way to instantiate variables. The only way // they are instantiated is within the TVariableNode's IOperator. In order // to create Variable inputs to nodes, we // - Create a TVariableNode // - Create a IOperator from the TVariableNode // - Access the variable from the outputs of the IOperator Frontend::IDataTypeRegistry& DataTypeRegistry = Frontend::IDataTypeRegistry::Get(); for (const FInputBinding& Binding : OutVertexData) { const FInputDataVertex& InputVertex = Binding.GetVertex(); if (const Frontend::IDataTypeRegistryEntry* VariableEntry = DataTypeRegistry.FindDataTypeRegistryEntry(InputVertex.DataTypeName)) { if (VariableEntry->GetDataTypeInfo().bIsVariable) { // Find the data type name of the data type wrapped by the variable FName UnderlyingDataTypeName = *InputVertex.DataTypeName.ToString().Replace(TEXT(METASOUND_DATA_TYPE_NAME_VARIABLE_TYPE_SPECIFIER), TEXT("")); if (const Frontend::IDataTypeRegistryEntry* DataTypeEntry = DataTypeRegistry.FindDataTypeRegistryEntry(UnderlyingDataTypeName)) { TSharedPtr VariableNodeMetadata = DataTypeEntry->GetVariableClassMetadata(); if (!VariableNodeMetadata) { continue; } // Create a variable node for the underlying data type TUniquePtr VariableNode = DataTypeEntry->CreateVariableNode( DataTypeRegistry.CreateDefaultLiteral(UnderlyingDataTypeName), FNodeData{"VariableNodeName", FGuid::NewGuid(), VariableNodeMetadata->DefaultInterface}); if (VariableNode.IsValid()) { // Create the variable operator for the underlying data type FInputVertexInterfaceData InputData(VariableNode->GetVertexInterface().GetInputInterface()); FBuildOperatorParams BuildParams { *VariableNode, InOperatorSettings, InputData, FMetasoundEnvironment{} }; FBuildResults OutResults; TUniquePtr VariableOperator = VariableNode->GetDefaultOperatorFactory()->CreateOperator(BuildParams, OutResults); if (VariableOperator.IsValid()) { // Access the TVariable data referenced created within the operator. FOutputVertexInterfaceData OutOperatorData; VariableOperator->BindOutputs(OutOperatorData); if (const FAnyDataReference* VariableRef = OutOperatorData.FindDataReference(METASOUND_GET_PARAM_NAME(VariableNames::OutputVariable))) { OutVertexData.SetVertex(InputVertex.VertexName, *VariableRef); } } } } } } } } void CreateDefaultsAndVariables(const FOperatorSettings& InOperatorSettings, FInputVertexInterfaceData& OutVertexData) { Frontend::CreateDefaults(InOperatorSettings, OutVertexData); CreateVariables(InOperatorSettings, OutVertexData); } } #endif // WITH_EDITORONLY_DATA #endif //WITH_DEV_AUTOMATION_TESTS