Files
UnrealEngine/Engine/Plugins/Runtime/Metasound/Source/MetasoundFrontend/Private/MetasoundFrontendGraphController.cpp
2025-05-18 13:04:45 +08:00

2385 lines
83 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MetasoundFrontendGraphController.h"
#include "Algo/NoneOf.h"
#include "Internationalization/Text.h"
#include "MetasoundDocumentInterface.h"
#include "MetasoundFrontendDocument.h"
#include "MetasoundFrontendDocumentAccessPtr.h"
#include "MetasoundFrontendDocumentBuilder.h"
#include "MetasoundFrontendDocumentIdGenerator.h"
#include "MetasoundFrontendGraph.h"
#include "MetasoundFrontendNodeClassRegistry.h"
#include "MetasoundFrontendNodeController.h"
#include "MetasoundFrontendNodeTemplateRegistry.h"
#include "MetasoundFrontendProxyDataCache.h"
#include "MetasoundFrontendSubgraphNodeController.h"
#include "MetasoundFrontendInvalidController.h"
#include "MetasoundFrontendVariableController.h"
#include "MetasoundOperatorBuilder.h"
#include "MetasoundVariableNodes.h"
#include "Misc/Guid.h"
#include "Templates/SharedPointer.h"
#define LOCTEXT_NAMESPACE "MetasoundFrontendGraphController"
namespace Metasound
{
namespace Frontend
{
const FMetasoundFrontendGraph& FindConstBuildGraphChecked(const FMetasoundFrontendGraphClass& InGraphClass)
{
// Registry can be null in test builds for legacy controller implementation, so doesn't use GetChecked
if (IDocumentBuilderRegistry* BuilderRegistry = IDocumentBuilderRegistry::Get())
{
if (const FMetaSoundFrontendDocumentBuilder* Builder = BuilderRegistry->FindBuilder(InGraphClass.Metadata.GetClassName(), { }))
{
return Builder->FindConstBuildGraphChecked();
}
}
return InGraphClass.GetConstDefaultGraph();
}
FMetasoundFrontendGraph& FindBuildGraphChecked(FMetasoundFrontendGraphClass& InGraphClass)
{
// Registry can be null in test builds for legacy controller implementation, so doesn't use GetChecked
if (IDocumentBuilderRegistry* BuilderRegistry = IDocumentBuilderRegistry::Get())
{
if (FMetaSoundFrontendDocumentBuilder* Builder = BuilderRegistry->FindBuilder(InGraphClass.Metadata.GetClassName(), { }))
{
// const cast to dissuade mutable accessor for external API as the controller API is actively deprecated across engine releases.
return const_cast<FMetasoundFrontendGraph&>(Builder->FindConstBuildGraphChecked());
}
}
return InGraphClass.GetDefaultGraph();
}
//
// FGraphController
//
FGraphController::FGraphController(EPrivateToken InToken, const FGraphController::FInitParams& InParams)
: GraphClassPtr(InParams.GraphClassPtr)
, OwningDocument(InParams.OwningDocument)
{
}
FGraphHandle FGraphController::CreateGraphHandle(const FGraphController::FInitParams& InParams)
{
if (FMetasoundFrontendGraphClass* GraphClass = InParams.GraphClassPtr.Get())
{
if (GraphClass->Metadata.GetType() == EMetasoundFrontendClassType::Graph)
{
return MakeShared<FGraphController>(EPrivateToken::Token, InParams);
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Failed to make graph controller [ClassID:%s]. Class must be EMeatsoundFrontendClassType::Graph."), *GraphClass->ID.ToString())
}
}
return IGraphController::GetInvalidHandle();
}
FConstGraphHandle FGraphController::CreateConstGraphHandle(const FGraphController::FInitParams& InParams)
{
if (const FMetasoundFrontendGraphClass* GraphClass = InParams.GraphClassPtr.Get())
{
if (GraphClass->Metadata.GetType() == EMetasoundFrontendClassType::Graph)
{
return MakeShared<FGraphController>(EPrivateToken::Token, InParams);
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Failed to make graph controller [ClassID:%s]. Class must be EMeatsoundFrontendClassType::Graph."), *GraphClass->ID.ToString())
}
}
return IGraphController::GetInvalidHandle();
}
bool FGraphController::IsValid() const
{
return (nullptr != GraphClassPtr.Get()) && OwningDocument->IsValid();
}
FGuid FGraphController::GetClassID() const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->ID;
}
return Metasound::FrontendInvalidID;
}
#if WITH_EDITOR
FText FGraphController::GetDisplayName() const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->Metadata.GetDisplayName();
}
return Invalid::GetInvalidText();
}
#endif // WITH_EDITOR
TArray<FVertexName> FGraphController::GetInputVertexNames() const
{
TArray<FVertexName> Names;
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
for (const FMetasoundFrontendClassInput& Input : GraphClass->GetDefaultInterface().Inputs)
{
Names.Add(Input.Name);
}
}
return Names;
}
TArray<FVertexName> FGraphController::GetOutputVertexNames() const
{
TArray<FVertexName> Names;
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
for (const FMetasoundFrontendClassOutput& Output : GraphClass->GetDefaultInterface().Outputs)
{
Names.Add(Output.Name);
}
}
return Names;
}
FConstClassInputAccessPtr FGraphController::FindClassInputWithName(const FVertexName& InName) const
{
return GraphClassPtr.GetInputWithName(InName);
}
FConstClassOutputAccessPtr FGraphController::FindClassOutputWithName(const FVertexName& InName) const
{
return GraphClassPtr.GetOutputWithName(InName);
}
FGuid FGraphController::GetVertexIDForInputVertex(const FVertexName& InInputName) const
{
if (const FMetasoundFrontendClassInput* Input = FindClassInputWithName(InInputName).Get())
{
return Input->VertexID;
}
return Metasound::FrontendInvalidID;
}
FGuid FGraphController::GetVertexIDForOutputVertex(const FVertexName& InOutputName) const
{
if (const FMetasoundFrontendClassOutput* Output = FindClassOutputWithName(InOutputName).Get())
{
return Output->VertexID;
}
return Metasound::FrontendInvalidID;
}
TArray<FNodeHandle> FGraphController::GetNodes()
{
return GetNodeHandles(GetNodesAndClasses());
}
TArray<FConstNodeHandle> FGraphController::GetConstNodes() const
{
return GetNodeHandles(GetNodesAndClasses());
}
FConstNodeHandle FGraphController::GetNodeWithID(FGuid InNodeID) const
{
auto IsNodeWithSameID = [&](const FMetasoundFrontendClass& NodeClas, const FMetasoundFrontendNode& Node)
{
return Node.GetID() == InNodeID;
};
return GetNodeByPredicate(IsNodeWithSameID);
}
FNodeHandle FGraphController::GetNodeWithID(FGuid InNodeID)
{
auto IsNodeWithSameID = [&](const FMetasoundFrontendClass& NodeClas, const FMetasoundFrontendNode& Node)
{
return Node.GetID() == InNodeID;
};
return GetNodeByPredicate(IsNodeWithSameID);
}
#if WITH_EDITOR
const FMetasoundFrontendGraphStyle& FGraphController::GetGraphStyle() const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return FindConstBuildGraphChecked(*GraphClass).Style;
}
return Invalid::GetInvalidGraphStyle();
}
const FMetasoundFrontendInterfaceStyle& FGraphController::GetInputStyle() const
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->GetDefaultInterface().GetInputStyle();
}
return Invalid::GetInvalidInterfaceStyle();
}
const FMetasoundFrontendInterfaceStyle& FGraphController::GetOutputStyle() const
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->GetDefaultInterface().GetOutputStyle();
}
return Invalid::GetInvalidInterfaceStyle();
}
void FGraphController::SetGraphStyle(FMetasoundFrontendGraphStyle Style)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
FindBuildGraphChecked(*GraphClass).Style = MoveTemp(Style);
}
}
void FGraphController::SetInputStyle(FMetasoundFrontendInterfaceStyle Style)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
TArray<FMetasoundFrontendClassInput>& Inputs = GraphClass->GetDefaultInterface().Inputs;
Style.DefaultSortOrder.SetNumZeroed(Inputs.Num());
for (int32 i = 0; i < Inputs.Num(); ++i)
{
Inputs[i].Metadata.SortOrderIndex = Style.DefaultSortOrder[i];
}
GraphClass->GetDefaultInterface().SetInputStyle(MoveTemp(Style));
}
}
void FGraphController::SetOutputStyle(FMetasoundFrontendInterfaceStyle Style)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
TArray<FMetasoundFrontendClassOutput>& Outputs = GraphClass->GetDefaultInterface().Outputs;
Style.DefaultSortOrder.SetNumZeroed(Outputs.Num());
for (int32 i = 0; i < Outputs.Num(); ++i)
{
Outputs[i].Metadata.SortOrderIndex = Style.DefaultSortOrder[i];
}
GraphClass->GetDefaultInterface().SetOutputStyle(MoveTemp(Style));
}
}
#endif // WITH_EDITOR
TArray<FNodeHandle> FGraphController::GetOutputNodes()
{
auto IsOutputNode = [](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return NodeClass.Metadata.GetType() == EMetasoundFrontendClassType::Output;
};
return GetNodesByPredicate(IsOutputNode);
}
TArray<FNodeHandle> FGraphController::GetInputNodes()
{
auto IsInputNode = [](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return NodeClass.Metadata.GetType() == EMetasoundFrontendClassType::Input;
};
return GetNodesByPredicate(IsInputNode);
}
FVariableHandle FGraphController::AddVariable(const FName& InDataType)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
const IDataTypeRegistry& Registry = IDataTypeRegistry::Get();
FDataTypeRegistryInfo Info;
if (ensure(Registry.GetDataTypeInfo(InDataType, Info)))
{
FGuid VariableID = FGuid::NewGuid();
FMetasoundFrontendVariable Variable;
#if WITH_EDITORONLY_DATA
Variable.DisplayName = Info.DataTypeDisplayText;
#endif // WITH_EDITORONLY_DATA
Variable.TypeName = Info.DataTypeName;
Variable.Literal.SetFromLiteral(Registry.CreateDefaultLiteral(InDataType));
Variable.ID = VariableID;
#if WITH_EDITOR
if (FMetasoundFrontendDocumentMetadata* DocMetadata = OwningDocument->GetMetadata())
{
DocMetadata->ModifyContext.AddMemberIDModified(Variable.ID);
}
#endif // WITH_EDITOR
FMetasoundFrontendClass VariableNodeClass;
if (IDataTypeRegistry::Get().GetFrontendVariableClass(Variable.TypeName, VariableNodeClass))
{
FNodeHandle InitNode = AddNode(VariableNodeClass.Metadata, FGuid::NewGuid());
if (InitNode->IsValid())
{
Variable.VariableNodeID = InitNode->GetID();
FindBuildGraphChecked(*GraphClass).Variables.Add(MoveTemp(Variable));
}
}
return FindVariable(VariableID);
}
}
return IVariableController::GetInvalidHandle();
}
FVariableHandle FGraphController::FindVariable(const FGuid& InVariableID)
{
FVariableAccessPtr VariablePtr = GraphClassPtr.GetVariableWithID(InVariableID);
return MakeShared<FVariableController>(FVariableController::FInitParams{VariablePtr, this->AsShared()});
}
FConstVariableHandle FGraphController::FindVariable(const FGuid& InVariableID) const
{
FConstVariableAccessPtr ConstVariablePtr = GraphClassPtr.GetVariableWithID(InVariableID);
return MakeShared<FVariableController>(FVariableController::FInitParams{ConstCastAccessPtr<FVariableAccessPtr>(ConstVariablePtr), ConstCastSharedRef<IGraphController>(this->AsShared())});
}
FVariableHandle FGraphController::FindVariableContainingNode(const FGuid& InNodeID)
{
FGuid VariableID = FindVariableIDOfVariableContainingNode(InNodeID);
if (VariableID.IsValid())
{
return FindVariable(VariableID);
}
return IVariableController::GetInvalidHandle();
}
FConstVariableHandle FGraphController::FindVariableContainingNode(const FGuid& InNodeID) const
{
FGuid VariableID = FindVariableIDOfVariableContainingNode(InNodeID);
if (VariableID.IsValid())
{
return FindVariable(VariableID);
}
return IVariableController::GetInvalidHandle();
}
bool FGraphController::RemoveVariable(const FGuid& InVariableID)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
if (FMetasoundFrontendVariable* Variable = FindFrontendVariable(InVariableID))
{
FNodeHandle NodeHandle = GetNodeWithID(Variable->VariableNodeID);
RemoveNode(*NodeHandle);
NodeHandle = GetNodeWithID(Variable->MutatorNodeID);
RemoveNode(*NodeHandle);
// Copy ids as node removal will update variable node IDs
TArray<FGuid> AccessorNodeIDs = Variable->AccessorNodeIDs;
for (const FGuid& NodeID : AccessorNodeIDs)
{
NodeHandle = GetNodeWithID(NodeID);
RemoveNode(*NodeHandle);
}
// Copy ids as node removal will update variable node IDs
TArray<FGuid> DeferredAccessorNodeIDs = Variable->DeferredAccessorNodeIDs;
for (const FGuid& NodeID : DeferredAccessorNodeIDs)
{
NodeHandle = GetNodeWithID(NodeID);
RemoveNode(*NodeHandle);
}
#if WITH_EDITOR
if (FMetasoundFrontendDocumentMetadata* DocMetadata = OwningDocument->GetMetadata())
{
DocMetadata->ModifyContext.AddMemberIDModified(InVariableID);
}
#endif // WITH_EDITOR
auto IsVariableWithID = [&](const FMetasoundFrontendVariable& InVariable)
{
return InVariable.ID == InVariableID;
};
FindBuildGraphChecked(*GraphClass).Variables.RemoveAllSwap(IsVariableWithID);
return true;
}
}
return false;
}
TArray<FVariableHandle> FGraphController::GetVariables()
{
TArray<FVariableHandle> VariableHandles;
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
const TArray<FMetasoundFrontendVariable>& Variables = FindConstBuildGraphChecked(*GraphClass).Variables;
for (const FMetasoundFrontendVariable& Variable : Variables)
{
VariableHandles.Add(FindVariable(Variable.ID));
}
}
return VariableHandles;
}
TArray<FConstVariableHandle> FGraphController::GetVariables() const
{
TArray<FConstVariableHandle> VariableHandles;
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
const TArray<FMetasoundFrontendVariable>& Variables = FindConstBuildGraphChecked(*GraphClass).Variables;
for (const FMetasoundFrontendVariable& Variable : Variables)
{
VariableHandles.Add(FindVariable(Variable.ID));
}
}
return VariableHandles;
}
FNodeHandle FGraphController::FindOrAddVariableMutatorNode(const FGuid& InVariableID)
{
using namespace VariableNames;
if (FMetasoundFrontendVariable* Variable = FindFrontendVariable(InVariableID))
{
FNodeHandle SetNode = GetNodeWithID(Variable->MutatorNodeID);
if (!SetNode->IsValid())
{
FMetasoundFrontendClass SetNodeClass;
if (IDataTypeRegistry::Get().GetFrontendVariableMutatorClass(Variable->TypeName, SetNodeClass))
{
SetNode = AddNode(SetNodeClass.Metadata, FGuid::NewGuid());
if (SetNode->IsValid())
{
// Initialize set default literal value to that of the variable
FInputHandle InputHandle = SetNode->GetInputWithVertexName(METASOUND_GET_PARAM_NAME(InputData));
if (ensure(InputHandle->IsValid()))
{
InputHandle->SetLiteral(Variable->Literal);
}
Variable->MutatorNodeID = SetNode->GetID();
FGuid SourceVariableNodeID = Variable->VariableNodeID;
// Connect last delayed getter in variable stack.
if (Variable->DeferredAccessorNodeIDs.Num() > 0)
{
SourceVariableNodeID = Variable->DeferredAccessorNodeIDs.Last();
}
FNodeHandle SourceVariableNode = GetNodeWithID(SourceVariableNodeID);
if (ensure(SourceVariableNode->IsValid()))
{
FInputHandle SetNodeInput = SetNode->GetInputWithVertexName(METASOUND_GET_PARAM_NAME(InputVariable));
FOutputHandle SourceVariableNodeOutput = SourceVariableNode->GetOutputWithVertexName(METASOUND_GET_PARAM_NAME(OutputVariable));
ensure(SetNodeInput->Connect(*SourceVariableNodeOutput));
}
// Connect to first inline getter in variable stack
if (Variable->AccessorNodeIDs.Num() > 0)
{
FNodeHandle HeadGetNode = GetNodeWithID(Variable->AccessorNodeIDs[0]);
if (HeadGetNode->IsValid())
{
FOutputHandle SetNodeOutput = SetNode->GetOutputWithVertexName(METASOUND_GET_PARAM_NAME(OutputVariable));
FInputHandle GetNodeInput = HeadGetNode->GetInputWithVertexName(METASOUND_GET_PARAM_NAME(InputVariable));
ensure(SetNodeOutput->Connect(*GetNodeInput));
}
}
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Could not find registered \"set variable\" node class for data type \"%s\""), *Variable->TypeName.ToString());
}
}
return SetNode;
}
return INodeController::GetInvalidHandle();
}
FNodeHandle FGraphController::AddVariableAccessorNode(const FGuid& InVariableID)
{
using namespace VariableNames;
if (FMetasoundFrontendVariable* Variable = FindFrontendVariable(InVariableID))
{
FMetasoundFrontendClass NodeClass;
if (IDataTypeRegistry::Get().GetFrontendVariableAccessorClass(Variable->TypeName, NodeClass))
{
FNodeHandle NewNode = AddNode(NodeClass.Metadata, FGuid::NewGuid());
if (NewNode->IsValid())
{
// Connect new node.
FInputHandle NewInput = NewNode->GetInputWithVertexName(METASOUND_GET_PARAM_NAME(InputVariable));
FNodeHandle TailNode = FindTailNodeInVariableStack(InVariableID);
if (!TailNode->IsValid())
{
// variable stack is empty. Use connect to varialbe init node.
TailNode = GetNodeWithID(Variable->VariableNodeID);
}
if (ensure(TailNode->IsValid()))
{
// connect new node to the last "get" node.
FOutputHandle TailNodeOutput = TailNode->GetOutputWithVertexName(METASOUND_GET_PARAM_NAME(OutputVariable));
check(!TailNodeOutput->IsConnected());
const bool bSuccess = TailNodeOutput->Connect(*NewInput);
check(bSuccess);
}
// Add node ID to variable after connecting since the array
// order of node ids is used to determine whether a node
// is the tail node.
Variable->AccessorNodeIDs.Add(NewNode->GetID());
}
return NewNode;
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Could not find registered \"get variable\" node class for data type \"%s\""), *Variable->TypeName.ToString());
}
}
return INodeController::GetInvalidHandle();
}
FNodeHandle FGraphController::AddVariableDeferredAccessorNode(const FGuid& InVariableID)
{
using namespace VariableNames;
if (FMetasoundFrontendVariable* Variable = FindFrontendVariable(InVariableID))
{
FMetasoundFrontendClass NodeClass;
if (IDataTypeRegistry::Get().GetFrontendVariableDeferredAccessorClass(Variable->TypeName, NodeClass))
{
FNodeHandle NewNode = AddNode(NodeClass.Metadata, FGuid::NewGuid());
if (NewNode->IsValid())
{
// Connect new node.
FOutputHandle NewNodeOutput = NewNode->GetOutputWithVertexName(METASOUND_GET_PARAM_NAME(OutputVariable));
FNodeHandle HeadNode = FindHeadNodeInVariableStack(InVariableID);
if (HeadNode->IsValid())
{
FInputHandle HeadNodeInput = HeadNode->GetInputWithVertexName(METASOUND_GET_PARAM_NAME(InputVariable));
const bool bSuccess = HeadNodeInput->Connect(*NewNodeOutput);
check(bSuccess);
}
FInputHandle NewNodeInput = NewNode->GetInputWithVertexName(METASOUND_GET_PARAM_NAME(InputVariable));
FNodeHandle VariableNode = GetNodeWithID(Variable->VariableNodeID);
if (ensure(VariableNode->IsValid()))
{
FOutputHandle VariableNodeOutput = VariableNode->GetOutputWithVertexName(METASOUND_GET_PARAM_NAME(OutputVariable));
const bool bSuccess = VariableNodeOutput->Connect(*NewNodeInput);
check(bSuccess);
}
// Add node ID to variable after connecting since the array
// order of node ids is used to determine whether a node
// is the tail node.
Variable->DeferredAccessorNodeIDs.Add(NewNode->GetID());
}
return NewNode;
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Could not find registered \"get variable\" node class for data type \"%s\""), *Variable->TypeName.ToString());
}
}
return INodeController::GetInvalidHandle();
}
FMetasoundFrontendVariable* FGraphController::FindFrontendVariable(const FGuid& InVariableID)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsVariableWithID = [&](const FMetasoundFrontendVariable& InVariable)
{
return InVariable.ID == InVariableID;
};
return FindBuildGraphChecked(*GraphClass).Variables.FindByPredicate(IsVariableWithID);
}
return nullptr;
}
const FMetasoundFrontendVariable* FGraphController::FindFrontendVariable(const FGuid& InVariableID) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsVariableWithID = [&](const FMetasoundFrontendVariable& InVariable)
{
return InVariable.ID == InVariableID;
};
const TArray<FMetasoundFrontendVariable>& Variables = FindConstBuildGraphChecked(*GraphClass).Variables;
return Variables.FindByPredicate(IsVariableWithID);
}
return nullptr;
}
FGuid FGraphController::FindVariableIDOfVariableContainingNode(const FGuid& InNodeID) const
{
if (InNodeID.IsValid())
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto ContainsNodeWithID = [&InNodeID](const FMetasoundFrontendVariable& InVariable)
{
return (InNodeID == InVariable.VariableNodeID) || (InNodeID == InVariable.MutatorNodeID) || InVariable.AccessorNodeIDs.Contains(InNodeID) || InVariable.DeferredAccessorNodeIDs.Contains(InNodeID);
};
const TArray<FMetasoundFrontendVariable>& Variables = FindConstBuildGraphChecked(*GraphClass).Variables;
if (const FMetasoundFrontendVariable* Variable = Variables.FindByPredicate(ContainsNodeWithID))
{
return Variable->ID;
}
}
}
return FGuid();
}
FNodeHandle FGraphController::FindHeadNodeInVariableStack(const FGuid& InVariableID)
{
// The variable "stack" is [GetDelayedNodes, SetNode, GetNodes].
if (FMetasoundFrontendVariable* Variable = FindFrontendVariable(InVariableID))
{
if (Variable->DeferredAccessorNodeIDs.Num() > 0)
{
return GetNodeWithID(Variable->DeferredAccessorNodeIDs[0]);
}
if (FrontendInvalidID != Variable->MutatorNodeID)
{
return GetNodeWithID(Variable->MutatorNodeID);
}
if (Variable->AccessorNodeIDs.Num() > 0)
{
return GetNodeWithID(Variable->AccessorNodeIDs[0]);
}
}
return INodeController::GetInvalidHandle();
}
FNodeHandle FGraphController::FindTailNodeInVariableStack(const FGuid& InVariableID)
{
// The variable "stack" is [GetDelayedNodes, SetNode, GetNodes].
if (FMetasoundFrontendVariable* Variable = FindFrontendVariable(InVariableID))
{
if (Variable->AccessorNodeIDs.Num() > 0)
{
return GetNodeWithID(Variable->AccessorNodeIDs.Last());
}
if (FrontendInvalidID != Variable->MutatorNodeID)
{
return GetNodeWithID(Variable->MutatorNodeID);
}
if (Variable->DeferredAccessorNodeIDs.Num() > 0)
{
return GetNodeWithID(Variable->DeferredAccessorNodeIDs.Last());
}
}
return INodeController::GetInvalidHandle();
}
void FGraphController::RemoveNodeIDFromAssociatedVariable(const INodeController& InNode)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
const FGuid NodeID = InNode.GetID();
for (FMetasoundFrontendVariable& Variable : FindBuildGraphChecked(*GraphClass).Variables)
{
if (NodeID == Variable.VariableNodeID)
{
Variable.VariableNodeID = FGuid();
break;
}
if (NodeID == Variable.MutatorNodeID)
{
Variable.MutatorNodeID = FGuid();
}
if (Variable.AccessorNodeIDs.Remove(NodeID) > 0)
{
break;
}
if (Variable.DeferredAccessorNodeIDs.Remove(NodeID) > 0)
{
break;
}
}
}
}
void FGraphController::SpliceVariableNodeFromVariableStack(INodeController& InNode)
{
// Variable nodes are organized in a stack to ensure that variables are
// accessed in a consistent manner at runtime. A single variable object is
// shared amongst all nodes associated with a single variable. The variable
// object is shared by daisy-chaining the variable from one node to the
// next.
//
// If a node is removed, that daisy-chain must be preserved. This function
// removes a node while maintaining the daisy-chain.
check(InNode.IsValid());
using namespace VariableNames;
FInputHandle InputToSpliceOut = InNode.GetInputWithVertexName(METASOUND_GET_PARAM_NAME(InputVariable));
FOutputHandle OutputToReroute = InputToSpliceOut->GetConnectedOutput();
if (OutputToReroute->IsValid())
{
FOutputHandle OutputToSpliceOut = InNode.GetOutputWithVertexName(METASOUND_GET_PARAM_NAME(OutputVariable));
for (const FInputHandle& InputToReroute : OutputToSpliceOut->GetConnectedInputs())
{
ensure(InputToReroute->Connect(*OutputToReroute));
}
}
}
void FGraphController::ClearGraph()
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
FMetasoundFrontendGraph& Graph = FindBuildGraphChecked(*GraphClass);
Graph.Nodes.Reset();
Graph.Edges.Reset();
GraphClass->GetDefaultInterface().Inputs.Reset();
GraphClass->GetDefaultInterface().Outputs.Reset();
GraphClass->PresetOptions.InputsInheritingDefault.Reset();
OwningDocument->ClearInterfaceVersions();
OwningDocument->RemoveUnreferencedDependencies();
#if WITH_EDITORONLY_DATA
FDocumentAccessPtr DocumentPtr = OwningDocument->GetDocumentPtr();
FMetasoundFrontendDocument* Document = DocumentPtr.Get();
check(Document);
Document->Metadata.MemberMetadata.Reset();
Graph.Style.EdgeStyles.Reset();
#endif // WITH_EDITORONLY_DATA
}
}
void FGraphController::IterateNodes(TFunctionRef<void(FNodeHandle)> InFunction, EMetasoundFrontendClassType InClassType)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
const TArray<FMetasoundFrontendNode>& Nodes = FindConstBuildGraphChecked(*GraphClass).Nodes;
for (const FMetasoundFrontendNode& Node : Nodes)
{
FConstClassAccessPtr NodeClassPtr = OwningDocument->FindClassWithID(Node.ClassID);
if (const FMetasoundFrontendClass* NodeClass = NodeClassPtr.Get())
{
if (InClassType == EMetasoundFrontendClassType::Invalid || NodeClass->Metadata.GetType() == InClassType)
{
FNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.GetID());
FNodeHandle NodeHandle = GetNodeHandle(FGraphController::FNodeAndClass{ NodePtr, NodeClassPtr });
InFunction(NodeHandle);
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Failed to find class for node [NodeID:%s, ClassID:%s]"), *Node.GetID().ToString(), *Node.ClassID.ToString());
}
}
}
}
void FGraphController::IterateConstNodes(TFunctionRef<void(FConstNodeHandle)> InFunction, EMetasoundFrontendClassType InClassType) const
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
const TArray<FMetasoundFrontendNode>& Nodes = FindConstBuildGraphChecked(*GraphClass).Nodes;
for (const FMetasoundFrontendNode& Node : Nodes)
{
FConstClassAccessPtr NodeClassPtr = OwningDocument->FindClassWithID(Node.ClassID);
if (const FMetasoundFrontendClass* NodeClass = NodeClassPtr.Get())
{
if (InClassType == EMetasoundFrontendClassType::Invalid || NodeClass->Metadata.GetType() == InClassType)
{
FConstNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.GetID());
FConstNodeHandle NodeHandle = GetNodeHandle(FGraphController::FConstNodeAndClass{ NodePtr, NodeClassPtr });
InFunction(NodeHandle);
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Failed to find class for node [NodeID:%s, ClassID:%s]"), *Node.GetID().ToString(), *Node.ClassID.ToString());
}
}
}
}
TArray<FConstNodeHandle> FGraphController::GetConstOutputNodes() const
{
auto IsOutputNode = [](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return NodeClass.Metadata.GetType() == EMetasoundFrontendClassType::Output;
};
return GetNodesByPredicate(IsOutputNode);
}
TArray<FConstNodeHandle> FGraphController::GetConstInputNodes() const
{
auto IsInputNode = [](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return NodeClass.Metadata.GetType() == EMetasoundFrontendClassType::Input;
};
return GetNodesByPredicate(IsInputNode);
}
const TSet<FName>& FGraphController::GetInputsInheritingDefault() const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
if (GraphClass->PresetOptions.bIsPreset)
{
return GraphClass->PresetOptions.InputsInheritingDefault;
}
}
return Invalid::GetInvalidNameSet();
}
bool FGraphController::SetInputInheritsDefault(FName InName, bool bInputInheritsDefault)
{
if (bInputInheritsDefault)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
if (GraphClass->PresetOptions.bIsPreset)
{
return GraphClass->PresetOptions.InputsInheritingDefault.Add(InName).IsValidId();
}
}
}
else
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
if (GraphClass->PresetOptions.bIsPreset)
{
return GraphClass->PresetOptions.InputsInheritingDefault.Remove(InName) > 0;
}
}
}
return false;
}
void FGraphController::SetInputsInheritingDefault(TSet<FName>&& InNames)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
GraphClass->PresetOptions.bIsPreset = true;
GraphClass->PresetOptions.InputsInheritingDefault = MoveTemp(InNames);
}
}
bool FGraphController::ContainsOutputVertex(const FVertexName& InName, const FName& InTypeName) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsOutputVertexWithSameNameAndType = [&](const FMetasoundFrontendClassOutput& ClassOutput)
{
return ClassOutput.Name == InName && ClassOutput.TypeName == InTypeName;
};
return GraphClass->GetDefaultInterface().Outputs.ContainsByPredicate(IsOutputVertexWithSameNameAndType);
}
return false;
}
bool FGraphController::ContainsOutputVertexWithName(const FVertexName& InName) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsOutputVertexWithSameName = [&](const FMetasoundFrontendClassOutput& ClassOutput)
{
return ClassOutput.Name == InName;
};
return GraphClass->GetDefaultInterface().Outputs.ContainsByPredicate(IsOutputVertexWithSameName);
}
return false;
}
bool FGraphController::ContainsInputVertex(const FVertexName& InName, const FName& InTypeName) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsInputVertexWithSameNameAndType = [&](const FMetasoundFrontendClassInput& ClassInput)
{
return ClassInput.Name == InName && ClassInput.TypeName == InTypeName;
};
return GraphClass->GetDefaultInterface().Inputs.ContainsByPredicate(IsInputVertexWithSameNameAndType);
}
return false;
}
bool FGraphController::ContainsInputVertexWithName(const FVertexName& InName) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsInputVertexWithSameName = [&](const FMetasoundFrontendClassInput& ClassInput)
{
return ClassInput.Name == InName;
};
return GraphClass->GetDefaultInterface().Inputs.ContainsByPredicate(IsInputVertexWithSameName);
}
return false;
}
FConstNodeHandle FGraphController::GetOutputNodeWithName(const FVertexName& InName) const
{
auto IsOutputNodeWithSameName = [&](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return (NodeClass.Metadata.GetType() == EMetasoundFrontendClassType::Output) && (Node.Name == InName);
};
return GetNodeByPredicate(IsOutputNodeWithSameName);
}
FConstNodeHandle FGraphController::GetInputNodeWithName(const FVertexName& InName) const
{
auto IsInputNodeWithSameName = [&](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return (NodeClass.Metadata.GetType() == EMetasoundFrontendClassType::Input) && (Node.Name == InName);
};
return GetNodeByPredicate(IsInputNodeWithSameName);
}
FNodeHandle FGraphController::GetOutputNodeWithName(const FVertexName& InName)
{
auto IsOutputNodeWithSameName = [&](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return (NodeClass.Metadata.GetType() == EMetasoundFrontendClassType::Output) && (Node.Name == InName);
};
return GetNodeByPredicate(IsOutputNodeWithSameName);
}
FNodeHandle FGraphController::GetInputNodeWithName(const FVertexName& InName)
{
auto IsInputNodeWithSameName = [&](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return (NodeClass.Metadata.GetType() == EMetasoundFrontendClassType::Input) && (Node.Name == InName);
};
return GetNodeByPredicate(IsInputNodeWithSameName);
}
FNodeHandle FGraphController::AddInputVertex(const FMetasoundFrontendClassInput& InClassInput)
{
using FRegistry = INodeClassRegistry;
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsInputWithSameName = [&](const FMetasoundFrontendClassInput& ExistingDesc) { return ExistingDesc.Name == InClassInput.Name; };
if (Algo::NoneOf(GraphClass->GetDefaultInterface().Inputs, IsInputWithSameName))
{
FNodeRegistryKey Key;
if (FRegistry::GetInputNodeRegistryKeyForDataType(InClassInput.TypeName, InClassInput.AccessType, Key))
{
FConstClassAccessPtr InputClassPtr = OwningDocument->FindOrAddClass(Key);
if (const FMetasoundFrontendClass* InputClass = InputClassPtr.Get())
{
const FVertexName NewName = InClassInput.Name;
TInstancedStruct<FMetaSoundFrontendNodeConfiguration> Configuration = FRegistry::Get()->CreateFrontendNodeConfiguration(Key);
// Setup input node
FMetasoundFrontendNode& Node = FindBuildGraphChecked(*GraphClass).Nodes.Emplace_GetRef(*InputClass, MoveTemp(Configuration));
Node.Name = NewName;
FGuid NodeID = InClassInput.NodeID;
if (!NodeID.IsValid())
{
FDocumentAccessPtr DocumentPtr = OwningDocument->GetDocumentPtr();
const FMetasoundFrontendDocument* Document = DocumentPtr.Get();
check(Document);
NodeID = FDocumentIDGenerator::Get().CreateNodeID(*Document);
}
Node.UpdateID(NodeID);
// Set name on related vertices of input node
auto IsVertexWithTypeName = [&](FMetasoundFrontendVertex& Vertex) { return Vertex.TypeName == InClassInput.TypeName; };
if (FMetasoundFrontendVertex* InputVertex = Node.Interface.Inputs.FindByPredicate(IsVertexWithTypeName))
{
InputVertex->Name = NewName;
}
else
{
UE_LOG(LogMetaSound, Error, TEXT("Input node [TypeName:%s] does not contain input vertex with type [TypeName:%s]"), *InClassInput.TypeName.ToString(), *InClassInput.TypeName.ToString());
}
if (FMetasoundFrontendVertex* OutputVertex = Node.Interface.Outputs.FindByPredicate(IsVertexWithTypeName))
{
OutputVertex->Name = NewName;
}
else
{
UE_LOG(LogMetaSound, Error, TEXT("Input node [TypeName:%s] does not contain output vertex with type [TypeName:%s]"), *InClassInput.TypeName.ToString(), *InClassInput.TypeName.ToString());
}
// Add input to this graph class interface
FMetasoundFrontendClassInput& NewInput = GraphClass->GetDefaultInterface().Inputs.Add_GetRef(InClassInput);
NewInput.NodeID = Node.GetID();
if (!NewInput.VertexID.IsValid())
{
// Create a new guid if there wasn't a valid guid attached
// to input.
FDocumentAccessPtr DocumentPtr = OwningDocument->GetDocumentPtr();
const FMetasoundFrontendDocument* Document = DocumentPtr.Get();
check(Document);
NewInput.VertexID = FDocumentIDGenerator::Get().CreateVertexID(*Document);
}
#if WITH_EDITOR
if (FMetasoundFrontendDocumentMetadata* DocMetadata = OwningDocument->GetMetadata())
{
DocMetadata->ModifyContext.AddMemberIDModified(NewInput.NodeID);
}
#endif // WITH_EDITOR
GraphClass->GetDefaultInterface().UpdateChangeID();
FNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.GetID());
return GetNodeHandle(FGraphController::FNodeAndClass { NodePtr, InputClassPtr });
}
}
else
{
UE_LOG(LogMetaSound, Display, TEXT("Failed to add input. No input node registered for data type [TypeName:%s]"), *InClassInput.TypeName.ToString());
}
}
else
{
UE_LOG(LogMetaSound, Display, TEXT("Failed to add input. Input with same name \"%s\" exists in class [ClassID:%s]"), *InClassInput.Name.ToString(), *GraphClass->ID.ToString());
}
}
return INodeController::GetInvalidHandle();
}
bool FGraphController::RemoveInputVertex(const FVertexName& InName)
{
auto IsInputNodeWithSameName = [&](const FMetasoundFrontendClass& InClass, const FMetasoundFrontendNode& InNode)
{
return InClass.Metadata.GetType() == EMetasoundFrontendClassType::Input && InNode.Name == InName;
};
const TArray<FGraphController::FNodeAndClass> NodeAndClassPairs = GetNodesAndClassesByPredicate(IsInputNodeWithSameName);
for (const FNodeAndClass& NodeAndClass : NodeAndClassPairs)
{
if (const FMetasoundFrontendNode* Node = NodeAndClass.Node.Get())
{
return RemoveInput(*Node);
}
}
return false;
}
FNodeHandle FGraphController::AddOutputVertex(const FVertexName& InName, const FName InTypeName)
{
const FGuid VertexID = FGuid::NewGuid();
FMetasoundFrontendClassOutput Description;
Description.Name = InName;
Description.TypeName = InTypeName;
Description.VertexID = VertexID;
return AddOutputVertex(Description);
}
FNodeHandle FGraphController::AddOutputVertex(const FMetasoundFrontendClassOutput& InClassOutput)
{
using FRegistry = INodeClassRegistry;
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsOutputWithSameName = [&](const FMetasoundFrontendClassOutput& ExistingDesc) { return ExistingDesc.Name == InClassOutput.Name; };
if (Algo::NoneOf(GraphClass->GetDefaultInterface().Outputs, IsOutputWithSameName))
{
FNodeRegistryKey Key;
if (FRegistry::GetOutputNodeRegistryKeyForDataType(InClassOutput.TypeName, InClassOutput.AccessType, Key))
{
FConstClassAccessPtr OutputClassPtr = OwningDocument->FindOrAddClass(Key);
if (const FMetasoundFrontendClass* OutputClass = OutputClassPtr.Get())
{
const FVertexName NewName = InClassOutput.Name;
TInstancedStruct<FMetaSoundFrontendNodeConfiguration> Configuration = FRegistry::Get()->CreateFrontendNodeConfiguration(Key);
FMetasoundFrontendNode& Node = FindBuildGraphChecked(*GraphClass).Nodes.Emplace_GetRef(*OutputClass, MoveTemp(Configuration));
Node.Name = NewName;
FGuid NodeID = InClassOutput.NodeID;
if (!NodeID.IsValid())
{
FDocumentAccessPtr DocumentPtr = OwningDocument->GetDocumentPtr();
const FMetasoundFrontendDocument* Document = DocumentPtr.Get();
check(Document);
NodeID = FDocumentIDGenerator::Get().CreateNodeID(*Document);
}
Node.UpdateID(NodeID);
// Set vertex name on output node
auto IsVertexWithTypeName = [&](FMetasoundFrontendVertex& Vertex) { return Vertex.TypeName == InClassOutput.TypeName; };
if (FMetasoundFrontendVertex* InputVertex = Node.Interface.Inputs.FindByPredicate(IsVertexWithTypeName))
{
InputVertex->Name = NewName;
}
else
{
UE_LOG(LogMetaSound, Error, TEXT("Output node [TypeName:%s] does not contain input vertex with type [TypeName:%s]"), *InClassOutput.TypeName.ToString(), *InClassOutput.TypeName.ToString());
}
if (FMetasoundFrontendVertex* OutputVertex = Node.Interface.Outputs.FindByPredicate(IsVertexWithTypeName))
{
OutputVertex->Name = NewName;
}
else
{
UE_LOG(LogMetaSound, Error, TEXT("Output node [TypeName:%s] does not contain output vertex with type [TypeName:%s]"), *InClassOutput.TypeName.ToString(), *InClassOutput.TypeName.ToString());
}
// Add output to graph interface
FMetasoundFrontendClassOutput& NewOutput = GraphClass->GetDefaultInterface().Outputs.Add_GetRef(InClassOutput);
// Setup new output
NewOutput.NodeID = Node.GetID();
if (!NewOutput.VertexID.IsValid())
{
// Create a new guid if there wasn't a valid guid attached
// to output.
FDocumentAccessPtr DocumentPtr = OwningDocument->GetDocumentPtr();
const FMetasoundFrontendDocument* Document = DocumentPtr.Get();
check(Document);
NewOutput.VertexID = FDocumentIDGenerator::Get().CreateVertexID(*Document);
}
#if WITH_EDITOR
if (FMetasoundFrontendDocumentMetadata* DocMetadata = OwningDocument->GetMetadata())
{
DocMetadata->ModifyContext.AddMemberIDModified(NewOutput.NodeID);
}
#endif // WITH_EDITOR
// Mark interface as changed.
GraphClass->GetDefaultInterface().UpdateChangeID();
FNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.GetID());
return GetNodeHandle(FGraphController::FNodeAndClass { NodePtr, OutputClassPtr });
}
}
else
{
UE_LOG(LogMetaSound, Display, TEXT("Failed to add output. No output node registered for data type [TypeName:%s]"), *InClassOutput.TypeName.ToString());
}
}
else
{
UE_LOG(LogMetaSound, Display, TEXT("Failed to add output. Output with same name \"%s\" exists in class [ClassID:%s]"), *InClassOutput.Name.ToString(), *GraphClass->ID.ToString());
}
}
return INodeController::GetInvalidHandle();
}
bool FGraphController::RemoveOutputVertex(const FVertexName& InName)
{
auto IsOutputNodeWithSameName = [&](const FMetasoundFrontendClass& InClass, const FMetasoundFrontendNode& InNode)
{
return InClass.Metadata.GetType() == EMetasoundFrontendClassType::Output
&& InNode.Name == InName;
};
const TArray<FGraphController::FNodeAndClass> NodeAndClassPairs = GetNodesAndClassesByPredicate(IsOutputNodeWithSameName);
for (const FNodeAndClass& NodeAndClass : NodeAndClassPairs)
{
if (const FMetasoundFrontendNode* Node = NodeAndClass.Node.Get())
{
return RemoveOutput(*Node);
}
}
return false;
}
// This can be used to determine what kind of property editor we should use for the data type of a given input.
// Will return Invalid if the input couldn't be found, or if the input doesn't support any kind of literals.
ELiteralType FGraphController::GetPreferredLiteralTypeForInputVertex(const FVertexName& InInputName) const
{
if (const FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithName(InInputName))
{
return IDataTypeRegistry::Get().GetDesiredLiteralType(Desc->TypeName);
}
return ELiteralType::Invalid;
}
// For inputs whose preferred literal type is UObject or UObjectArray, this can be used to determine the UObject corresponding to that input's datatype.
UClass* FGraphController::GetSupportedClassForInputVertex(const FVertexName& InInputName)
{
if (const FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithName(InInputName))
{
return IDataTypeRegistry::Get().GetUClassForDataType(Desc->TypeName);
}
return nullptr;
}
FMetasoundFrontendLiteral FGraphController::GetDefaultInput(const FGuid& InVertexID) const
{
if (const FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithVertexID(InVertexID))
{
FGuid TargetPageID = Frontend::DefaultPageID;
if (IDocumentBuilderRegistry* DocRegistry = IDocumentBuilderRegistry::Get())
{
TargetPageID = DocRegistry->ResolveTargetPageID(*Desc);
}
const FMetasoundFrontendLiteral& DefaultLiteral = Desc->FindConstDefaultChecked(TargetPageID);
return DefaultLiteral;
}
return FMetasoundFrontendLiteral{};
}
bool FGraphController::SetDefaultInput(const FGuid& InVertexID, const FMetasoundFrontendLiteral& InLiteral)
{
if (FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithVertexID(InVertexID))
{
if (ensure(IDataTypeRegistry::Get().IsLiteralTypeSupported(Desc->TypeName, InLiteral.GetType())))
{
FGuid TargetPageID = Frontend::DefaultPageID;
if (IDocumentBuilderRegistry* DocRegistry = IDocumentBuilderRegistry::Get())
{
TargetPageID = DocRegistry->ResolveTargetPageID(*Desc);
}
FMetasoundFrontendLiteral& DefaultLiteral = Desc->FindDefaultChecked(TargetPageID);
DefaultLiteral = InLiteral;
return true;
}
else
{
SetDefaultInputToDefaultLiteralOfType(InVertexID);
}
}
return false;
}
bool FGraphController::SetDefaultInputToDefaultLiteralOfType(const FGuid& InVertexID)
{
if (FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithVertexID(InVertexID))
{
Metasound::FLiteral Literal = IDataTypeRegistry::Get().CreateDefaultLiteral(Desc->TypeName);
FMetasoundFrontendLiteral& DefaultLiteral = Desc->FindDefaultChecked(Frontend::DefaultPageID);
DefaultLiteral.SetFromLiteral(Literal);
return DefaultLiteral.IsValid();
}
return false;
}
#if WITH_EDITOR
const FText& FGraphController::GetInputDescription(const FVertexName& InName) const
{
if (const FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithName(InName))
{
return Desc->Metadata.GetDescription();
}
return FText::GetEmpty();
}
const FText& FGraphController::GetOutputDescription(const FVertexName& InName) const
{
if (const FMetasoundFrontendClassOutput* Desc = FindOutputDescriptionWithName(InName))
{
return Desc->Metadata.GetDescription();
}
return FText::GetEmpty();
}
#endif // WITH_EDITOR
#if WITH_EDITORONLY_DATA
int32 FGraphController::GetSortOrderIndexForInput(const FVertexName& InName) const
{
if (const FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithName(InName))
{
return Desc->Metadata.SortOrderIndex;
}
return 0;
}
int32 FGraphController::GetSortOrderIndexForOutput(const FVertexName& InName) const
{
if (const FMetasoundFrontendClassOutput* Desc = FindOutputDescriptionWithName(InName))
{
return Desc->Metadata.SortOrderIndex;
}
return 0;
}
#endif // WITH_EDITORONLY_DATA
#if WITH_EDITOR
void FGraphController::SetInputDisplayName(const FVertexName& InName, const FText& InDisplayName)
{
if (FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithName(InName))
{
Desc->Metadata.SetDisplayName(InDisplayName);
}
}
void FGraphController::SetOutputDisplayName(const FVertexName& InName, const FText& InDisplayName)
{
if (FMetasoundFrontendClassOutput* Desc = FindOutputDescriptionWithName(InName))
{
Desc->Metadata.SetDisplayName(InDisplayName);
}
}
void FGraphController::SetInputDescription(const FVertexName& InName, const FText& InDescription)
{
if (FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithName(InName))
{
Desc->Metadata.SetDescription(InDescription);
}
}
void FGraphController::SetOutputDescription(const FVertexName& InName, const FText& InDescription)
{
if (FMetasoundFrontendClassOutput* Desc = FindOutputDescriptionWithName(InName))
{
Desc->Metadata.SetDescription(InDescription);
}
}
void FGraphController::SetSortOrderIndexForInput(const FVertexName& InName, int32 InSortOrderIndex)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
FMetasoundFrontendInterfaceStyle Style = GraphClass->GetDefaultInterface().GetInputStyle();
Style.DefaultSortOrder.Reset();
for (FMetasoundFrontendClassInput& Input : GraphClass->GetDefaultInterface().Inputs)
{
if (Input.Name == InName)
{
Input.Metadata.SortOrderIndex = InSortOrderIndex;
}
Style.DefaultSortOrder.Add(Input.Metadata.SortOrderIndex);
}
GraphClass->GetDefaultInterface().SetInputStyle(Style);
}
}
void FGraphController::SetSortOrderIndexForOutput(const FVertexName& InName, int32 InSortOrderIndex)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
FMetasoundFrontendInterfaceStyle Style = GraphClass->GetDefaultInterface().GetOutputStyle();
Style.DefaultSortOrder.Reset();
for (FMetasoundFrontendClassOutput& Output : GraphClass->GetDefaultInterface().Outputs)
{
if (Output.Name == InName)
{
Output.Metadata.SortOrderIndex = InSortOrderIndex;
}
Style.DefaultSortOrder.Add(Output.Metadata.SortOrderIndex);
}
GraphClass->GetDefaultInterface().SetOutputStyle(MoveTemp(Style));
}
}
#endif // WITH_EDITOR
// This can be used to clear the current literal for a given input.
// @returns false if the input name couldn't be found.
bool FGraphController::ClearLiteralForInput(const FVertexName& InInputName, FGuid InVertexID)
{
if (FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithName(InInputName))
{
FMetasoundFrontendLiteral& DefaultLiteral = Desc->FindDefaultChecked(Frontend::DefaultPageID);
DefaultLiteral.Clear();
}
return false;
}
FNodeHandle FGraphController::AddNode(const FNodeRegistryKey& InKey, FGuid InNodeGuid)
{
// Construct a FNodeClassInfo from this lookup key.
FConstClassAccessPtr Class = OwningDocument->FindOrAddClass(InKey);
const bool bIsValidClass = (nullptr != Class.Get());
if (bIsValidClass)
{
return AddNode(Class, InNodeGuid);
}
UE_LOG(LogMetaSound, Warning, TEXT("Failed to find or add node class info with registry key [Key:%s]"), *InKey.ToString());
return INodeController::GetInvalidHandle();
}
FNodeHandle FGraphController::AddNode(const FMetasoundFrontendClassMetadata& InClassMetadata, FGuid InNodeGuid)
{
ensureAlwaysMsgf(InClassMetadata.GetType() != EMetasoundFrontendClassType::Template,
TEXT("Cannot implement '%s' template node using 'AddNode'. Template nodes must always "
"be added using AddTemplateNode function and supply the interface to be implemented"),
*InClassMetadata.GetClassName().ToString());
return AddNode(FNodeRegistryKey(InClassMetadata), InNodeGuid);
}
FNodeHandle FGraphController::AddTemplateNode(const INodeTemplate& InTemplate, FNodeTemplateGenerateInterfaceParams Params, FGuid InNodeGuid)
{
// Construct a FNodeClassInfo from this lookup key.
const FNodeRegistryKey Key(InTemplate.GetFrontendClass().Metadata);
FConstClassAccessPtr Class = OwningDocument->FindOrAddClass(Key);
const bool bIsValidClass = (nullptr != Class.Get());
if (bIsValidClass)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
if (const FMetasoundFrontendClass* NodeClass = Class.Get())
{
// Currently template nodes do not have node configurations so an invalid one is supplied here.
FMetasoundFrontendNode& Node = FindBuildGraphChecked(*GraphClass).Nodes.Emplace_GetRef(*NodeClass, TInstancedStruct<FMetaSoundFrontendNodeConfiguration>());
Node.UpdateID(InNodeGuid);
Node.Interface = InTemplate.GenerateNodeInterface(MoveTemp(Params));
FNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.GetID());
return GetNodeHandle(FGraphController::FNodeAndClass{ NodePtr, Class });
}
}
}
UE_LOG(LogMetaSound, Warning, TEXT("Failed to find or add node template class info with registry key [Key:%s]"), *Key.ToString());
return INodeController::GetInvalidHandle();
}
FNodeHandle FGraphController::AddDuplicateNode(const INodeController& InNode)
{
// TODO: will need to copy node interface when dynamic pins exist.
const FMetasoundFrontendClassMetadata& ClassMetadata = InNode.GetClassMetadata();
FConstClassAccessPtr ClassPtr;
if (EMetasoundFrontendClassType::Graph == ClassMetadata.GetType())
{
// Add subgraph and dependencies if needed
ClassPtr = OwningDocument->FindClass(ClassMetadata);
const bool bIsClassMissing = (nullptr == ClassPtr.Get());
if (bIsClassMissing)
{
// Class does not exist, need to add the subgraph
OwningDocument->AddDuplicateSubgraph(*(InNode.AsGraph()));
ClassPtr = OwningDocument->FindClass(ClassMetadata);
}
}
else
{
ClassPtr = OwningDocument->FindOrAddClass(ClassMetadata);
}
return AddNode(ClassPtr, FGuid::NewGuid());
}
// Remove the node corresponding to this node handle.
// On success, invalidates the received node handle.
bool FGraphController::RemoveNode(INodeController& InNode)
{
const EMetasoundFrontendClassType NodeClassType = InNode.GetClassMetadata().GetType();
const bool bIsVariableNodeClassType = (NodeClassType == EMetasoundFrontendClassType::Variable)
|| (NodeClassType == EMetasoundFrontendClassType::VariableAccessor)
|| (NodeClassType == EMetasoundFrontendClassType::VariableDeferredAccessor)
|| (NodeClassType == EMetasoundFrontendClassType::VariableMutator);
if (bIsVariableNodeClassType)
{
// Variables hold on to related node IDs. These need to be removed
// from the variable definition.
RemoveNodeIDFromAssociatedVariable(InNode);
// Variable nodes of the same variable are connected serially.
// Special care is taken to ensure the stack is connected when
// removing a node from the stack.
SpliceVariableNodeFromVariableStack(InNode);
}
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
FGuid NodeID = InNode.GetID();
auto IsNodeWithSameID = [&](const FMetasoundFrontendNode& InFrontendNode) { return InFrontendNode.GetID() == NodeID; };
const TArray<FMetasoundFrontendNode>& Nodes = FindConstBuildGraphChecked(*GraphClass).Nodes;
if (const FMetasoundFrontendNode* FrontendNode = Nodes.FindByPredicate(IsNodeWithSameID))
{
switch(NodeClassType)
{
case EMetasoundFrontendClassType::Input:
{
return RemoveInput(*FrontendNode);
}
case EMetasoundFrontendClassType::Output:
{
return RemoveOutput(*FrontendNode);
}
case EMetasoundFrontendClassType::Variable:
case EMetasoundFrontendClassType::VariableAccessor:
case EMetasoundFrontendClassType::VariableDeferredAccessor:
case EMetasoundFrontendClassType::VariableMutator:
// TODO: remove node from variable.7
case EMetasoundFrontendClassType::Literal:
case EMetasoundFrontendClassType::External:
case EMetasoundFrontendClassType::Template:
case EMetasoundFrontendClassType::Graph:
{
return RemoveNode(*FrontendNode);
}
default:
case EMetasoundFrontendClassType::Invalid:
{
static_assert(static_cast<int32>(EMetasoundFrontendClassType::Invalid) == 10, "Possible missing switch case coverage for EMetasoundFrontendClassType.");
checkNoEntry();
}
}
}
}
return false;
}
const FMetasoundFrontendGraphClassPresetOptions& FGraphController::GetGraphPresetOptions() const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->PresetOptions;
}
return Invalid::GetInvalidGraphClassPresetOptions();
}
void FGraphController::SetGraphPresetOptions(const FMetasoundFrontendGraphClassPresetOptions& InPresetOptions)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
GraphClass->PresetOptions = InPresetOptions;
}
}
const FMetasoundFrontendClassMetadata& FGraphController::GetGraphMetadata() const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->Metadata;
}
return Invalid::GetInvalidClassMetadata();
}
void FGraphController::SetGraphMetadata(const FMetasoundFrontendClassMetadata& InMetadata)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
GraphClass->Metadata = InMetadata;
}
}
FNodeHandle FGraphController::CreateEmptySubgraph(const FMetasoundFrontendClassMetadata& InMetadata)
{
if (InMetadata.GetType() == EMetasoundFrontendClassType::Graph)
{
if (const FMetasoundFrontendClass* ExistingDependency = OwningDocument->FindClass(InMetadata).Get())
{
UE_LOG(LogMetaSound, Error, TEXT("Cannot add new subgraph. Metasound class already exists with matching metadata Name: \"%s\", Version %d.%d"), *(ExistingDependency->Metadata.GetClassName().ToString()), ExistingDependency->Metadata.GetVersion().Major, ExistingDependency->Metadata.GetVersion().Minor);
}
else
{
return AddNode(OwningDocument->FindOrAddClass(InMetadata), FGuid::NewGuid());
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Incompatible Metasound NodeType encountered when attempting to create an empty subgraph. NodeType must equal EMetasoundFrontendClassType::Graph"));
}
return INodeController::GetInvalidHandle();
}
TUniquePtr<IOperator> FGraphController::BuildOperator(const FOperatorSettings& InSettings, const FMetasoundEnvironment& InEnvironment, FBuildResults& OutResults) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
const TArray<FMetasoundFrontendGraphClass>& Subgraphs = OwningDocument->GetSubgraphs();
const TArray<FMetasoundFrontendClass>& Dependencies = OwningDocument->GetDependencies();
FString UnknownAsset = TEXT("UnknownAsset");
FProxyDataCache ProxyCache;
const FMetasoundFrontendDocument& Doc = *(OwningDocument->GetDocumentPtr().Get());
ProxyCache.CreateAndCacheProxies(Doc);
TUniquePtr<FFrontendGraph> Graph = FGraphBuilder::CreateGraph(*GraphClass, Subgraphs, Dependencies, ProxyCache, UnknownAsset, Frontend::CreateLocallyUniqueId());
if (!Graph.IsValid())
{
return TUniquePtr<IOperator>(nullptr);
}
FInputVertexInterfaceData InterfaceData;
FBuildGraphOperatorParams BuildParams { *Graph, InSettings, InterfaceData, InEnvironment };
return FOperatorBuilder(FOperatorBuilderSettings::GetDefaultSettings()).BuildGraphOperator(BuildParams, OutResults);
}
else
{
return TUniquePtr<IOperator>(nullptr);
}
}
FDocumentHandle FGraphController::GetOwningDocument()
{
return OwningDocument;
}
FConstDocumentHandle FGraphController::GetOwningDocument() const
{
return OwningDocument;
}
FNodeHandle FGraphController::AddNode(FConstClassAccessPtr InExistingDependency, FGuid InNodeGuid)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
if (const FMetasoundFrontendClass* NodeClass = InExistingDependency.Get())
{
TInstancedStruct<FMetaSoundFrontendNodeConfiguration> Configuration;
if (NodeClass->Metadata.GetType() != EMetasoundFrontendClassType::Graph) // Subgraphs do not have extensions
{
Configuration = INodeClassRegistry::Get()->CreateFrontendNodeConfiguration(FNodeClassRegistryKey(NodeClass->Metadata));
}
FMetasoundFrontendNode& Node = FindBuildGraphChecked(*GraphClass).Nodes.Emplace_GetRef(*NodeClass, MoveTemp(Configuration));
// Cache the asset name on the node if it node is reference to asset-defined graph.
// AssetManager may not exist if this is called from a build that does not load the
// engine module (ex. unit test builds which only load frontend and core), or any build
// where the AssetManager more generally has not been implemented.
if (IMetaSoundAssetManager* AssetManager = IMetaSoundAssetManager::Get())
{
if (NodeClass->Metadata.GetType() == EMetasoundFrontendClassType::External)
{
const FNodeRegistryKey RegistryKey = FNodeRegistryKey(NodeClass->Metadata);
const FTopLevelAssetPath Path = AssetManager->FindAssetPath(RegistryKey);
if (Path.IsValid())
{
Node.Name = Path.GetAssetName();
}
}
}
Node.UpdateID(InNodeGuid);
#if WITH_EDITOR
if (FMetasoundFrontendDocumentMetadata* DocMetadata = OwningDocument->GetMetadata())
{
DocMetadata->ModifyContext.AddNodeIDModified(InNodeGuid);
}
#endif // WITH_EDITOR
FNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.GetID());
return GetNodeHandle(FGraphController::FNodeAndClass { NodePtr, InExistingDependency });
}
}
return INodeController::GetInvalidHandle();
}
bool FGraphController::RemoveNode(const FMetasoundFrontendNode& InDesc)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
// Remove any reference connections
FMetasoundFrontendGraph& Graph = FindBuildGraphChecked(*GraphClass);
#if WITH_EDITORONLY_DATA
{
auto IsStyleForThisNode = [&](const FMetasoundFrontendEdgeStyle& EdgeStyle) { return (EdgeStyle.NodeID == InDesc.GetID()); };
Graph.Style.EdgeStyles.RemoveAllSwap(IsStyleForThisNode);
}
#endif // WITH_EDITORONLY_DATA
auto IsEdgeForThisNode = [&](const FMetasoundFrontendEdge& ConDesc) { return (ConDesc.FromNodeID == InDesc.GetID()) || (ConDesc.ToNodeID == InDesc.GetID()); };
int32 NumRemoved = Graph.Edges.RemoveAll(IsEdgeForThisNode);
auto IsNodeWithID = [&](const FMetasoundFrontendNode& Desc) { return InDesc.GetID() == Desc.GetID(); };
NumRemoved += Graph.Nodes.RemoveAll(IsNodeWithID);
OwningDocument->RemoveUnreferencedDependencies();
#if WITH_EDITOR
if (NumRemoved > 0)
{
if (FMetasoundFrontendDocumentMetadata* DocMetadata = OwningDocument->GetMetadata())
{
DocMetadata->ModifyContext.AddNodeIDModified(InDesc.GetID());
}
}
#endif // WITH_EDITOR
return (NumRemoved > 0);
}
return false;
}
bool FGraphController::RemoveInput(const FMetasoundFrontendNode& InNode)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsInputWithSameNodeID = [&](const FMetasoundFrontendClassInput& ClassInput) { return ClassInput.NodeID == InNode.GetID(); };
int32 NumInputsRemoved = GraphClass->GetDefaultInterface().Inputs.RemoveAll(IsInputWithSameNodeID);
if (NumInputsRemoved > 0)
{
#if WITH_EDITOR
if (FMetasoundFrontendDocumentMetadata* DocMetadata = OwningDocument->GetMetadata())
{
DocMetadata->ModifyContext.AddMemberIDModified(InNode.GetID());
}
#endif // WITH_EDITOR
GraphClass->GetDefaultInterface().UpdateChangeID();
}
bool bDidRemoveNode = RemoveNode(InNode);
return NumInputsRemoved > 0 || bDidRemoveNode;
}
return false;
}
bool FGraphController::RemoveOutput(const FMetasoundFrontendNode& InNode)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsOutputWithSameNodeID = [&](const FMetasoundFrontendClassOutput& ClassOutput) { return ClassOutput.NodeID == InNode.GetID(); };
int32 NumOutputsRemoved = GraphClass->GetDefaultInterface().Outputs.RemoveAll(IsOutputWithSameNodeID);
if (NumOutputsRemoved)
{
#if WITH_EDITOR
if (FMetasoundFrontendDocumentMetadata* DocMetadata = OwningDocument->GetMetadata())
{
DocMetadata->ModifyContext.AddMemberIDModified(InNode.GetID());
}
#endif // WITH_EDITOR
GraphClass->GetDefaultInterface().UpdateChangeID();
}
bool bDidRemoveNode = RemoveNode(InNode);
return (NumOutputsRemoved > 0) || bDidRemoveNode;
}
return false;
}
void FGraphController::UpdateInterfaceChangeID()
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
GraphClass->GetDefaultInterface().UpdateChangeID();
}
}
bool FGraphController::ContainsNodesAndClassesByPredicate(TFunctionRef<bool (const FMetasoundFrontendClass&, const FMetasoundFrontendNode&)> InPredicate) const
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
for (FMetasoundFrontendNode& Node : FindBuildGraphChecked(*GraphClass).Nodes)
{
const FMetasoundFrontendClass* NodeClass = OwningDocument->FindClassWithID(Node.ClassID).Get();
if (nullptr != NodeClass)
{
if (InPredicate(*NodeClass, Node))
{
return true;
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Failed to find class for node [NodeID:%s, ClassID:%s]"), *Node.GetID().ToString(), *Node.ClassID.ToString());
}
}
}
return false;
}
TArray<FGraphController::FNodeAndClass> FGraphController::GetNodesAndClasses()
{
TArray<FNodeAndClass> NodesAndClasses;
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
for (FMetasoundFrontendNode& Node : FindBuildGraphChecked(*GraphClass).Nodes)
{
FNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.GetID());
FConstClassAccessPtr NodeClassPtr = OwningDocument->FindClassWithID(Node.ClassID);
const bool bIsValidNodePtr = (nullptr != NodePtr.Get());
const bool bIsValidNodeClassPtr = (nullptr != NodeClassPtr.Get());
if (bIsValidNodePtr && bIsValidNodeClassPtr)
{
NodesAndClasses.Add(FGraphController::FNodeAndClass{ NodePtr, NodeClassPtr });
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Failed to find class for node [NodeID:%s, ClassID:%s]"), *Node.GetID().ToString(), *Node.ClassID.ToString());
}
}
}
return NodesAndClasses;
}
TArray<FGraphController::FConstNodeAndClass> FGraphController::GetNodesAndClasses() const
{
TArray<FConstNodeAndClass> NodesAndClasses;
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
const TArray<FMetasoundFrontendNode>& Nodes = FindConstBuildGraphChecked(*GraphClass).Nodes;
for (const FMetasoundFrontendNode& Node : Nodes)
{
FConstNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.GetID());
FConstClassAccessPtr NodeClassPtr = OwningDocument->FindClassWithID(Node.ClassID);
const bool bIsValidNodePtr = (nullptr != NodePtr.Get());
const bool bIsValidNodeClassPtr = (nullptr != NodeClassPtr.Get());
if (bIsValidNodePtr && bIsValidNodeClassPtr)
{
NodesAndClasses.Add(FGraphController::FConstNodeAndClass{ NodePtr, NodeClassPtr });
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Failed to find class for node [NodeID:%s, ClassID:%s]"), *Node.GetID().ToString(), *Node.ClassID.ToString());
}
}
}
return NodesAndClasses;
}
TArray<FGraphController::FNodeAndClass> FGraphController::GetNodesAndClassesByPredicate(TFunctionRef<bool (const FMetasoundFrontendClass&, const FMetasoundFrontendNode&)> InPredicate)
{
TArray<FNodeAndClass> NodesAndClasses;
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
for (FMetasoundFrontendNode& Node : FindBuildGraphChecked(*GraphClass).Nodes)
{
FConstClassAccessPtr NodeClassPtr = OwningDocument->FindClassWithID(Node.ClassID);
if (const FMetasoundFrontendClass* NodeClass = NodeClassPtr.Get())
{
if (InPredicate(*NodeClass, Node))
{
FNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.GetID());
NodesAndClasses.Add(FGraphController::FNodeAndClass{ NodePtr, NodeClassPtr });
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Failed to find class for node [NodeID:%s, ClassID:%s]"), *Node.GetID().ToString(), *Node.ClassID.ToString());
}
}
}
return NodesAndClasses;
}
TArray<FGraphController::FConstNodeAndClass> FGraphController::GetNodesAndClassesByPredicate(TFunctionRef<bool (const FMetasoundFrontendClass&, const FMetasoundFrontendNode&)> InPredicate) const
{
TArray<FConstNodeAndClass> NodesAndClasses;
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
const TArray<FMetasoundFrontendNode>& Nodes = FindConstBuildGraphChecked(*GraphClass).Nodes;
for (const FMetasoundFrontendNode& Node : Nodes)
{
FConstClassAccessPtr NodeClassPtr = OwningDocument->FindClassWithID(Node.ClassID);
if (const FMetasoundFrontendClass* NodeClass = NodeClassPtr.Get())
{
if (InPredicate(*NodeClass, Node))
{
FConstNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.GetID());
NodesAndClasses.Add(FGraphController::FConstNodeAndClass{ NodePtr, NodeClassPtr });
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Failed to find class for node [NodeID:%s, ClassID:%s]"), *Node.GetID().ToString(), *Node.ClassID.ToString());
}
}
}
return NodesAndClasses;
}
FNodeHandle FGraphController::GetNodeByPredicate(TFunctionRef<bool (const FMetasoundFrontendClass&, const FMetasoundFrontendNode&)> InPredicate)
{
TArray<FNodeAndClass> NodeAndClass = GetNodesAndClassesByPredicate(InPredicate);
if (NodeAndClass.Num() > 0)
{
return GetNodeHandle(NodeAndClass[0]);
}
return INodeController::GetInvalidHandle();
}
FConstNodeHandle FGraphController::GetNodeByPredicate(TFunctionRef<bool (const FMetasoundFrontendClass&, const FMetasoundFrontendNode&)> InPredicate) const
{
TArray<FConstNodeAndClass> NodeAndClass = GetNodesAndClassesByPredicate(InPredicate);
if (NodeAndClass.Num() > 0)
{
return GetNodeHandle(NodeAndClass[0]);
}
return INodeController::GetInvalidHandle();
}
TArray<FNodeHandle> FGraphController::GetNodesByPredicate(TFunctionRef<bool (const FMetasoundFrontendClass&, const FMetasoundFrontendNode&)> InFilterFunc)
{
return GetNodeHandles(GetNodesAndClassesByPredicate(InFilterFunc));
}
TArray<FConstNodeHandle> FGraphController::GetNodesByPredicate(TFunctionRef<bool (const FMetasoundFrontendClass&, const FMetasoundFrontendNode&)> InFilterFunc) const
{
return GetNodeHandles(GetNodesAndClassesByPredicate(InFilterFunc));
}
TArray<FNodeHandle> FGraphController::GetNodeHandles(TArrayView<const FGraphController::FNodeAndClass> InNodesAndClasses)
{
TArray<FNodeHandle> Nodes;
for (const FNodeAndClass& NodeAndClass: InNodesAndClasses)
{
FNodeHandle NodeController = GetNodeHandle(NodeAndClass);
if (NodeController->IsValid())
{
Nodes.Add(NodeController);
}
}
return Nodes;
}
TArray<FConstNodeHandle> FGraphController::GetNodeHandles(TArrayView<const FGraphController::FConstNodeAndClass> InNodesAndClasses) const
{
TArray<FConstNodeHandle> Nodes;
for (const FConstNodeAndClass& NodeAndClass : InNodesAndClasses)
{
FConstNodeHandle NodeController = GetNodeHandle(NodeAndClass);
if (NodeController->IsValid())
{
Nodes.Add(NodeController);
}
}
return Nodes;
}
FNodeHandle FGraphController::GetNodeHandle(const FGraphController::FNodeAndClass& InNodeAndClass)
{
FMetasoundFrontendNode* Node = InNodeAndClass.Node.Get();
const FMetasoundFrontendClass* NodeClass = InNodeAndClass.Class.Get();
if ((nullptr != Node) && (nullptr != NodeClass))
{
FGraphHandle OwningGraph = this->AsShared();
FGraphAccessPtr GraphPtr = GraphClassPtr.GetGraph();
switch (NodeClass->Metadata.GetType())
{
case EMetasoundFrontendClassType::Input:
{
FClassInputAccessPtr OwningGraphClassInputPtr = FindInputDescriptionWithNodeID(Node->GetID());
if (nullptr != OwningGraphClassInputPtr.Get())
{
FInputNodeController::FInitParams InitParams
{
InNodeAndClass.Node,
InNodeAndClass.Class,
OwningGraphClassInputPtr,
GraphPtr,
OwningGraph
};
return FInputNodeController::CreateInputNodeHandle(InitParams);
}
else
{
// TODO: This supports input nodes introduced during subgraph inflation. Input nodes
// should be replaced with value nodes once they are implemented.
FNodeController::FInitParams InitParams
{
InNodeAndClass.Node,
InNodeAndClass.Class,
GraphPtr,
OwningGraph
};
return FNodeController::CreateNodeHandle(InitParams);
}
}
case EMetasoundFrontendClassType::Output:
{
FClassOutputAccessPtr OwningGraphClassOutputPtr = FindOutputDescriptionWithNodeID(Node->GetID());
if (nullptr != OwningGraphClassOutputPtr.Get())
{
FOutputNodeController::FInitParams InitParams
{
InNodeAndClass.Node,
InNodeAndClass.Class,
OwningGraphClassOutputPtr,
GraphPtr,
OwningGraph
};
return FOutputNodeController::CreateOutputNodeHandle(InitParams);
}
else
{
// TODO: This supports output nodes introduced during subgraph inflation. Output nodes
// should be replaced with value nodes once they are implemented.
FNodeController::FInitParams InitParams
{
InNodeAndClass.Node,
InNodeAndClass.Class,
GraphPtr,
OwningGraph
};
return FNodeController::CreateNodeHandle(InitParams);
}
}
case EMetasoundFrontendClassType::Variable:
case EMetasoundFrontendClassType::VariableAccessor:
case EMetasoundFrontendClassType::VariableDeferredAccessor:
case EMetasoundFrontendClassType::VariableMutator:
{
FVariableNodeController::FInitParams InitParams
{
InNodeAndClass.Node,
InNodeAndClass.Class,
GraphPtr,
OwningGraph
};
return FVariableNodeController::CreateNodeHandle(InitParams);
}
case EMetasoundFrontendClassType::External:
case EMetasoundFrontendClassType::Template:
{
FNodeController::FInitParams InitParams
{
InNodeAndClass.Node,
InNodeAndClass.Class,
GraphPtr,
OwningGraph
};
return FNodeController::CreateNodeHandle(InitParams);
}
case EMetasoundFrontendClassType::Graph:
{
FSubgraphNodeController::FInitParams InitParams
{
InNodeAndClass.Node,
InNodeAndClass.Class,
GraphPtr,
OwningGraph
};
return FSubgraphNodeController::CreateNodeHandle(InitParams);
}
default:
checkNoEntry();
static_assert(static_cast<int32>(EMetasoundFrontendClassType::Invalid) == 10, "Possible missing switch case coverage for EMetasoundFrontendClassType.");
}
}
return INodeController::GetInvalidHandle();
}
FConstNodeHandle FGraphController::GetNodeHandle(const FGraphController::FConstNodeAndClass& InNodeAndClass) const
{
const FMetasoundFrontendNode* Node = InNodeAndClass.Node.Get();
const FMetasoundFrontendClass* NodeClass = InNodeAndClass.Class.Get();
if ((nullptr != Node) && (nullptr != NodeClass))
{
FConstGraphHandle OwningGraph = this->AsShared();
FConstGraphAccessPtr GraphPtr = GraphClassPtr.GetGraph();
switch (NodeClass->Metadata.GetType())
{
case EMetasoundFrontendClassType::Input:
{
FConstClassInputAccessPtr OwningGraphClassInputPtr = FindInputDescriptionWithNodeID(Node->GetID());
if (nullptr != OwningGraphClassInputPtr.Get())
{
FInputNodeController::FInitParams InitParams
{
ConstCastAccessPtr<FNodeAccessPtr>(InNodeAndClass.Node),
InNodeAndClass.Class,
ConstCastAccessPtr<FClassInputAccessPtr>(OwningGraphClassInputPtr),
ConstCastAccessPtr<FGraphAccessPtr>(GraphPtr),
ConstCastSharedRef<IGraphController>(OwningGraph)
};
return FInputNodeController::CreateConstInputNodeHandle(InitParams);
}
}
break;
case EMetasoundFrontendClassType::Output:
{
FConstClassOutputAccessPtr OwningGraphClassOutputPtr = FindOutputDescriptionWithNodeID(Node->GetID());
if (nullptr != OwningGraphClassOutputPtr.Get())
{
FOutputNodeController::FInitParams InitParams
{
ConstCastAccessPtr<FNodeAccessPtr>(InNodeAndClass.Node),
InNodeAndClass.Class,
ConstCastAccessPtr<FClassOutputAccessPtr>(OwningGraphClassOutputPtr),
ConstCastAccessPtr<FGraphAccessPtr>(GraphPtr),
ConstCastSharedRef<IGraphController>(OwningGraph)
};
return FOutputNodeController::CreateConstOutputNodeHandle(InitParams);
}
}
break;
case EMetasoundFrontendClassType::Variable:
case EMetasoundFrontendClassType::VariableAccessor:
case EMetasoundFrontendClassType::VariableDeferredAccessor:
case EMetasoundFrontendClassType::VariableMutator:
{
FVariableNodeController::FInitParams InitParams
{
ConstCastAccessPtr<FNodeAccessPtr>(InNodeAndClass.Node),
InNodeAndClass.Class,
ConstCastAccessPtr<FGraphAccessPtr>(GraphPtr),
ConstCastSharedRef<IGraphController>(OwningGraph)
};
return FVariableNodeController::CreateConstNodeHandle(InitParams);
}
case EMetasoundFrontendClassType::External:
case EMetasoundFrontendClassType::Template:
{
FNodeController::FInitParams InitParams
{
ConstCastAccessPtr<FNodeAccessPtr>(InNodeAndClass.Node),
InNodeAndClass.Class,
ConstCastAccessPtr<FGraphAccessPtr>(GraphPtr),
ConstCastSharedRef<IGraphController>(OwningGraph)
};
return FNodeController::CreateConstNodeHandle(InitParams);
}
case EMetasoundFrontendClassType::Graph:
{
FSubgraphNodeController::FInitParams InitParams
{
ConstCastAccessPtr<FNodeAccessPtr>(InNodeAndClass.Node),
InNodeAndClass.Class,
ConstCastAccessPtr<FGraphAccessPtr>(GraphPtr),
ConstCastSharedRef<IGraphController>(OwningGraph)
};
return FSubgraphNodeController::CreateConstNodeHandle(InitParams);
}
default:
checkNoEntry();
static_assert(static_cast<int32>(EMetasoundFrontendClassType::Invalid) == 10, "Possible missing switch case coverage for EMetasoundFrontendClassType.");
}
}
return INodeController::GetInvalidHandle();
}
FMetasoundFrontendClassInput* FGraphController::FindInputDescriptionWithName(const FVertexName& InName)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
FMetasoundFrontendClassInput* ClassInput = GraphClass->GetDefaultInterface().Inputs.FindByPredicate([&](const FMetasoundFrontendClassInput& Desc) { return Desc.Name == InName; });
if (ClassInput)
{
// TODO: This assumes the class input is being mutated due to the adjacent const correct call not being utilized.
// Make this more explicit rather than risking whether or not the caller is using proper const correctness.
GraphClass->GetDefaultInterface().UpdateChangeID();
return ClassInput;
}
}
return nullptr;
}
const FMetasoundFrontendClassInput* FGraphController::FindInputDescriptionWithName(const FVertexName& InName) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->GetDefaultInterface().Inputs.FindByPredicate([&](const FMetasoundFrontendClassInput& Desc) { return Desc.Name == InName; });
}
return nullptr;
}
FMetasoundFrontendClassOutput* FGraphController::FindOutputDescriptionWithName(const FVertexName& InName)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
FMetasoundFrontendClassOutput* ClassOutput = GraphClass->GetDefaultInterface().Outputs.FindByPredicate([&](const FMetasoundFrontendClassOutput& Desc) { return Desc.Name == InName; });
if (ClassOutput)
{
// TODO: This assumes the class input is being mutated due to the adjacent const correct call not being utilized.
// Make this more explicit rather than risking whether or not the caller is using proper const correctness.
GraphClass->GetDefaultInterface().UpdateChangeID();
return ClassOutput;
}
}
return nullptr;
}
const FMetasoundFrontendClassOutput* FGraphController::FindOutputDescriptionWithName(const FVertexName& InName) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->GetDefaultInterface().Outputs.FindByPredicate([&](const FMetasoundFrontendClassOutput& Desc) { return Desc.Name == InName; });
}
return nullptr;
}
FMetasoundFrontendClassInput* FGraphController::FindInputDescriptionWithVertexID(const FGuid& InVertexID)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
FMetasoundFrontendClassInput* ClassInput = GraphClass->GetDefaultInterface().Inputs.FindByPredicate([&](const FMetasoundFrontendClassInput& Desc) { return Desc.VertexID == InVertexID; });
if (ClassInput)
{
// TODO: This assumes the class input is being mutated due to the adjacent const correct call not being utilized.
// Make this more explicit rather than risking whether or not the caller is using proper const correctness.
GraphClass->GetDefaultInterface().UpdateChangeID();
return ClassInput;
}
}
return nullptr;
}
const FMetasoundFrontendClassInput* FGraphController::FindInputDescriptionWithVertexID(const FGuid& InVertexID) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->GetDefaultInterface().Inputs.FindByPredicate([&](const FMetasoundFrontendClassInput& Desc) { return Desc.VertexID == InVertexID; });
}
return nullptr;
}
FMetasoundFrontendClassOutput* FGraphController::FindOutputDescriptionWithVertexID(const FGuid& InVertexID)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
FMetasoundFrontendClassOutput* ClassOutput = GraphClass->GetDefaultInterface().Outputs.FindByPredicate([&](const FMetasoundFrontendClassOutput& Desc) { return Desc.VertexID == InVertexID; });
if (ClassOutput)
{
// TODO: This assumes the class input is being mutated due to the adjacent const correct call not being utilized.
// Make this more explicit rather than risking whether or not the caller is using proper const correctness.
GraphClass->GetDefaultInterface().UpdateChangeID();
return ClassOutput;
}
}
return nullptr;
}
const FMetasoundFrontendClassOutput* FGraphController::FindOutputDescriptionWithVertexID(const FGuid& InVertexID) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->GetDefaultInterface().Outputs.FindByPredicate([&](const FMetasoundFrontendClassOutput& Desc) { return Desc.VertexID == InVertexID; });
}
return nullptr;
}
FClassInputAccessPtr FGraphController::FindInputDescriptionWithNodeID(FGuid InNodeID)
{
return GraphClassPtr.GetInputWithNodeID(InNodeID);
}
FConstClassInputAccessPtr FGraphController::FindInputDescriptionWithNodeID(FGuid InNodeID) const
{
return GraphClassPtr.GetInputWithNodeID(InNodeID);
}
FClassOutputAccessPtr FGraphController::FindOutputDescriptionWithNodeID(FGuid InNodeID)
{
return GraphClassPtr.GetOutputWithNodeID(InNodeID);
}
FConstClassOutputAccessPtr FGraphController::FindOutputDescriptionWithNodeID(FGuid InNodeID) const
{
return GraphClassPtr.GetOutputWithNodeID(InNodeID);
}
FDocumentAccess FGraphController::ShareAccess()
{
FDocumentAccess Access;
Access.GraphClass = GraphClassPtr;
Access.ConstGraphClass = GraphClassPtr;
return Access;
}
FConstDocumentAccess FGraphController::ShareAccess() const
{
FConstDocumentAccess Access;
Access.ConstGraphClass = GraphClassPtr;
return Access;
}
}
}
#undef LOCTEXT_NAMESPACE