// Copyright Epic Games, Inc. All Rights Reserved. #include "Interfaces/MetasoundFrontendInterface.h" #include "Algo/ForEach.h" #include "MetasoundFrontendDocument.h" namespace Metasound { namespace Frontend { namespace InterfacePrivate { bool IsLessThanVertex(const FMetasoundFrontendVertex& VertexA, const FMetasoundFrontendVertex& VertexB) { // For interfaces we can ignore metadata when comparing if (VertexA.Name == VertexB.Name) { return VertexA.TypeName.FastLess(VertexB.TypeName); } return VertexA.Name.FastLess(VertexB.Name); } bool IsLessThanEnvironmentVariable(const FMetasoundFrontendClassEnvironmentVariable& EnvironmentA, const FMetasoundFrontendClassEnvironmentVariable& EnvironmentB) { // For interfaces we can ignore metadata when comparing if (EnvironmentA.Name == EnvironmentB.Name) { return EnvironmentA.TypeName.FastLess(EnvironmentB.TypeName); } return EnvironmentA.Name.FastLess(EnvironmentB.Name); } bool IsEquivalentEnvironmentVariable(const FMetasoundFrontendClassEnvironmentVariable& EnvironmentA, const FMetasoundFrontendClassEnvironmentVariable& EnvironmentB) { // For interfaces we can ignore display name and tooltip return (EnvironmentA.Name == EnvironmentB.Name) && (EnvironmentA.TypeName == EnvironmentB.TypeName); } template TArray MakePointerToArrayElements(const TArray& InArray) { TArray PointerArray; for (const Type& Element : InArray) { PointerArray.Add(&Element); } return PointerArray; } template void SetDifference(const TArray& InSetA, const TArray& InSetB, TArray& OutUniqueA, TArray& OutUniqueB, ComparisonType Compare) { TArray SortedA = MakePointerToArrayElements(InSetA); SortedA.Sort(Compare); TArray SortedB = MakePointerToArrayElements(InSetB); SortedB.Sort(Compare); int32 IndexA = 0; int32 IndexB = 0; while ((IndexA < SortedA.Num()) && (IndexB < SortedB.Num())) { if (Compare(*SortedA[IndexA], *SortedB[IndexB])) { OutUniqueA.Add(SortedA[IndexA]); IndexA++; } else if (Compare(*SortedB[IndexB], *SortedA[IndexA])) { OutUniqueB.Add(SortedB[IndexB]); IndexB++; } else { // Equal. Increment both indices. IndexA++; IndexB++; } } if (IndexA < SortedA.Num()) { OutUniqueA.Append(&SortedA[IndexA], SortedA.Num() - IndexA); } if (IndexB < SortedB.Num()) { OutUniqueB.Append(&SortedB[IndexB], SortedB.Num() - IndexB); } } template bool IsSetEquivalent(const TArray& InSetA, const TArray& InSetB, ComparisonType Compare) { TArray UniqueA; TArray UniqueB; SetDifference(InSetA, InSetB, UniqueA, UniqueB, Compare); return (UniqueA.Num() == 0) && (UniqueB.Num() == 0); } template bool IsSetIncluded(const TArray& InSubset, const TArray& InSuperset, ComparisonType Compare) { TArray UniqueSubset; TArray UniqueSuperset; SetDifference(InSubset, InSuperset, UniqueSubset, UniqueSuperset, Compare); return UniqueSubset.Num() == 0; } template int32 SetDifferenceCount(const TArray& InSetA, const TArray& InSetB, ComparisonType Compare) { TArray UniqueA; TArray UniqueB; SetDifference(InSetA, InSetB, UniqueA, UniqueB, Compare); return UniqueA.Num() + UniqueB.Num(); } } // namespace InterfacePrivate bool IsSubsetOfInterface(const FMetasoundFrontendInterface& InSubsetInterface, const FMetasoundFrontendInterface& InSupersetInterface) { using namespace InterfacePrivate; const bool bIsInputSetIncluded = IsSetIncluded(InSubsetInterface.Inputs, InSupersetInterface.Inputs, &IsLessThanVertex); const bool bIsOutputSetIncluded = IsSetIncluded(InSubsetInterface.Outputs, InSupersetInterface.Outputs, &IsLessThanVertex); const bool bIsEnvironmentVariableSetIncluded = IsSetIncluded(InSubsetInterface.Environment, InSupersetInterface.Environment, &IsLessThanEnvironmentVariable); return bIsInputSetIncluded && bIsOutputSetIncluded && bIsEnvironmentVariableSetIncluded; } bool IsSubsetOfClass(const FMetasoundFrontendInterface& InSubsetInterface, const FMetasoundFrontendClass& InSupersetClass) { return IsSubsetOfClassInterface(InSubsetInterface, InSupersetClass.GetDefaultInterface()); } bool IsSubsetOfClassInterface(const FMetasoundFrontendInterface& InSubsetInterface, const FMetasoundFrontendClassInterface& InSupersetClassInterface) { // TODO: Environment variables are ignored as they are poorly supported and classes describe which environment variables are required, // not which are supported using namespace InterfacePrivate; const bool bIsInputSetIncluded = IsSetIncluded(InSubsetInterface.Inputs, InSupersetClassInterface.Inputs, &IsLessThanVertex); const bool bIsOutputSetIncluded = IsSetIncluded(InSubsetInterface.Outputs, InSupersetClassInterface.Outputs, &IsLessThanVertex); return bIsInputSetIncluded && bIsOutputSetIncluded; } bool IsEquivalentInterface(const FMetasoundFrontendInterface& InInputInterface, const FMetasoundFrontendInterface& InTargetInterface) { using namespace InterfacePrivate; const bool bIsInputSetEquivalent = IsSetEquivalent(InTargetInterface.Inputs, InInputInterface.Inputs, &IsLessThanVertex); const bool bIsOutputSetEquivalent = IsSetEquivalent(InTargetInterface.Outputs, InInputInterface.Outputs, &IsLessThanVertex); const bool bIsEnvironmentVariableSetEquivalent = IsSetEquivalent(InTargetInterface.Environment, InInputInterface.Environment, &IsLessThanEnvironmentVariable); return bIsInputSetEquivalent && bIsOutputSetEquivalent && bIsEnvironmentVariableSetEquivalent; } bool IsEqualInterface(const FMetasoundFrontendInterface& InInputInterface, const FMetasoundFrontendInterface& InTargetInterface) { const bool bIsMetadataEqual = InInputInterface.Metadata.Version == InTargetInterface.Metadata.Version; if (bIsMetadataEqual) { // If metadata is equal, then inputs/outputs/environment variables should also be equal. // Consider updating interface version info if this ensure is hit. return ensure(IsEquivalentInterface(InInputInterface, InTargetInterface)); } return false; } bool DeclaredInterfaceVersionsMatch(const FMetasoundFrontendDocument& InDocumentA, const FMetasoundFrontendDocument& InDocumentB) { if (InDocumentA.Interfaces.Num() != InDocumentB.Interfaces.Num()) { return false; } return InDocumentA.Interfaces.Includes(InDocumentB.Interfaces); } int32 InputOutputDifferenceCount(const FMetasoundFrontendClass& InClass, const FMetasoundFrontendInterface& InInterface) { return InputOutputDifferenceCount(InClass.GetDefaultInterface(), InInterface); } int32 InputOutputDifferenceCount(const FMetasoundFrontendClassInterface& InClassInterface, const FMetasoundFrontendInterface& InInterface) { using namespace InterfacePrivate; int32 DiffCount = SetDifferenceCount(InClassInterface.Inputs, InInterface.Inputs, &IsLessThanVertex); DiffCount += SetDifferenceCount(InClassInterface.Outputs, InInterface.Outputs, &IsLessThanVertex); return DiffCount; } int32 InputOutputDifferenceCount(const FMetasoundFrontendInterface& InInterfaceA, const FMetasoundFrontendInterface& InInterfaceB) { using namespace InterfacePrivate; int32 DiffCount = SetDifferenceCount(InInterfaceA.Inputs, InInterfaceB.Inputs, &IsLessThanVertex); DiffCount += SetDifferenceCount(InInterfaceA.Outputs, InInterfaceB.Outputs, &IsLessThanVertex); return DiffCount; } void GatherRequiredEnvironmentVariables(const FMetasoundFrontendGraphClass& InRootGraph, const TArray& InDependencies, const TArray& InSubgraphs, TArray& OutEnvironmentVariables) { auto GatherRequiredEnvironmentVariablesFromClass = [&](const FMetasoundFrontendClass& InClass) { using namespace InterfacePrivate; // Environment variables are maintained on the default interface. Changing the environment variables in a // class interface override is not supported. for (const FMetasoundFrontendClassEnvironmentVariable& EnvVar : InClass.GetDefaultInterface().Environment) { if (EnvVar.bIsRequired) { auto IsEquivalentEnvVar = [&](const FMetasoundFrontendClassEnvironmentVariable& OtherEnvVar) { return IsEquivalentEnvironmentVariable(EnvVar, OtherEnvVar); }; // Basically same as TArray::AddUnique except uses the `IsEquivalentEnvironmentVariable` instead of `operator==` to test for uniqueness. if (nullptr == OutEnvironmentVariables.FindByPredicate(IsEquivalentEnvVar)) { OutEnvironmentVariables.Add(EnvVar); } } } }; GatherRequiredEnvironmentVariablesFromClass(InRootGraph); Algo::ForEach(InDependencies, GatherRequiredEnvironmentVariablesFromClass); Algo::ForEach(InSubgraphs, GatherRequiredEnvironmentVariablesFromClass); } const FMetasoundFrontendInterface* FindMostSimilarInterfaceSupportingEnvironment(const FMetasoundFrontendGraphClass& InRootGraph, const TArray& InDependencies, const TArray& InSubgraphs, const TArray& InCandidateInterfaces) { using namespace InterfacePrivate; const FMetasoundFrontendInterface* Result = nullptr; TArray CandidateInterfaces = MakePointerToArrayElements(InCandidateInterfaces); TArray AllEnvironmentVariables; GatherRequiredEnvironmentVariables(InRootGraph, InDependencies, InSubgraphs, AllEnvironmentVariables); // Remove all interfaces which do not provide all environment variables. auto DoesNotProvideAllEnvironmentVariables = [&](const FMetasoundFrontendInterface* CandidateInterface) { return !IsSetIncluded(AllEnvironmentVariables, CandidateInterface->Environment, &IsLessThanEnvironmentVariable); }; CandidateInterfaces.RemoveAll(DoesNotProvideAllEnvironmentVariables); // Return the interface with the least amount of differences. auto DifferencesFromRootGraph = [&](const FMetasoundFrontendInterface* CandidateInterface) -> int32 { return InputOutputDifferenceCount(InRootGraph.GetDefaultInterface(), *CandidateInterface); }; Algo::SortBy(CandidateInterfaces, DifferencesFromRootGraph); if (0 == CandidateInterfaces.Num()) { return nullptr; } return CandidateInterfaces[0]; } const FMetasoundFrontendInterface* FindMostSimilarInterfaceSupportingEnvironment(const FMetasoundFrontendDocument& InDocument, const TArray& InCandidateInterfaces) { return FindMostSimilarInterfaceSupportingEnvironment(InDocument.RootGraph, InDocument.Dependencies, InDocument.Subgraphs, InCandidateInterfaces); } } // namespace Frontend } // namespace Metasound