// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "MetasoundBuilderInterface.h" #include "MetasoundBuildError.h" #include "MetasoundNode.h" #include "MetasoundNodeInterface.h" #include "MetasoundNodeRegistrationMacro.h" #include "MetasoundOperatorInterface.h" #include "MetasoundDataFactory.h" #include "MetasoundDataReference.h" #include "MetasoundExecutableOperator.h" #include "MetasoundFrontend.h" #include "MetasoundFrontendNodesCategories.h" #include "MetasoundVertex.h" #include #define LOCTEXT_NAMESPACE "MetasoundFrontend" namespace Metasound { // Determines whether an auto converter node will be registered to convert // between two types. template struct TIsAutoConvertible { static constexpr bool bIsConvertible = std::is_convertible::value; // Handle case of converting enums to/from integers. static constexpr bool bIsIntToEnumConversion = std::is_same::value && TEnumTraits::bIsEnum; static constexpr bool bIsEnumToIntConversion = TEnumTraits::bIsEnum && std::is_same::value; static constexpr bool Value = bIsConvertible || bIsIntToEnumConversion || bIsEnumToIntConversion; }; // This convenience node can be registered and will invoke static_cast(FromDataType) every time it is executed, // with a special case for enum <-> int32 conversions. template class TAutoConverterNode : public FNode { static_assert(TIsAutoConvertible::Value, "Tried to create an auto converter node between two types we can't static_cast between."); public: static const FVertexName& GetInputName() { static const FVertexName InputName = GetMetasoundDataTypeName(); return InputName; } static const FVertexName& GetOutputName() { static const FVertexName OutputName = GetMetasoundDataTypeName(); return OutputName; } static FVertexInterface DeclareVertexInterface() { static const FText InputDesc = METASOUND_LOCTEXT_FORMAT("AutoConvDisplayNamePatternFrom", "Input {0} value.", GetMetasoundDataTypeDisplayText()); static const FText OutputDesc = METASOUND_LOCTEXT_FORMAT("AutoConvDisplayNamePatternTo", "Output {0} value.", GetMetasoundDataTypeDisplayText()); return FVertexInterface( FInputVertexInterface( TInputDataVertex(GetInputName(), FDataVertexMetadata{ InputDesc }) ), FOutputVertexInterface( TOutputDataVertex(GetOutputName(), FDataVertexMetadata{ OutputDesc }) ) ); } static const FNodeClassMetadata& GetAutoConverterNodeMetadata() { auto InitNodeInfo = []() -> FNodeClassMetadata { FNodeDisplayStyle DisplayStyle; DisplayStyle.bShowName = false; DisplayStyle.ImageName = TEXT("MetasoundEditor.Graph.Node.Conversion"); DisplayStyle.bShowInputNames = false; DisplayStyle.bShowOutputNames = false; const FText FromTypeText = GetMetasoundDataTypeDisplayText(); const FText ToTypeText = GetMetasoundDataTypeDisplayText(); FNodeClassMetadata Info; Info.ClassName = { TEXT("Convert"), GetMetasoundDataTypeName(), GetMetasoundDataTypeName() }; Info.MajorVersion = 1; Info.MinorVersion = 0; Info.DisplayName = METASOUND_LOCTEXT_FORMAT("Metasound_AutoConverterNodeDisplayNameFormat", "{0} to {1}", FromTypeText, ToTypeText); Info.Description = METASOUND_LOCTEXT_FORMAT("Metasound_AutoConverterNodeDescriptionNameFormat", "Converts from {0} to {1}.", FromTypeText, ToTypeText); Info.Author = PluginAuthor; Info.DisplayStyle = DisplayStyle; Info.PromptIfMissing = PluginNodeMissingPrompt; Info.DefaultInterface = DeclareVertexInterface(); Info.CategoryHierarchy.Emplace(NodeCategories::Conversions); if (TEnumTraits::bIsEnum || TEnumTraits::bIsEnum) { Info.CategoryHierarchy.Emplace(NodeCategories::EnumConversions); } Info.Keywords = { METASOUND_LOCTEXT("MetasoundConvertKeyword", "Convert"), GetMetasoundDataTypeDisplayText(), GetMetasoundDataTypeDisplayText() }; return Info; }; static const FNodeClassMetadata Info = InitNodeInfo(); return Info; } private: /** FConverterOperator converts from "FromDataType" to "ToDataType" using * a implicit conversion operators. */ class FConverterOperator : public TExecutableOperator { public: FConverterOperator(TDataReadReference InFromDataReference, TDataWriteReference InToDataReference) : FromData(InFromDataReference) , ToData(InToDataReference) { Execute(); } virtual ~FConverterOperator() {} virtual FDataReferenceCollection GetInputs() const override { FDataReferenceCollection Inputs; Inputs.AddDataReadReference(GetInputName(), FromData); return Inputs; } virtual FDataReferenceCollection GetOutputs() const override { FDataReferenceCollection Outputs; Outputs.AddDataReadReference(GetOutputName(), ToData); return Outputs; } void Execute() { // enum -> int32 if constexpr (TIsAutoConvertible::bIsEnumToIntConversion) { // Convert from enum wrapper to inner enum type, then to int typename TEnumTraits::InnerType InnerEnum = static_cast::InnerType>(*FromData); *ToData = static_cast(InnerEnum); } // int32 -> enum else if constexpr (TIsAutoConvertible::bIsIntToEnumConversion) { const int32 FromInt = *FromData; // Convert from int to inner enum type typename TEnumTraits::InnerType InnerEnum = static_cast::InnerType>(FromInt); // Update tracking for previous int value we tried to convert, used to prevent log spam if it's an invalid enum value if (FromInt != PreviousIntValueForEnumConversion) { PreviousIntValueForEnumConversion = FromInt; bHasLoggedInvalidEnum = false; } // If int value is invalid for this enum, return enum default value TOptional EnumName = ToDataType::ToName(InnerEnum); if (!EnumName.IsSet()) { if (!bHasLoggedInvalidEnum) { UE_LOG(LogMetaSound, Warning, TEXT("Cannot convert int32 value '%d' to enum type '%s'. No valid corresponding enum value exists, so returning enum default value instead."), FromInt, *GetMetasoundDataTypeDisplayText().ToString()); bHasLoggedInvalidEnum = true; } *ToData = static_cast(TEnumTraits::DefaultValue); } else { // Convert from inner enum type to int *ToData = static_cast(InnerEnum); } } else { *ToData = static_cast(*FromData); } } private: TDataReadReference FromData; TDataWriteReference ToData; // To prevent log spam, keep track of whether we've logged an invalid enum value being converted already // and the previous int value (need both bool and int for the initial case) bool bHasLoggedInvalidEnum = false; int32 PreviousIntValueForEnumConversion = 0; }; /** FConverterOperatorFactory creates an operator which converts from * "FromDataType" to "ToDataType". */ class FCoverterOperatorFactory : public IOperatorFactory { public: FCoverterOperatorFactory() = default; virtual TUniquePtr CreateOperator(const FBuildOperatorParams& InParams, FBuildResults& OutResults) override { TDataWriteReference WriteReference = TDataWriteReferenceFactory::CreateAny(InParams.OperatorSettings); const FVertexName& InputName = GetInputName(); const bool bContainsRef = InParams.InputData.IsVertexBound(InputName); if (bContainsRef) { TDataReadReference ReadReference = InParams.InputData.GetDataReadReference(InputName); return MakeUnique(ReadReference, WriteReference); } if constexpr (TIsParsable::Value) { TDataReadReference ReadReference = TDataReadReferenceFactory::CreateAny(InParams.OperatorSettings); return MakeUnique(ReadReference, WriteReference); } // Converter node requires parsable reference if input not connected. Report as an error. if (ensure(InParams.Node.GetVertexInterface().ContainsInputVertex(InputName))) { FInputDataDestination Dest(InParams.Node, InParams.Node.GetVertexInterface().GetInputVertex(GetInputName())); AddBuildError(OutResults.Errors, Dest); } return TUniquePtr(nullptr); } }; public: TAutoConverterNode(const FNodeInitData& InInitData) : FNode(InInitData.InstanceName, InInitData.InstanceID, GetAutoConverterNodeMetadata()) , Interface(DeclareVertexInterface()) , Factory(MakeOperatorFactoryRef()) { } virtual ~TAutoConverterNode() = default; 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 FOperatorFactorySharedRef GetDefaultOperatorFactory() const override { return Factory; } private: FVertexInterface Interface; FOperatorFactorySharedRef Factory; }; } // namespace Metasound #undef LOCTEXT_NAMESPACE