You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb trivial #jira UE-179513 #rnx #preflight 640958338832f48a4de03600 [CL 24572248 by rob gay in ue5-main branch]
1384 lines
47 KiB
C++
1384 lines
47 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
#include "MetasoundFrontendDocumentBuilder.h"
|
|
|
|
#include "Algo/AnyOf.h"
|
|
#include "Algo/Find.h"
|
|
#include "Algo/NoneOf.h"
|
|
#include "Algo/Transform.h"
|
|
#include "MetasoundAssetBase.h"
|
|
#include "MetasoundAssetManager.h"
|
|
#include "MetasoundDocumentInterface.h"
|
|
#include "MetasoundFrontendArchetypeRegistry.h"
|
|
#include "MetasoundFrontendDocumentCache.h"
|
|
#include "MetasoundFrontendDocument.h"
|
|
#include "MetasoundFrontendRegistries.h"
|
|
#include "MetasoundFrontendSearchEngine.h"
|
|
#include "MetasoundFrontendTransform.h"
|
|
#include "MetasoundTrace.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(MetasoundFrontendDocumentBuilder)
|
|
|
|
|
|
namespace Metasound::Frontend
|
|
{
|
|
namespace DocumentBuilderPrivate
|
|
{
|
|
void FinalizeGraphVertexNode(FMetasoundFrontendNode& InOutNode, const FMetasoundFrontendClassVertex& InVertex)
|
|
{
|
|
InOutNode.Name = InVertex.Name;
|
|
// Set name on related vertices of input node
|
|
auto IsVertexWithTypeName = [&InVertex](const FMetasoundFrontendVertex& Vertex) { return Vertex.TypeName == InVertex.TypeName; };
|
|
if (FMetasoundFrontendVertex* InputVertex = InOutNode.Interface.Inputs.FindByPredicate(IsVertexWithTypeName))
|
|
{
|
|
InputVertex->Name = InVertex.Name;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogMetaSound, Error, TEXT("Node associated with graph vertex of type '%s' does not contain input vertex with type '%s'"), *InVertex.TypeName.ToString());
|
|
}
|
|
|
|
if (FMetasoundFrontendVertex* OutputVertex = InOutNode.Interface.Outputs.FindByPredicate(IsVertexWithTypeName))
|
|
{
|
|
OutputVertex->Name = InVertex.Name;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogMetaSound, Error, TEXT("Node associated with graph vertex of type '%s' does not contain output vertex of matching type."), *InVertex.TypeName.ToString());
|
|
}
|
|
}
|
|
} // namespace DocumentBuilderPrivate
|
|
} // namespace Metasound::Frontend
|
|
|
|
FMetaSoundFrontendDocumentBuilder::FMetaSoundFrontendDocumentBuilder()
|
|
{
|
|
}
|
|
|
|
FMetaSoundFrontendDocumentBuilder::FMetaSoundFrontendDocumentBuilder(TScriptInterface<IMetaSoundDocumentInterface> InDocumentInterface)
|
|
: DocumentInterface(InDocumentInterface)
|
|
{
|
|
ReloadCache();
|
|
}
|
|
|
|
FMetaSoundFrontendDocumentBuilder::FMetaSoundFrontendDocumentBuilder(const FMetaSoundFrontendDocumentBuilder& InBuilder)
|
|
{
|
|
DocumentInterface = InBuilder.DocumentInterface;
|
|
if (DocumentInterface)
|
|
{
|
|
ReloadCache();
|
|
}
|
|
}
|
|
|
|
FMetaSoundFrontendDocumentBuilder::FMetaSoundFrontendDocumentBuilder(FMetaSoundFrontendDocumentBuilder&& InBuilder)
|
|
: DocumentInterface(MoveTemp(InBuilder.DocumentInterface))
|
|
, DocumentCache(MoveTemp(InBuilder.DocumentCache))
|
|
{
|
|
}
|
|
|
|
FMetaSoundFrontendDocumentBuilder& FMetaSoundFrontendDocumentBuilder::operator=(FMetaSoundFrontendDocumentBuilder&& InRHS)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
DocumentInterface = MoveTemp(InRHS.DocumentInterface);
|
|
DocumentCache = MoveTemp(InRHS.DocumentCache);
|
|
return *this;
|
|
}
|
|
|
|
FMetaSoundFrontendDocumentBuilder& FMetaSoundFrontendDocumentBuilder::operator=(const FMetaSoundFrontendDocumentBuilder& InRHS)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
DocumentInterface = InRHS.DocumentInterface;
|
|
if (DocumentInterface)
|
|
{
|
|
ReloadCache();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
const FMetasoundFrontendClass* FMetaSoundFrontendDocumentBuilder::AddGraphDependency(const FMetasoundFrontendGraphClass& InClass)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
checkf(InClass.Metadata.GetType() == EMetasoundFrontendClassType::Graph, TEXT("Call should only be used for 'Graph' type dependencies. Use 'AddNativeDependency' for other class types."));
|
|
|
|
FMetasoundFrontendDocument& Document = GetDocument();
|
|
const FMetasoundFrontendClass* Dependency = nullptr;
|
|
|
|
FMetasoundFrontendClass NewDependency = InClass;
|
|
|
|
// All dependencies are listed as 'External' from the perspective of the owning document.
|
|
// This makes them implementation agnostic to accommodate nativization of assets.
|
|
NewDependency.Metadata.SetType(EMetasoundFrontendClassType::External);
|
|
NewDependency.ID = FGuid::NewGuid();
|
|
Dependency = &Document.Dependencies.Emplace_GetRef(MoveTemp(NewDependency));
|
|
|
|
const int32 NewIndex = Document.Dependencies.Num() - 1;
|
|
DocumentCache->OnDependencyAdded(NewIndex);
|
|
|
|
return Dependency;
|
|
}
|
|
|
|
const FMetasoundFrontendClass* FMetaSoundFrontendDocumentBuilder::AddNativeDependency(const FMetasoundFrontendClassMetadata& InClassMetadata)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
checkf(InClassMetadata.GetType() != EMetasoundFrontendClassType::Graph, TEXT("Graph dependencies must be added via 'AddGraphDependency' to avoid overhead of registration where not necessary"));
|
|
|
|
FMetasoundFrontendDocument& Document = GetDocument();
|
|
const FMetasoundFrontendClass* Dependency = nullptr;
|
|
|
|
const FNodeRegistryKey RegistryKey = NodeRegistryKey::CreateKey(InClassMetadata);
|
|
FMetasoundFrontendClass NewDependency = GenerateClass(RegistryKey);
|
|
NewDependency.ID = FGuid::NewGuid();
|
|
Dependency = &Document.Dependencies.Emplace_GetRef(MoveTemp(NewDependency));
|
|
|
|
const int32 NewIndex = Document.Dependencies.Num() - 1;
|
|
DocumentCache->OnDependencyAdded(NewIndex);
|
|
|
|
return Dependency;
|
|
}
|
|
|
|
const FMetasoundFrontendEdge* FMetaSoundFrontendDocumentBuilder::AddEdge(FMetasoundFrontendEdge&& InNewEdge)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendDocument& Document = GetDocument();
|
|
FMetasoundFrontendGraph& Graph = Document.RootGraph.Graph;
|
|
if (CanAddEdge(InNewEdge))
|
|
{
|
|
IDocumentGraphEdgeCache& EdgeCache = DocumentCache->GetEdgeCache();
|
|
|
|
const FMetasoundFrontendEdge& NewEdge = Graph.Edges.Add_GetRef(MoveTemp(InNewEdge));
|
|
const int32 NewIndex = Graph.Edges.Num() - 1;
|
|
EdgeCache.OnEdgeAdded(NewIndex);
|
|
return &NewEdge;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::AddEdgesByNamedConnections(const TSet<Metasound::Frontend::FNamedEdge>& ConnectionsToMake, TArray<const FMetasoundFrontendEdge*>* OutNewEdges)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendDocument& Document = GetDocument();
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
|
|
if (OutNewEdges)
|
|
{
|
|
OutNewEdges->Reset();
|
|
}
|
|
|
|
bool bSuccess = true;
|
|
|
|
TArray<FMetasoundFrontendEdge> EdgesToAdd;
|
|
for (const FNamedEdge& Connection : ConnectionsToMake)
|
|
{
|
|
const FMetasoundFrontendGraph& Graph = Document.RootGraph.Graph;
|
|
const FMetasoundFrontendVertex* OutputVertex = NodeCache.FindOutputVertex(Connection.OutputNodeID, Connection.OutputName);
|
|
const FMetasoundFrontendVertex* InputVertex = NodeCache.FindInputVertex(Connection.InputNodeID, Connection.InputName);
|
|
|
|
if (OutputVertex && InputVertex)
|
|
{
|
|
FMetasoundFrontendEdge NewEdge = { Connection.OutputNodeID, OutputVertex->VertexID, Connection.InputNodeID, InputVertex->VertexID };
|
|
if (CanAddEdge(NewEdge))
|
|
{
|
|
EdgesToAdd.Add(MoveTemp(NewEdge));
|
|
}
|
|
else
|
|
{
|
|
bSuccess = false;
|
|
UE_LOG(LogMetaSound, Warning, TEXT("Failed to add connections between MetaSound output '%s' and input '%s': Vertex types either incompatable (eg. construction pin to non-construction pin) or input already connected."), *Connection.OutputName.ToString(), *Connection.InputName.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
for (FMetasoundFrontendEdge& EdgeToAdd : EdgesToAdd)
|
|
{
|
|
const FMetasoundFrontendEdge* NewEdge = AddEdge(MoveTemp(EdgeToAdd));
|
|
if (ensureAlwaysMsgf(NewEdge, TEXT("Failed to add MetaSound graph edge via DocumentBuilder when prior step validated edge add was valid")))
|
|
{
|
|
if (OutNewEdges)
|
|
{
|
|
OutNewEdges->Add(NewEdge);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::AddEdgesByNodeClassInterfaceBindings(const FGuid& InFromNodeID, const FGuid& InToNodeID)
|
|
{
|
|
using namespace Metasound;
|
|
using namespace Metasound::Frontend;
|
|
|
|
auto FindNodeClassDocAndInterfaces = [this](const FGuid & InNodeID) -> const FMetasoundFrontendDocument*
|
|
{
|
|
const TScriptInterface<IMetaSoundDocumentInterface> NodeDocumentUInterface = FindOrLoadNodeClassDocument(InNodeID);
|
|
if (NodeDocumentUInterface)
|
|
{
|
|
return &NodeDocumentUInterface->GetDocument();
|
|
}
|
|
|
|
return nullptr;
|
|
};
|
|
|
|
const FMetasoundFrontendDocument* OutputNodeDocument = FindNodeClassDocAndInterfaces(InFromNodeID);
|
|
const FMetasoundFrontendDocument* InputNodeDocument = FindNodeClassDocAndInterfaces(InToNodeID);
|
|
|
|
if (!InputNodeDocument || !OutputNodeDocument)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const TSet<FMetasoundFrontendVersion>& OutputInterfaceVersions = OutputNodeDocument->Interfaces;
|
|
|
|
TArray<const IInterfaceRegistryEntry*> InputNodeInterfaces;
|
|
FindDeclaredInterfaces(*InputNodeDocument, InputNodeInterfaces);
|
|
|
|
TSet<FNamedEdge> ConnectionsToMake;
|
|
TSet<FName> InputsToConnect;
|
|
for (const IInterfaceRegistryEntry* InputInterfaceEntry : InputNodeInterfaces)
|
|
{
|
|
const FMetasoundFrontendInterface& InputInterface = InputInterfaceEntry->GetInterface();
|
|
const TArray<FMetasoundFrontendInterfaceBinding>& OutputBindings = InputInterfaceEntry->GetOutputBindings();
|
|
|
|
// Bindings are sorted in registry with earlier entries being higher priority to apply connections,
|
|
// so earlier listed connections are selected over potential collisions with later entries.
|
|
for (const FMetasoundFrontendInterfaceBinding& OutputBinding : OutputBindings)
|
|
{
|
|
if (OutputInterfaceVersions.Contains(OutputBinding.Version))
|
|
{
|
|
for (const FMetasoundFrontendInterfaceBindingConnection& Connection : OutputBinding.Connections)
|
|
{
|
|
if (!InputsToConnect.Contains(Connection.InputName))
|
|
{
|
|
InputsToConnect.Add(Connection.InputName);
|
|
ConnectionsToMake.Add(FNamedEdge { InFromNodeID, Connection.OutputName, InToNodeID, Connection.InputName });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return AddEdgesByNamedConnections(ConnectionsToMake);
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::AddEdgesFromMatchingInterfaceNodeOutputsToGraphOutputs(const FGuid& InNodeID, TArray<const FMetasoundFrontendEdge*>& OutEdgesCreated)
|
|
{
|
|
METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(FMetaSoundFrontendDocumentBuilder::AddEdgesFromMatchingInterfaceNodeOutputsToGraphOutputs);
|
|
|
|
using namespace Metasound::Frontend;
|
|
|
|
OutEdgesCreated.Reset();
|
|
|
|
const TScriptInterface<IMetaSoundDocumentInterface> NodeDocumentInterface = FindOrLoadNodeClassDocument(InNodeID);
|
|
if (!NodeDocumentInterface)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
|
|
const TSet<FMetasoundFrontendVersion>& NodeInterfaces = NodeDocumentInterface->GetDocument().Interfaces;
|
|
const TSet<FMetasoundFrontendVersion> CommonInterfaces = NodeInterfaces.Intersect(GetDocument().Interfaces);
|
|
|
|
TSet<FNamedEdge> ConnectionsToMake;
|
|
for (const FMetasoundFrontendVersion& Version : CommonInterfaces)
|
|
{
|
|
const FInterfaceRegistryKey InterfaceKey = GetInterfaceRegistryKey(Version);
|
|
if (const IInterfaceRegistryEntry* RegistryEntry = IInterfaceRegistry::Get().FindInterfaceRegistryEntry(InterfaceKey))
|
|
{
|
|
Algo::Transform(RegistryEntry->GetInterface().Outputs, ConnectionsToMake, [this, &NodeCache, InNodeID](const FMetasoundFrontendClassOutput& Output)
|
|
{
|
|
const FMetasoundFrontendGraph& Graph = GetDocument().RootGraph.Graph;
|
|
const FMetasoundFrontendVertex* NodeVertex = NodeCache.FindOutputVertex(InNodeID, Output.Name);
|
|
check(NodeVertex);
|
|
const FMetasoundFrontendNode* OutputNode = NodeCache.FindOutputNode(Output.Name);
|
|
check(OutputNode);
|
|
const TArray<FMetasoundFrontendVertex>& Inputs = OutputNode->Interface.Inputs;
|
|
check(!Inputs.IsEmpty());
|
|
return FNamedEdge { InNodeID, NodeVertex->Name, OutputNode->GetID(), Inputs.Last().Name };
|
|
});
|
|
}
|
|
}
|
|
|
|
return AddEdgesByNamedConnections(ConnectionsToMake, &OutEdgesCreated);
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::AddEdgesFromMatchingInterfaceNodeInputsToGraphInputs(const FGuid& InNodeID, TArray<const FMetasoundFrontendEdge*>& OutEdgesCreated)
|
|
{
|
|
METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(FMetaSoundFrontendDocumentBuilder::AddEdgesFromMatchingInterfaceNodeInputsToGraphInputs);
|
|
|
|
using namespace Metasound::Frontend;
|
|
|
|
OutEdgesCreated.Reset();
|
|
|
|
const TScriptInterface<IMetaSoundDocumentInterface> NodeDocumentInterface = FindOrLoadNodeClassDocument(InNodeID);
|
|
if (!NodeDocumentInterface)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
|
|
const TSet<FMetasoundFrontendVersion>& NodeInterfaces = NodeDocumentInterface->GetDocument().Interfaces;
|
|
const TSet<FMetasoundFrontendVersion> CommonInterfaces = NodeInterfaces.Intersect(GetDocument().Interfaces);
|
|
|
|
TSet<FNamedEdge> ConnectionsToMake;
|
|
for (const FMetasoundFrontendVersion& Version : CommonInterfaces)
|
|
{
|
|
const FInterfaceRegistryKey InterfaceKey = GetInterfaceRegistryKey(Version);
|
|
if (const IInterfaceRegistryEntry* RegistryEntry = IInterfaceRegistry::Get().FindInterfaceRegistryEntry(InterfaceKey))
|
|
{
|
|
Algo::Transform(RegistryEntry->GetInterface().Inputs, ConnectionsToMake, [this, &NodeCache, InNodeID](const FMetasoundFrontendClassInput& Input)
|
|
{
|
|
const FMetasoundFrontendGraph& Graph = GetDocument().RootGraph.Graph;
|
|
const FMetasoundFrontendVertex* NodeVertex = NodeCache.FindOutputVertex(InNodeID, Input.Name);
|
|
check(NodeVertex);
|
|
const FMetasoundFrontendNode* InputNode = NodeCache.FindInputNode(Input.Name);
|
|
check(InputNode);
|
|
const TArray<FMetasoundFrontendVertex>& Outputs = InputNode->Interface.Outputs;
|
|
check(!Outputs.IsEmpty());
|
|
return FNamedEdge { InNodeID, NodeVertex->Name, InputNode->GetID(), Outputs.Last().Name };
|
|
});
|
|
}
|
|
}
|
|
|
|
return AddEdgesByNamedConnections(ConnectionsToMake, &OutEdgesCreated);
|
|
}
|
|
|
|
const FMetasoundFrontendNode* FMetaSoundFrontendDocumentBuilder::AddGraphInput(const FMetasoundFrontendClassInput& InClassInput)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendGraphClass& RootGraph = GetDocument().RootGraph;
|
|
|
|
checkf(InClassInput.NodeID.IsValid(), TEXT("Unassigned NodeID when adding graph input"));
|
|
checkf(InClassInput.VertexID.IsValid(), TEXT("Unassigned VertexID when adding graph input"));
|
|
|
|
if (InClassInput.TypeName.IsNone())
|
|
{
|
|
UE_LOG(LogMetaSound, Error, TEXT("TypeName unset when attempting to add class input '%s'"), *InClassInput.Name.ToString());
|
|
return nullptr;
|
|
}
|
|
else if (const FMetasoundFrontendNode* Node = DocumentCache->GetNodeCache().FindInputNode(InClassInput.Name))
|
|
{
|
|
UE_LOG(LogMetaSound, Error, TEXT("Attempting to add MetaSound graph input '%s' when input with name already exists"), *InClassInput.Name.ToString());
|
|
return Node;
|
|
}
|
|
|
|
auto FindRegistryClass = [&InClassInput](FMetasoundFrontendClass& OutClass) -> bool
|
|
{
|
|
switch (InClassInput.AccessType)
|
|
{
|
|
case EMetasoundFrontendVertexAccessType::Value:
|
|
{
|
|
return IDataTypeRegistry::Get().GetFrontendConstructorInputClass(InClassInput.TypeName, OutClass);
|
|
}
|
|
break;
|
|
|
|
case EMetasoundFrontendVertexAccessType::Reference:
|
|
{
|
|
return IDataTypeRegistry::Get().GetFrontendInputClass(InClassInput.TypeName, OutClass);
|
|
}
|
|
break;
|
|
|
|
case EMetasoundFrontendVertexAccessType::Unset:
|
|
default:
|
|
{
|
|
checkNoEntry();
|
|
}
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
FMetasoundFrontendClass Class;
|
|
if (FindRegistryClass(Class))
|
|
{
|
|
if(!FindDependency(Class.Metadata))
|
|
{
|
|
AddNativeDependency(Class.Metadata);
|
|
}
|
|
|
|
auto FinalizeNode = [&InClassInput](FMetasoundFrontendNode& InOutNode, const Metasound::Frontend::FNodeRegistryKey&)
|
|
{
|
|
DocumentBuilderPrivate::FinalizeGraphVertexNode(InOutNode, InClassInput);
|
|
};
|
|
if (FMetasoundFrontendNode* NewNode = AddNodeInternal(Class.Metadata, FinalizeNode, InClassInput.NodeID))
|
|
{
|
|
|
|
FMetasoundFrontendClassInput& NewInput = RootGraph.Interface.Inputs.Add_GetRef(InClassInput);
|
|
if (!NewInput.VertexID.IsValid())
|
|
{
|
|
NewInput.VertexID = FGuid::NewGuid();
|
|
}
|
|
return NewNode;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const FMetasoundFrontendNode* FMetaSoundFrontendDocumentBuilder::AddGraphOutput(const FMetasoundFrontendClassOutput& InClassOutput)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
checkf(InClassOutput.NodeID.IsValid(), TEXT("Unassigned NodeID when adding graph output"));
|
|
checkf(InClassOutput.VertexID.IsValid(), TEXT("Unassigned VertexID when adding graph output"));
|
|
|
|
FMetasoundFrontendGraphClass& RootGraph = GetDocument().RootGraph;
|
|
|
|
if (InClassOutput.TypeName.IsNone())
|
|
{
|
|
UE_LOG(LogMetaSound, Error, TEXT("TypeName unset when attempting to add class output '%s'"), *InClassOutput.Name.ToString());
|
|
return nullptr;
|
|
}
|
|
else if (const FMetasoundFrontendNode* Node = DocumentCache->GetNodeCache().FindInputNode(InClassOutput.Name))
|
|
{
|
|
UE_LOG(LogMetaSound, Error, TEXT("Attempting to add MetaSound graph output '%s' when output with name already exists"), *InClassOutput.Name.ToString());
|
|
return Node;
|
|
}
|
|
|
|
auto FindRegistryClass = [&InClassOutput](FMetasoundFrontendClass& OutClass) -> bool
|
|
{
|
|
switch (InClassOutput.AccessType)
|
|
{
|
|
case EMetasoundFrontendVertexAccessType::Value:
|
|
{
|
|
return IDataTypeRegistry::Get().GetFrontendConstructorOutputClass(InClassOutput.TypeName, OutClass);
|
|
}
|
|
break;
|
|
|
|
case EMetasoundFrontendVertexAccessType::Reference:
|
|
{
|
|
return IDataTypeRegistry::Get().GetFrontendOutputClass(InClassOutput.TypeName, OutClass);
|
|
}
|
|
break;
|
|
|
|
case EMetasoundFrontendVertexAccessType::Unset:
|
|
default:
|
|
{
|
|
checkNoEntry();
|
|
}
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
FMetasoundFrontendClass Class;
|
|
if (FindRegistryClass(Class))
|
|
{
|
|
if (!FindDependency(Class.Metadata))
|
|
{
|
|
AddNativeDependency(Class.Metadata);
|
|
}
|
|
|
|
auto FinalizeNode = [&InClassOutput](FMetasoundFrontendNode& InOutNode, const Metasound::Frontend::FNodeRegistryKey&)
|
|
{
|
|
DocumentBuilderPrivate::FinalizeGraphVertexNode(InOutNode, InClassOutput);
|
|
};
|
|
if (FMetasoundFrontendNode* NewNode = AddNodeInternal(Class.Metadata, FinalizeNode, InClassOutput.NodeID))
|
|
{
|
|
FMetasoundFrontendClassOutput& NewOutput = RootGraph.Interface.Outputs.Add_GetRef(InClassOutput);
|
|
if (!NewOutput.VertexID.IsValid())
|
|
{
|
|
NewOutput.VertexID = FGuid::NewGuid();
|
|
}
|
|
return NewNode;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::AddInterface(FName InterfaceName)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendInterface Interface;
|
|
if (ISearchEngine::Get().FindInterfaceWithHighestVersion(InterfaceName, Interface))
|
|
{
|
|
if (GetDocument().Interfaces.Contains(Interface.Version))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const FModifyRootGraphInterfaces ModifyInterfaces({ }, { Interface.Version });
|
|
if (ModifyInterfaces.Transform(GetDocument()))
|
|
{
|
|
// TODO: Add ability to update local builder caches dynamically
|
|
// in ModifyRootGraphInterfaces transform once it moves away
|
|
// from using controllers internally. Then cache updates can
|
|
// be more isolated and not use this full rebuild call.
|
|
ReloadCache();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const FMetasoundFrontendNode* FMetaSoundFrontendDocumentBuilder::AddGraphNode(const FMetasoundFrontendGraphClass& InGraphClass, FGuid InNodeID)
|
|
{
|
|
auto FinalizeNode = [](const FMetasoundFrontendNode& Node, const Metasound::Frontend::FNodeRegistryKey& ClassKey)
|
|
{
|
|
#if WITH_EDITOR
|
|
using namespace Metasound::Frontend;
|
|
|
|
// Cache the asset name on the node if it node is reference to asset-defined graph.
|
|
if (IMetaSoundAssetManager* AssetManager = IMetaSoundAssetManager::Get())
|
|
{
|
|
if (const FSoftObjectPath* Path = AssetManager->FindObjectPathFromKey(ClassKey))
|
|
{
|
|
return FName(*Path->GetAssetName());
|
|
}
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
return Node.Name;
|
|
};
|
|
|
|
|
|
// Dependency is considered "External" when looked up or added on another graph
|
|
FMetasoundFrontendClassMetadata NewClassMetadata = InGraphClass.Metadata;
|
|
NewClassMetadata.SetType(EMetasoundFrontendClassType::External);
|
|
|
|
if (!FindDependency(NewClassMetadata))
|
|
{
|
|
AddGraphDependency(InGraphClass);
|
|
}
|
|
|
|
return AddNodeInternal(NewClassMetadata, FinalizeNode, InNodeID);
|
|
}
|
|
|
|
FMetasoundFrontendNode* FMetaSoundFrontendDocumentBuilder::AddNodeInternal(const FMetasoundFrontendClassMetadata& InClassMetadata, FFinalizeNodeFunctionRef FinalizeNode, FGuid InNodeID)
|
|
{
|
|
METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(FMetaSoundFrontendDocumentBuilder::AddNodeInternal);
|
|
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendDocument& Document = GetDocument();
|
|
const FNodeRegistryKey ClassKey = NodeRegistryKey::CreateKey(InClassMetadata);
|
|
if (const FMetasoundFrontendClass* Dependency = DocumentCache->FindDependency(ClassKey))
|
|
{
|
|
FMetasoundFrontendGraph& Graph = Document.RootGraph.Graph;
|
|
TArray<FMetasoundFrontendNode>& Nodes = Graph.Nodes;
|
|
FMetasoundFrontendNode& Node = Nodes.Emplace_GetRef(*Dependency);
|
|
Node.UpdateID(InNodeID);
|
|
FinalizeNode(Node, ClassKey);
|
|
|
|
const int32 NewIndex = Nodes.Num() - 1;
|
|
IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
NodeCache.OnNodeAdded(NewIndex);
|
|
return &Node;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::CanAddEdge(const FMetasoundFrontendEdge& InEdge) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
const FMetasoundFrontendDocument& Document = GetDocument();
|
|
const IDocumentGraphEdgeCache& EdgeCache = DocumentCache->GetEdgeCache();
|
|
|
|
if (!EdgeCache.IsNodeInputConnected(InEdge.ToNodeID, InEdge.ToVertexID))
|
|
{
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
const FMetasoundFrontendGraph& Graph = Document.RootGraph.Graph;
|
|
const FMetasoundFrontendVertex* OutputVertex = NodeCache.FindOutputVertex(InEdge.FromNodeID, InEdge.FromVertexID);
|
|
const FMetasoundFrontendVertex* InputVertex = NodeCache.FindInputVertex(InEdge.ToNodeID, InEdge.ToVertexID);
|
|
if (OutputVertex && InputVertex)
|
|
{
|
|
if (OutputVertex->TypeName == InputVertex->TypeName)
|
|
{
|
|
const FMetasoundFrontendClassOutput* ClassOutput = FindNodeOutputClassOutput(InEdge.FromNodeID, InEdge.FromVertexID);
|
|
const FMetasoundFrontendClassInput* ClassInput = FindNodeInputClassInput(InEdge.ToNodeID, InEdge.ToVertexID);
|
|
if (ClassInput && ClassOutput)
|
|
{
|
|
return FMetasoundFrontendClassVertex::CanConnectVertexAccessTypes(ClassOutput->AccessType, ClassInput->AccessType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::ContainsEdge(const FMetasoundFrontendEdge& InEdge) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
IDocumentGraphEdgeCache& EdgeCache = DocumentCache->GetEdgeCache();
|
|
return EdgeCache.ContainsEdge(InEdge);
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::ContainsNode(const FGuid& InNodeID) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
return NodeCache.ContainsNode(InNodeID);
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::ConvertFromPreset()
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendDocument& Document = GetDocument();
|
|
FMetasoundFrontendGraphClass& RootGraphClass = Document.RootGraph;
|
|
FMetasoundFrontendGraphClassPresetOptions& PresetOptions = RootGraphClass.PresetOptions;
|
|
PresetOptions.bIsPreset = false;
|
|
|
|
#if WITH_EDITOR
|
|
FMetasoundFrontendGraphStyle Style = Document.RootGraph.Graph.Style;
|
|
Style.bIsGraphEditable = true;
|
|
#endif // WITH_EDITOR
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::FindDeclaredInterfaces(TArray<const Metasound::Frontend::IInterfaceRegistryEntry*>& OutInterfaces) const
|
|
{
|
|
return FindDeclaredInterfaces(GetDocument(), OutInterfaces);
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::FindDeclaredInterfaces(const FMetasoundFrontendDocument& InDocument, TArray<const Metasound::Frontend::IInterfaceRegistryEntry*>& OutInterfaces)
|
|
{
|
|
using namespace Metasound;
|
|
using namespace Metasound::Frontend;
|
|
|
|
bool bInterfacesFound = true;
|
|
|
|
Algo::Transform(InDocument.Interfaces, OutInterfaces, [&bInterfacesFound](const FMetasoundFrontendVersion& Version)
|
|
{
|
|
const FInterfaceRegistryKey InterfaceKey = GetInterfaceRegistryKey(Version);
|
|
const IInterfaceRegistryEntry* RegistryEntry = IInterfaceRegistry::Get().FindInterfaceRegistryEntry(InterfaceKey);
|
|
if (!RegistryEntry)
|
|
{
|
|
bInterfacesFound = false;
|
|
UE_LOG(LogMetaSound, Warning, TEXT("No registered interface matching interface version on document [InterfaceVersion:%s]"), *Version.ToString());
|
|
}
|
|
|
|
return RegistryEntry;
|
|
});
|
|
|
|
return bInterfacesFound;
|
|
}
|
|
|
|
const FMetasoundFrontendClass* FMetaSoundFrontendDocumentBuilder::FindDependency(const FGuid& InClassID) const
|
|
{
|
|
return DocumentCache->FindDependency(InClassID);
|
|
}
|
|
|
|
const FMetasoundFrontendClass* FMetaSoundFrontendDocumentBuilder::FindDependency(const FMetasoundFrontendClassMetadata& InMetadata) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
checkf(InMetadata.GetType() != EMetasoundFrontendClassType::Graph,
|
|
TEXT("Dependencies are never listed as 'Graph' types. Graphs are considered 'External' from the perspective of the parent document to allow for nativization."));
|
|
const FNodeRegistryKey RegistryKey = NodeRegistryKey::CreateKey(InMetadata);
|
|
return DocumentCache->FindDependency(RegistryKey);
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::FindInterfaceInputNodes(FName InterfaceName, TArray<const FMetasoundFrontendNode*>& OutInputs) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
OutInputs.Reset();
|
|
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
|
|
FMetasoundFrontendInterface Interface;
|
|
if (ISearchEngine::Get().FindInterfaceWithHighestVersion(InterfaceName, Interface))
|
|
{
|
|
if (GetDocument().Interfaces.Contains(Interface.Version))
|
|
{
|
|
TArray<const FMetasoundFrontendNode*> InterfaceInputs;
|
|
for (const FMetasoundFrontendClassInput& Input : Interface.Inputs)
|
|
{
|
|
if (const int32* NodeIndex = NodeCache.FindInputNodeIndex(Input.Name))
|
|
{
|
|
const FMetasoundFrontendNode& Node = GetDocument().RootGraph.Graph.Nodes[*NodeIndex];
|
|
InterfaceInputs.Add(&Node);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
OutInputs = MoveTemp(InterfaceInputs);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::FindInterfaceOutputNodes(FName InterfaceName, TArray<const FMetasoundFrontendNode*>& OutOutputs) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
OutOutputs.Reset();
|
|
|
|
FMetasoundFrontendInterface Interface;
|
|
if (ISearchEngine::Get().FindInterfaceWithHighestVersion(InterfaceName, Interface))
|
|
{
|
|
if (GetDocument().Interfaces.Contains(Interface.Version))
|
|
{
|
|
TArray<const FMetasoundFrontendNode*> InterfaceOutputs;
|
|
for (const FMetasoundFrontendClassOutput& Output : Interface.Outputs)
|
|
{
|
|
if (const int32* NodeIndex = DocumentCache->GetNodeCache().FindOutputNodeIndex(Output.Name))
|
|
{
|
|
const FMetasoundFrontendNode& Node = GetDocument().RootGraph.Graph.Nodes[*NodeIndex];
|
|
InterfaceOutputs.Add(&Node);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
OutOutputs = MoveTemp(InterfaceOutputs);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const FMetasoundFrontendNode* FMetaSoundFrontendDocumentBuilder::FindGraphInputNode(FName InputName) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
return NodeCache.FindInputNode(InputName);
|
|
}
|
|
|
|
const FMetasoundFrontendNode* FMetaSoundFrontendDocumentBuilder::FindGraphOutputNode(FName OutputName) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
return NodeCache.FindOutputNode(OutputName);
|
|
}
|
|
|
|
const FMetasoundFrontendVertex* FMetaSoundFrontendDocumentBuilder::FindNodeInput(const FGuid& InNodeID, const FGuid& InVertexID) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
return NodeCache.FindInputVertex(InNodeID, InVertexID);
|
|
}
|
|
|
|
const FMetasoundFrontendVertex* FMetaSoundFrontendDocumentBuilder::FindNodeInput(const FGuid& InNodeID, FName InVertexName) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
return NodeCache.FindInputVertex(InNodeID, InVertexName);
|
|
}
|
|
|
|
const FMetasoundFrontendVertex* FMetaSoundFrontendDocumentBuilder::FindNodeOutput(const FGuid& InNodeID, const FGuid& InVertexID) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
return NodeCache.FindOutputVertex(InNodeID, InVertexID);
|
|
}
|
|
|
|
const FMetasoundFrontendVertex* FMetaSoundFrontendDocumentBuilder::FindNodeOutput(const FGuid& InNodeID, FName InVertexName) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
return NodeCache.FindOutputVertex(InNodeID, InVertexName);
|
|
}
|
|
|
|
const FMetasoundFrontendClassInput* FMetaSoundFrontendDocumentBuilder::FindNodeInputClassInput(const FGuid& InNodeID, const FGuid& InVertexID) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
const FMetasoundFrontendDocument& Document = GetDocument();
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
|
|
if (const FMetasoundFrontendNode* Node = NodeCache.FindNode(InNodeID))
|
|
{
|
|
if (const FMetasoundFrontendClass* Class = DocumentCache->FindDependency(Node->ClassID))
|
|
{
|
|
auto InputMatchesVertex = [&InVertexID](const FMetasoundFrontendClassVertex& ClassVertex) { return ClassVertex.VertexID == InVertexID; };
|
|
return Class->Interface.Inputs.FindByPredicate(InputMatchesVertex);
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const FMetasoundFrontendClassOutput* FMetaSoundFrontendDocumentBuilder::FindNodeOutputClassOutput(const FGuid& InNodeID, const FGuid& InVertexID) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
const FMetasoundFrontendDocument& Document = GetDocument();
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
|
|
if (const FMetasoundFrontendNode* Node = NodeCache.FindNode(InNodeID))
|
|
{
|
|
if (const FMetasoundFrontendClass* Class = DocumentCache->FindDependency(Node->ClassID))
|
|
{
|
|
auto OutputMatchesVertex = [&InVertexID](const FMetasoundFrontendClassVertex& ClassVertex) { return ClassVertex.VertexID == InVertexID; };
|
|
return Class->Interface.Outputs.FindByPredicate(OutputMatchesVertex);
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const FMetasoundFrontendNode* FMetaSoundFrontendDocumentBuilder::FindNode(const FGuid& InNodeID) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
return NodeCache.FindNode(InNodeID);
|
|
}
|
|
|
|
TArray<const FMetasoundFrontendVertex*> FMetaSoundFrontendDocumentBuilder::FindNodeInputs(const FGuid& InNodeID, FName TypeName) const
|
|
{
|
|
return DocumentCache->GetNodeCache().FindNodeInputs(InNodeID, TypeName);
|
|
}
|
|
|
|
TArray<const FMetasoundFrontendVertex*> FMetaSoundFrontendDocumentBuilder::FindNodeOutputs(const FGuid& InNodeID, FName TypeName) const
|
|
{
|
|
return DocumentCache->GetNodeCache().FindNodeOutputs(InNodeID, TypeName);
|
|
}
|
|
|
|
const TScriptInterface<IMetaSoundDocumentInterface> FMetaSoundFrontendDocumentBuilder::FindOrLoadNodeClassDocument(const FGuid& InNodeID) const
|
|
{
|
|
using namespace Metasound;
|
|
using namespace Metasound::Frontend;
|
|
|
|
const FMetasoundFrontendDocument& Document = GetDocument();
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
if (const FMetasoundFrontendNode* Node = NodeCache.FindNode(InNodeID))
|
|
{
|
|
if (const FMetasoundFrontendClass* NodeClass = DocumentCache->FindDependency(Node->ClassID))
|
|
{
|
|
const FNodeRegistryKey NodeClassRegistryKey = NodeRegistryKey::CreateKey(NodeClass->Metadata);
|
|
if (FMetasoundAssetBase* Asset = IMetaSoundAssetManager::GetChecked().TryLoadAssetFromKey(NodeClassRegistryKey))
|
|
{
|
|
UObject* Object = Asset->GetOwningAsset();
|
|
check(Object);
|
|
return Object;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FMetasoundFrontendDocument& FMetaSoundFrontendDocumentBuilder::GetDocument()
|
|
{
|
|
IMetaSoundDocumentInterface* Interface = DocumentInterface.GetInterface();
|
|
return Interface->GetDocument();
|
|
}
|
|
|
|
const FMetasoundFrontendDocument& FMetaSoundFrontendDocumentBuilder::GetDocument() const
|
|
{
|
|
return DocumentInterface->GetDocument();
|
|
}
|
|
|
|
void FMetaSoundFrontendDocumentBuilder::InitGraphClassMetadata(FMetasoundFrontendClassMetadata& InOutMetadata, bool bResetVersion)
|
|
{
|
|
InOutMetadata.SetClassName(FMetasoundFrontendClassName(FName(), *FGuid::NewGuid().ToString(), FName()));
|
|
|
|
if (bResetVersion)
|
|
{
|
|
InOutMetadata.SetVersion({ 1, 0 });
|
|
}
|
|
|
|
InOutMetadata.SetType(EMetasoundFrontendClassType::Graph);
|
|
}
|
|
|
|
void FMetaSoundFrontendDocumentBuilder::InitDocument(FName UClassName)
|
|
{
|
|
METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(FMetaSoundFrontendDocumentBuilder::InitDocument);
|
|
|
|
using namespace Metasound::Frontend;
|
|
|
|
// 1. Set default class Metadata
|
|
{
|
|
constexpr bool bResetVersion = true;
|
|
FMetasoundFrontendClassMetadata& ClassMetadata = GetDocument().RootGraph.Metadata;
|
|
InitGraphClassMetadata(ClassMetadata, bResetVersion);
|
|
}
|
|
|
|
// 2. Set default doc version Metadata
|
|
{
|
|
FMetasoundFrontendDocumentMetadata& DocMetadata = GetDocument().Metadata;
|
|
DocMetadata.Version.Number = FMetasoundFrontendDocument::GetMaxVersion();
|
|
}
|
|
|
|
// 3. Add default interfaces for given UClass
|
|
{
|
|
TArray<FMetasoundFrontendInterface> InitInterfaces = ISearchEngine::Get().FindUClassDefaultInterfaces(UClassName);
|
|
FModifyRootGraphInterfaces ModifyRootGraphInterfaces({ }, InitInterfaces);
|
|
ModifyRootGraphInterfaces.Transform(GetDocument());
|
|
}
|
|
|
|
// 5. Reload the whole cache as transforms above mutate cached collections. Can be safely removed once transforms above
|
|
// are re-implemented in builder and support cache to enable marginally better performance on document initialization.
|
|
ReloadCache();
|
|
}
|
|
|
|
void FMetaSoundFrontendDocumentBuilder::InitNodeLocations()
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
using namespace Metasound;
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendGraph& Graph = GetDocument().RootGraph.Graph;
|
|
|
|
FVector2D InputNodeLocation = FVector2D::ZeroVector;
|
|
FVector2D ExternalNodeLocation = InputNodeLocation + DisplayStyle::NodeLayout::DefaultOffsetX;
|
|
FVector2D OutputNodeLocation = ExternalNodeLocation + DisplayStyle::NodeLayout::DefaultOffsetX;
|
|
|
|
TArray<FMetasoundFrontendNode>& Nodes = Graph.Nodes;
|
|
for (FMetasoundFrontendNode& Node : Nodes)
|
|
{
|
|
if (const int32* ClassIndex = DocumentCache->FindDependencyIndex(Node.ClassID))
|
|
{
|
|
FMetasoundFrontendClass& Class = GetDocument().Dependencies[*ClassIndex];
|
|
|
|
const EMetasoundFrontendClassType NodeType = Class.Metadata.GetType();
|
|
FVector2D NewLocation;
|
|
if (NodeType == EMetasoundFrontendClassType::Input)
|
|
{
|
|
NewLocation = InputNodeLocation;
|
|
InputNodeLocation += DisplayStyle::NodeLayout::DefaultOffsetY;
|
|
}
|
|
else if (NodeType == EMetasoundFrontendClassType::Output)
|
|
{
|
|
NewLocation = OutputNodeLocation;
|
|
OutputNodeLocation += DisplayStyle::NodeLayout::DefaultOffsetY;
|
|
}
|
|
else
|
|
{
|
|
NewLocation = ExternalNodeLocation;
|
|
ExternalNodeLocation += DisplayStyle::NodeLayout::DefaultOffsetY;
|
|
}
|
|
|
|
// TODO: Find consistent location for controlling node locations.
|
|
// Currently it is split between MetasoundEditor and MetasoundFrontend modules.
|
|
FMetasoundFrontendNodeStyle& Style = Node.Style;
|
|
Style.Display.Locations = { { FGuid::NewGuid(), NewLocation } };
|
|
}
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::IsNodeInputConnected(const FGuid& InNodeID, const FGuid& InVertexID) const
|
|
{
|
|
return DocumentCache->GetEdgeCache().IsNodeInputConnected(InNodeID, InVertexID);
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::IsNodeOutputConnected(const FGuid& InNodeID, const FGuid& InVertexID) const
|
|
{
|
|
return DocumentCache->GetEdgeCache().IsNodeOutputConnected(InNodeID, InVertexID);
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::IsInterfaceDeclared(FName InInterfaceName) const
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendInterface Interface;
|
|
if (ISearchEngine::Get().FindInterfaceWithHighestVersion(InInterfaceName, Interface))
|
|
{
|
|
return IsInterfaceDeclared(Interface.Version);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::IsInterfaceDeclared(const FMetasoundFrontendVersion& InInterfaceVersion) const
|
|
{
|
|
return GetDocument().Interfaces.Contains(InInterfaceVersion);
|
|
}
|
|
|
|
void FMetaSoundFrontendDocumentBuilder::ReloadCache()
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
DocumentCache = MakeUnique<FDocumentCache>(GetDocument());
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::RemoveDependency(EMetasoundFrontendClassType ClassType, const FMetasoundFrontendClassName& InClassName, const FMetasoundFrontendVersionNumber& InClassVersionNumber)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendDocument& Document = GetDocument();
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
|
|
TArray<FMetasoundFrontendClass>& Dependencies = Document.Dependencies;
|
|
const FNodeRegistryKey ClassKey = NodeRegistryKey::CreateKey(ClassType, InClassName.GetFullName().ToString(), InClassVersionNumber.Major, InClassVersionNumber.Minor);
|
|
if (const int32* IndexPtr = DocumentCache->FindDependencyIndex(ClassKey))
|
|
{
|
|
const int32 Index = *IndexPtr;
|
|
|
|
TArray<const FMetasoundFrontendNode*> Nodes = NodeCache.FindNodesOfClassID(Dependencies[Index].ID);
|
|
for (const FMetasoundFrontendNode* Node : Nodes)
|
|
{
|
|
if (!RemoveNode(Node->GetID()))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const int32 LastIndex = Dependencies.Num() - 1;
|
|
DocumentCache->OnRemovingDependency(Index);
|
|
if (Index != LastIndex)
|
|
{
|
|
DocumentCache->OnRemovingDependency(LastIndex);
|
|
}
|
|
Dependencies.RemoveAtSwap(Index, 1, false);
|
|
if (Index != LastIndex)
|
|
{
|
|
DocumentCache->OnDependencyAdded(Index);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::RemoveEdge(const FMetasoundFrontendEdge& EdgeToRemove)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendDocument& Document = GetDocument();
|
|
FMetasoundFrontendGraph& Graph = Document.RootGraph.Graph;
|
|
TArray<FMetasoundFrontendEdge>& Edges = Graph.Edges;
|
|
IDocumentGraphEdgeCache& EdgeCache = DocumentCache->GetEdgeCache();
|
|
if (const int32* IndexPtr = EdgeCache.FindEdgeIndexToNodeInput(EdgeToRemove.ToNodeID, EdgeToRemove.ToVertexID))
|
|
{
|
|
const int32 Index = *IndexPtr;
|
|
const int32 LastIndex = Edges.Num() - 1;
|
|
EdgeCache.OnRemovingEdge(Index);
|
|
if (Index != LastIndex)
|
|
{
|
|
EdgeCache.OnRemovingEdge(LastIndex);
|
|
}
|
|
Edges.RemoveAtSwap(Index, 1, false);
|
|
if (Index != LastIndex)
|
|
{
|
|
EdgeCache.OnEdgeAdded(Index);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::RemoveEdgesByNamedConnections(const TSet<Metasound::Frontend::FNamedEdge>& ConnectionsToRemove, TArray<FMetasoundFrontendEdge>* OutRemovedEdges)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendDocument& Document = GetDocument();
|
|
const IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
|
|
if (OutRemovedEdges)
|
|
{
|
|
OutRemovedEdges->Reset();
|
|
}
|
|
|
|
bool bSuccess = true;
|
|
|
|
TArray<FMetasoundFrontendEdge> EdgesToRemove;
|
|
for (const FNamedEdge& Connection : ConnectionsToRemove)
|
|
{
|
|
const FMetasoundFrontendGraph& Graph = Document.RootGraph.Graph;
|
|
const FMetasoundFrontendVertex* OutputVertex = NodeCache.FindOutputVertex(Connection.OutputNodeID, Connection.OutputName);
|
|
const FMetasoundFrontendVertex* InputVertex = NodeCache.FindInputVertex(Connection.InputNodeID, Connection.InputName);
|
|
|
|
if (OutputVertex && InputVertex)
|
|
{
|
|
FMetasoundFrontendEdge NewEdge = { Connection.OutputNodeID, OutputVertex->VertexID, Connection.InputNodeID, InputVertex->VertexID };
|
|
if (ContainsEdge(NewEdge))
|
|
{
|
|
EdgesToRemove.Add(MoveTemp(NewEdge));
|
|
}
|
|
else
|
|
{
|
|
bSuccess = false;
|
|
UE_LOG(LogMetaSound, Warning, TEXT("Failed to remove connections between MetaSound node output '%s' and input '%s': No connection found."), *Connection.OutputName.ToString(), *Connection.InputName.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
for (FMetasoundFrontendEdge& EdgeToRemove : EdgesToRemove)
|
|
{
|
|
const bool bRemovedEdge = RemoveEdgeToNodeInput(EdgeToRemove.ToNodeID, EdgeToRemove.ToVertexID);
|
|
if (ensureAlwaysMsgf(bRemovedEdge, TEXT("Failed to remove MetaSound graph edge via DocumentBuilder when prior step validated edge remove was valid")))
|
|
{
|
|
if (OutRemovedEdges)
|
|
{
|
|
OutRemovedEdges->Add(EdgeToRemove);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bSuccess = false;
|
|
}
|
|
}
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::RemoveEdgesByNodeClassInterfaceBindings(const FGuid& InFromNodeID, const FGuid& InToNodeID)
|
|
{
|
|
using namespace Metasound;
|
|
using namespace Metasound::Frontend;
|
|
|
|
auto FindNodeClassDocAndInterfaces = [this](const FGuid& InNodeID) -> const FMetasoundFrontendDocument*
|
|
{
|
|
const TScriptInterface<IMetaSoundDocumentInterface> NodeDocumentUInterface = FindOrLoadNodeClassDocument(InNodeID);
|
|
if (NodeDocumentUInterface)
|
|
{
|
|
return &NodeDocumentUInterface->GetDocument();
|
|
}
|
|
|
|
return nullptr;
|
|
};
|
|
|
|
const FMetasoundFrontendDocument* OutputNodeDocument = FindNodeClassDocAndInterfaces(InFromNodeID);
|
|
const FMetasoundFrontendDocument* InputNodeDocument = FindNodeClassDocAndInterfaces(InToNodeID);
|
|
|
|
if (!InputNodeDocument || !OutputNodeDocument)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const TSet<FMetasoundFrontendVersion>& OutputInterfaceVersions = OutputNodeDocument->Interfaces;
|
|
|
|
TArray<const IInterfaceRegistryEntry*> InputNodeInterfaces;
|
|
FindDeclaredInterfaces(*InputNodeDocument, InputNodeInterfaces);
|
|
|
|
bool bSuccess = true;
|
|
|
|
TSet<FNamedEdge> ConnectionsToRemove;
|
|
TSet<FName> InputsToDisconnect;
|
|
for (const IInterfaceRegistryEntry* InputInterfaceEntry : InputNodeInterfaces)
|
|
{
|
|
const FMetasoundFrontendInterface& InputInterface = InputInterfaceEntry->GetInterface();
|
|
const TArray<FMetasoundFrontendInterfaceBinding>& OutputBindings = InputInterfaceEntry->GetOutputBindings();
|
|
|
|
// Bindings are sorted in registry with earlier entries being higher priority to apply connections,
|
|
// so earlier listed connections are selected over potential collisions with later entries.
|
|
for (const FMetasoundFrontendInterfaceBinding& OutputBinding : OutputBindings)
|
|
{
|
|
if (OutputInterfaceVersions.Contains(OutputBinding.Version))
|
|
{
|
|
for (const FMetasoundFrontendInterfaceBindingConnection& Connection : OutputBinding.Connections)
|
|
{
|
|
if (!InputsToDisconnect.Contains(Connection.InputName))
|
|
{
|
|
InputsToDisconnect.Add(Connection.InputName);
|
|
ConnectionsToRemove.Add(FNamedEdge{ InFromNodeID, Connection.OutputName, InToNodeID, Connection.InputName });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return RemoveEdgesByNamedConnections(ConnectionsToRemove);
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::RemoveEdgesFromNodeOutput(const FGuid& InNodeID, const FGuid& InVertexID)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendDocument& Document = GetDocument();
|
|
FMetasoundFrontendGraph& Graph = Document.RootGraph.Graph;
|
|
IDocumentGraphEdgeCache& EdgeCache = DocumentCache->GetEdgeCache();
|
|
|
|
if (const TArray<int32>* Indices = EdgeCache.FindEdgeIndicesFromNodeOutput(InNodeID, InVertexID))
|
|
{
|
|
TArray<int32> IndicesCopy = *Indices; // Copy off indices as the array may be modified when notifying the cache in the loop below
|
|
for (int32 Index : IndicesCopy)
|
|
{
|
|
const int32 LastIndex = Graph.Edges.Num() - 1;
|
|
EdgeCache.OnRemovingEdge(Index);
|
|
if (Index != LastIndex)
|
|
{
|
|
EdgeCache.OnRemovingEdge(LastIndex);
|
|
}
|
|
Graph.Edges.RemoveAtSwap(Index, 1, false);
|
|
if (Index != LastIndex)
|
|
{
|
|
EdgeCache.OnEdgeAdded(Index);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::RemoveEdgeToNodeInput(const FGuid& InNodeID, const FGuid& InVertexID)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendDocument& Document = GetDocument();
|
|
IDocumentGraphEdgeCache& EdgeCache = DocumentCache->GetEdgeCache();
|
|
FMetasoundFrontendGraph& Graph = Document.RootGraph.Graph;
|
|
if (const int32* IndexPtr = EdgeCache.FindEdgeIndexToNodeInput(InNodeID, InVertexID))
|
|
{
|
|
const int32 Index = *IndexPtr; // Copy off indices as the pointer may be modified when notifying the cache below
|
|
const int32 LastIndex = Graph.Edges.Num() - 1;
|
|
EdgeCache.OnRemovingEdge(Index);
|
|
if (Index != LastIndex)
|
|
{
|
|
EdgeCache.OnRemovingEdge(LastIndex);
|
|
}
|
|
Graph.Edges.RemoveAtSwap(Index, 1, false);
|
|
if (Index != LastIndex)
|
|
{
|
|
EdgeCache.OnEdgeAdded(Index);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::RemoveInterface(FName InterfaceName)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendInterface Interface;
|
|
if (ISearchEngine::Get().FindInterfaceWithHighestVersion(InterfaceName, Interface))
|
|
{
|
|
if (!GetDocument().Interfaces.Contains(Interface.Version))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const FModifyRootGraphInterfaces ModifyInterfaces({ { Interface.Version }, { } });
|
|
if (ModifyInterfaces.Transform(GetDocument()))
|
|
{
|
|
// TODO: Add ability to update local builder caches dynamically
|
|
// in ModifyRootGraphInterfaces transform once it moves away
|
|
// from using controllers internally. Then cache updates can
|
|
// be more isolated and not use this full rebuild call.
|
|
ReloadCache();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::RemoveNode(const FGuid& InNodeID)
|
|
{
|
|
METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(FMetaSoundFrontendDocumentBuilder::RemoveNode);
|
|
|
|
using namespace Metasound::Frontend;
|
|
|
|
FMetasoundFrontendDocument& Document = GetDocument();
|
|
IDocumentGraphNodeCache& NodeCache = DocumentCache->GetNodeCache();
|
|
|
|
FMetasoundFrontendGraph& Graph = Document.RootGraph.Graph;
|
|
TArray<FMetasoundFrontendNode>& Nodes = Graph.Nodes;
|
|
if (const int32* IndexPtr = NodeCache.FindNodeIndex(InNodeID))
|
|
{
|
|
const int32 Index = *IndexPtr; // Copy off indices as the pointer may be modified when notifying the cache below
|
|
const FMetasoundFrontendNode& Node = Nodes[Index];
|
|
|
|
for (const FMetasoundFrontendVertex& Vertex : Node.Interface.Inputs)
|
|
{
|
|
if (!RemoveEdgeToNodeInput(Node.GetID(), Vertex.VertexID))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (const FMetasoundFrontendVertex& Vertex : Node.Interface.Outputs)
|
|
{
|
|
RemoveEdgesFromNodeOutput(Node.GetID(), Vertex.VertexID);
|
|
}
|
|
|
|
NodeCache.OnRemovingNode(Index);
|
|
|
|
const int32 LastIndex = Nodes.Num() - 1;
|
|
if (Index != LastIndex)
|
|
{
|
|
NodeCache.OnRemovingNode(LastIndex);
|
|
}
|
|
Nodes.RemoveAtSwap(Index, 1, false);
|
|
if (Index != LastIndex)
|
|
{
|
|
NodeCache.OnNodeAdded(Index);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void FMetaSoundFrontendDocumentBuilder::SetAuthor(const FString& InAuthor)
|
|
{
|
|
FMetasoundFrontendClassMetadata& ClassMetadata = GetDocument().RootGraph.Metadata;
|
|
ClassMetadata.SetAuthor(InAuthor);
|
|
}
|
|
#endif // WITH_EDITOR
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::SetNodeInputDefault(const FGuid& InNodeID, const FGuid& InVertexID, const FMetasoundFrontendLiteral& InLiteral)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
if (const int32* Index = DocumentCache->GetNodeCache().FindNodeIndex(InNodeID))
|
|
{
|
|
FMetasoundFrontendGraph& Graph = GetDocument().RootGraph.Graph;
|
|
FMetasoundFrontendNode& Node = Graph.Nodes[*Index];
|
|
TArray<FMetasoundFrontendVertex>& Inputs = Node.Interface.Inputs;
|
|
|
|
auto IsVertex = [&InVertexID](const FMetasoundFrontendVertex& Vertex) { return Vertex.VertexID == InVertexID; };
|
|
if (Inputs.ContainsByPredicate(IsVertex))
|
|
{
|
|
FMetasoundFrontendVertexLiteral NewVertexLiteral;
|
|
NewVertexLiteral.VertexID = InVertexID;
|
|
NewVertexLiteral.Value = InLiteral;
|
|
|
|
auto IsLiteral = [&InVertexID](const FMetasoundFrontendVertexLiteral& Literal) { return Literal.VertexID == InVertexID; };
|
|
if (FMetasoundFrontendVertexLiteral* InputLiteral = Node.InputLiterals.FindByPredicate(IsLiteral))
|
|
{
|
|
*InputLiteral = MoveTemp(NewVertexLiteral);
|
|
}
|
|
else
|
|
{
|
|
Node.InputLiterals.Add(MoveTemp(NewVertexLiteral));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FMetaSoundFrontendDocumentBuilder::ModifyInterfaces(TArray<FMetasoundFrontendVersion> OutputFormatsToRemove, TArray<FMetasoundFrontendVersion> OutputFormatsToAdd)
|
|
{
|
|
using namespace Metasound::Frontend;
|
|
|
|
// Integrating this transform into the builder will allow for more selective updates and not having to rebuild the cache from scratch after performing the transform.
|
|
FModifyRootGraphInterfaces ModifyRootGraph(OutputFormatsToRemove, OutputFormatsToAdd);
|
|
if (ModifyRootGraph.Transform(GetDocument()))
|
|
{
|
|
ReloadCache();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|