// Copyright Epic Games, Inc. All Rights Reserved. #include "MetasoundFrontendSubgraphNodeController.h" #include "Algo/Transform.h" namespace Metasound { namespace Frontend { namespace FrontendSubgraphNodeControllerPrivate { template void InplaceBidirectionalVertexSetDifference(TArray& SetA, TArray& SetB) { // TODO: comparison is done by `Name`. May update to a `ClassVertexID` in the future. // Reverse iterate to allow removal of elements from SetA within loop. for (int32 SetAIndex = (SetA.Num() - 1); SetAIndex >= 0; SetAIndex--) { const FString& SetAName = SetA[SetAIndex]->Name; int32 SetBIndex = SetB.IndexOfByPredicate([&](const SetBType* InVertex) { return InVertex->Name == SetAName; }); if (INDEX_NONE != SetBIndex) { SetA.RemoveAt(SetAIndex); SetB.RemoveAt(SetBIndex); } } } // Manipulates the NodeVertexArray to match the ClassVertexArray. template bool ConformNodeVertexArrayToClassVertexArray(TArray& NodeVertexArray, const TArray& ClassVertexArray) { // TODO: May need to change this to support dynamic pins. bool bDidAlterNodeVertexArray = false; TArray NodeVerticesToRemove; Algo::Transform(NodeVertexArray, NodeVerticesToRemove, [](const NodeVertexType& Vertex) { return &Vertex; }); TArray ClassVerticesToAdd; Algo::Transform(ClassVertexArray, ClassVerticesToAdd, [](const ClassVertexType& Vertex) { return &Vertex; }); FrontendSubgraphNodeControllerPrivate::InplaceBidirectionalVertexSetDifference(NodeVerticesToRemove, ClassVerticesToAdd); bDidAlterNodeVertexArray = (NodeVerticesToRemove.Num() > 0) || (ClassVerticesToAdd.Num() > 0); // Remove extra node vertices. for (const NodeVertexType* VertexToRemove : NodeVerticesToRemove) { NodeVertexArray.RemoveAll([&](const NodeVertexType& InVertex) { return InVertex.VertexID == VertexToRemove->VertexID; }); } // Add missing node vertices. for (const ClassVertexType* VertexToAdd : ClassVerticesToAdd) { NodeVertexType NewNodeVertex(*VertexToAdd); NewNodeVertex.VertexID = FGuid::NewGuid(); NodeVertexArray.Add(NewNodeVertex); } return bDidAlterNodeVertexArray; } } // // FSubgraphNodeController // FSubgraphNodeController::FSubgraphNodeController(EPrivateToken InToken, const FSubgraphNodeController::FInitParams& InParams) : FBaseNodeController({InParams.NodePtr, InParams.ClassPtr, InParams.OwningGraph}) , GraphPtr(InParams.GraphPtr) { } FNodeHandle FSubgraphNodeController::CreateNodeHandle(const FSubgraphNodeController::FInitParams& InParams) { if (FMetasoundFrontendNode* Node = InParams.NodePtr.Get()) { if (const FMetasoundFrontendClass* Class = InParams.ClassPtr.Get()) { // Cannot make a valid node handle if the node description and class description differ if (Node->ClassID == Class->ID) { return MakeShared(EPrivateToken::Token, InParams); } else { UE_LOG(LogMetaSound, Warning, TEXT("Frontend Node [NodeID:%s, ClassID:%s] is not of expected class class [ClassID:%s]"), *Node->ID.ToString(), *Node->ClassID.ToString(), *Class->ID.ToString()); } } } return FInvalidNodeController::GetInvalid(); } FConstNodeHandle FSubgraphNodeController::CreateConstNodeHandle(const FSubgraphNodeController::FInitParams& InParams) { if (FMetasoundFrontendNode* Node = InParams.NodePtr.Get()) { if (const FMetasoundFrontendClass* Class = InParams.ClassPtr.Get()) { // Cannot make a valid node handle if the node description and class description differ if (Node->ClassID == Class->ID) { return MakeShared(EPrivateToken::Token, InParams); } else { UE_LOG(LogMetaSound, Warning, TEXT("Frontend Node [NodeID:%s, ClassID:%s] is not of expected class class [ClassID:%s]"), *Node->ID.ToString(), *Node->ClassID.ToString(), *Class->ID.ToString()); } } } return FInvalidNodeController::GetInvalid(); } bool FSubgraphNodeController::IsValid() const { return FBaseNodeController::IsValid() && (nullptr != GraphPtr.Get()); } int32 FSubgraphNodeController::GetNumInputs() const { // TODO: Trigger ConformNodeInterfaceToClassInterface() on an // event callback rather than on every call to this function. const_cast(this)->ConformNodeInterfaceToClassInterface(); return Super::GetNumInputs(); } int32 FSubgraphNodeController::GetNumOutputs() const { // TODO: Trigger ConformNodeInterfaceToClassInterface() on an // event callback rather than on every call to this function. const_cast(this)->ConformNodeInterfaceToClassInterface(); return Super::GetNumOutputs(); } TArray FSubgraphNodeController::GetInputControllerParams() const { // TODO: Trigger ConformNodeInterfaceToClassInterface() on an // event callback rather than on every call to this function. const_cast(this)->ConformNodeInterfaceToClassInterface(); return Super::GetInputControllerParams(); } TArray FSubgraphNodeController::GetOutputControllerParams() const { // TODO: Trigger ConformNodeInterfaceToClassInterface() on an // event callback rather than on every call to this function. const_cast(this)->ConformNodeInterfaceToClassInterface(); return Super::GetOutputControllerParams(); } TArray FSubgraphNodeController::GetInputControllerParamsWithVertexName(const FString& InName) const { // TODO: Trigger ConformNodeInterfaceToClassInterface() on an // event callback rather than on every call to this function. const_cast(this)->ConformNodeInterfaceToClassInterface(); return Super::GetInputControllerParamsWithVertexName(InName); } TArray FSubgraphNodeController::GetOutputControllerParamsWithVertexName(const FString& InName) const { TArray Outputs; // TODO: Trigger ConformNodeInterfaceToClassInterface() on an // event callback rather than on every call to this function. const_cast(this)->ConformNodeInterfaceToClassInterface(); return Super::GetOutputControllerParamsWithVertexName(InName); } bool FSubgraphNodeController::FindInputControllerParamsWithID(FGuid InVertexID, FInputControllerParams& OutParams) const { // TODO: Trigger ConformNodeInterfaceToClassInterface() on an // event callback rather than on every call to this function. const_cast(this)->ConformNodeInterfaceToClassInterface(); return Super::FindInputControllerParamsWithID(InVertexID, OutParams); } bool FSubgraphNodeController::FindOutputControllerParamsWithID(FGuid InVertexID, FOutputControllerParams& OutParams) const { // TODO: Trigger ConformNodeInterfaceToClassInterface() on an // event callback rather than on every call to this function. const_cast(this)->ConformNodeInterfaceToClassInterface(); return Super::FindOutputControllerParamsWithID(InVertexID, OutParams); } void FSubgraphNodeController::ConformNodeInterfaceToClassInterface() { using namespace FrontendSubgraphNodeControllerPrivate; // TODO: will need to update this logic to handle external classes and // dynamic interfaces. These updates will get piped down to the INodeClass // interface. if (FMetasoundFrontendNode* Node = NodePtr.Get()) { if (const FMetasoundFrontendClass* NodeClass = ClassPtr.Get()) { ConformNodeVertexArrayToClassVertexArray(Node->Interface.Inputs, NodeClass->Interface.Inputs); ConformNodeVertexArrayToClassVertexArray(Node->Interface.Outputs, NodeClass->Interface.Outputs); // TODO: Conform environment variables by aggregating all dependent environment variables. } } } FDocumentAccess FSubgraphNodeController::ShareAccess() { FDocumentAccess Access = FBaseNodeController::ShareAccess(); Access.Graph = GraphPtr; Access.ConstGraph = GraphPtr; return Access; } FConstDocumentAccess FSubgraphNodeController::ShareAccess() const { FConstDocumentAccess Access = FBaseNodeController::ShareAccess(); Access.ConstGraph = GraphPtr; return Access; } FInputHandle FSubgraphNodeController::CreateInputController(FGuid InVertexID, FConstVertexAccessPtr InNodeVertexPtr, FConstClassInputAccessPtr InClassInputPtr, FNodeHandle InOwningNode) const { return MakeShared(FBaseInputController::FInitParams{InVertexID, InNodeVertexPtr, InClassInputPtr, GraphPtr, InOwningNode}); } FOutputHandle FSubgraphNodeController::CreateOutputController(FGuid InVertexID, FConstVertexAccessPtr InNodeVertexPtr, FConstClassOutputAccessPtr InClassOutputPtr, FNodeHandle InOwningNode) const { return MakeShared(FBaseOutputController::FInitParams{InVertexID, InNodeVertexPtr, InClassOutputPtr, GraphPtr, InOwningNode}); } } }