Files
UnrealEngineUWP/Engine/Plugins/Runtime/Metasound/Source/MetasoundFrontend/Private/MetasoundFrontendStandardController.cpp
phil popp 33b7b2a911 MetaSound Frontend Speedups
- Cached array indices for TAccessPtr point to an object in an array
- Reduced number of call sites to TAccessPtr::Get()
- Avoid creating registry keys when possible.
#jira UE-116999
#rb Rob.Gay


#ROBOMERGE-SOURCE: CL 16830765
#ROBOMERGE-BOT: (v836-16769935)

[CL 16830791 by phil popp in ue5-main branch]
2021-07-12 16:12:36 -04:00

3899 lines
121 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MetasoundFrontendStandardController.h"
#include "Algo/ForEach.h"
#include "Algo/NoneOf.h"
#include "CoreMinimal.h"
#include "HAL/FileManager.h"
#include "MetasoundAccessPtr.h"
#include "MetasoundFrontendArchetypeRegistry.h"
#include "MetasoundFrontendController.h"
#include "MetasoundFrontendDocument.h"
#include "MetasoundFrontendDocumentAccessPtr.h"
#include "MetasoundFrontendGraph.h"
#include "MetasoundFrontendRegistries.h"
#include "MetasoundFrontendSubgraphNodeController.h"
#include "MetasoundJsonBackend.h"
#include "MetasoundOperatorBuilder.h"
#include "StructDeserializer.h"
#include "StructSerializer.h"
#include "Serialization/MemoryReader.h"
#define LOCTEXT_NAMESPACE "MetasoundFrontendStandardController"
namespace Metasound
{
namespace Frontend
{
namespace FrontendControllerIntrinsics
{
// utility function for returning invalid values. If an invalid value type
// needs special construction, this template can be specialized.
template<typename ValueType>
ValueType GetInvalidValue()
{
ValueType InvalidValue;
return InvalidValue;
}
// Invalid value specialization for int32
template<>
int32 GetInvalidValue<int32>() { return INDEX_NONE; }
// Invalid value specialization for EMetasoundFrontendClassType
template<>
EMetasoundFrontendClassType GetInvalidValue<EMetasoundFrontendClassType>() { return EMetasoundFrontendClassType::Invalid; }
// Invalid value specialization for FText
template<>
FText GetInvalidValue<FText>() { return FText::GetEmpty(); }
template<typename ValueType>
const ValueType& GetInvalidValueConstRef()
{
static const ValueType Value = GetInvalidValue<ValueType>();
return Value;
}
}
FDocumentAccess IDocumentAccessor::GetSharedAccess(IDocumentAccessor& InDocumentAccessor)
{
return InDocumentAccessor.ShareAccess();
}
FConstDocumentAccess IDocumentAccessor::GetSharedAccess(const IDocumentAccessor& InDocumentAccessor)
{
return InDocumentAccessor.ShareAccess();
}
//
// FBaseOutputController
//
FBaseOutputController::FBaseOutputController(const FBaseOutputController::FInitParams& InParams)
: ID(InParams.ID)
, NodeVertexPtr(InParams.NodeVertexPtr)
, ClassOutputPtr(InParams.ClassOutputPtr)
, GraphPtr(InParams.GraphPtr)
, OwningNode(InParams.OwningNode)
{
}
bool FBaseOutputController::IsValid() const
{
return OwningNode->IsValid() && (nullptr != NodeVertexPtr.Get()) && (nullptr != GraphPtr.Get());
}
FGuid FBaseOutputController::GetID() const
{
return ID;
}
const FName& FBaseOutputController::GetDataType() const
{
if (const FMetasoundFrontendVertex* Vertex = NodeVertexPtr.Get())
{
return Vertex->TypeName;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FName>();
}
const FString& FBaseOutputController::GetName() const
{
if (const FMetasoundFrontendVertex* Vertex = NodeVertexPtr.Get())
{
return Vertex->Name;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FString>();
}
FGuid FBaseOutputController::GetOwningNodeID() const
{
return OwningNode->GetID();
}
FNodeHandle FBaseOutputController::GetOwningNode()
{
return OwningNode;
}
FConstNodeHandle FBaseOutputController::GetOwningNode() const
{
return OwningNode;
}
bool FBaseOutputController::IsConnected() const
{
return (FindEdges().Num() > 0);
}
TArray<FInputHandle> FBaseOutputController::GetConnectedInputs()
{
TArray<FInputHandle> Inputs;
// Create output handle from output node.
FGraphHandle Graph = OwningNode->GetOwningGraph();
for (const FMetasoundFrontendEdge& Edge : FindEdges())
{
FNodeHandle InputNode = Graph->GetNodeWithID(Edge.ToNodeID);
FInputHandle Input = InputNode->GetInputWithID(Edge.ToVertexID);
if (Input->IsValid())
{
Inputs.Add(Input);
}
}
return Inputs;
}
TArray<FConstInputHandle> FBaseOutputController::GetConstConnectedInputs() const
{
TArray<FConstInputHandle> Inputs;
// Create output handle from output node.
FConstGraphHandle Graph = OwningNode->GetOwningGraph();
for (const FMetasoundFrontendEdge& Edge : FindEdges())
{
FConstNodeHandle InputNode = Graph->GetNodeWithID(Edge.ToNodeID);
FConstInputHandle Input = InputNode->GetInputWithID(Edge.ToVertexID);
if (Input->IsValid())
{
Inputs.Add(Input);
}
}
return Inputs;
}
bool FBaseOutputController::Disconnect()
{
bool bSuccess = true;
for (FInputHandle Input : GetConnectedInputs())
{
if (Input->IsValid())
{
bSuccess &= Disconnect(*Input);
}
}
return bSuccess;
}
FConnectability FBaseOutputController::CanConnectTo(const IInputController& InController) const
{
return InController.CanConnectTo(*this);
}
bool FBaseOutputController::Connect(IInputController& InController)
{
return InController.Connect(*this);
}
bool FBaseOutputController::ConnectWithConverterNode(IInputController& InController, const FConverterNodeInfo& InNodeClassName)
{
return InController.ConnectWithConverterNode(*this, InNodeClassName);
}
bool FBaseOutputController::Disconnect(IInputController& InController)
{
return InController.Disconnect(*this);
}
TArray<FMetasoundFrontendEdge> FBaseOutputController::FindEdges() const
{
if (const FMetasoundFrontendGraph* Graph = GraphPtr.Get())
{
const FGuid NodeID = GetOwningNodeID();
FGuid VertexID = GetID();
auto EdgeHasMatchingSource = [&](const FMetasoundFrontendEdge& Edge)
{
return (Edge.FromNodeID == NodeID) && (Edge.FromVertexID == VertexID);
};
return Graph->Edges.FilterByPredicate(EdgeHasMatchingSource);
}
return TArray<FMetasoundFrontendEdge>();
}
const FText& FBaseOutputController::GetDisplayName() const
{
if (const FMetasoundFrontendClassOutput* Output = ClassOutputPtr.Get())
{
return Output->Metadata.DisplayName;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FText& FBaseOutputController::GetTooltip() const
{
if (const FMetasoundFrontendClassOutput* Output = ClassOutputPtr.Get())
{
return Output->Metadata.Description;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FMetasoundFrontendVertexMetadata& FBaseOutputController::GetMetadata() const
{
if (const FMetasoundFrontendClassOutput* Output = ClassOutputPtr.Get())
{
return Output->Metadata;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FMetasoundFrontendVertexMetadata>();
}
FDocumentAccess FBaseOutputController::ShareAccess()
{
FDocumentAccess Access;
Access.ConstVertex = NodeVertexPtr;
Access.ConstClassOutput = ClassOutputPtr;
Access.Graph = GraphPtr;
Access.ConstGraph = GraphPtr;
return Access;
}
FConstDocumentAccess FBaseOutputController::ShareAccess() const
{
FConstDocumentAccess Access;
Access.ConstVertex = NodeVertexPtr;
Access.ConstClassOutput = ClassOutputPtr;
Access.ConstGraph = GraphPtr;
return Access;
}
//
// FInputNodeOutputController
//
FInputNodeOutputController::FInputNodeOutputController(const FInputNodeOutputController::FInitParams& InParams)
: FBaseOutputController({InParams.ID, InParams.NodeVertexPtr, InParams.ClassOutputPtr, InParams.GraphPtr, InParams.OwningNode})
, OwningGraphClassInputPtr(InParams.OwningGraphClassInputPtr)
{
}
bool FInputNodeOutputController::IsValid() const
{
return FBaseOutputController::IsValid() && (nullptr != OwningGraphClassInputPtr.Get());
}
const FText& FInputNodeOutputController::GetDisplayName() const
{
if (const FMetasoundFrontendClassInput* OwningInput = OwningGraphClassInputPtr.Get())
{
if (const FMetasoundFrontendClassOutput* ClassOutput = ClassOutputPtr.Get())
{
// If there is a valid ClassOutput, combine the names.
CachedDisplayName = FText::Format(LOCTEXT("InputNodeOutputControllerFormat", "{1} {0}"), OwningInput->Metadata.DisplayName, ClassOutput->Metadata.DisplayName);
}
else
{
// If there is no valid ClassOutput, use the owning value display name.
CachedDisplayName = OwningInput->Metadata.DisplayName;
}
}
return CachedDisplayName;
}
const FText& FInputNodeOutputController::GetTooltip() const
{
if (const FMetasoundFrontendClassInput* Input = OwningGraphClassInputPtr.Get())
{
return Input->Metadata.Description;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FMetasoundFrontendVertexMetadata& FInputNodeOutputController::GetMetadata() const
{
if (const FMetasoundFrontendClassInput* Input = OwningGraphClassInputPtr.Get())
{
return Input->Metadata;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FMetasoundFrontendVertexMetadata>();
}
FDocumentAccess FInputNodeOutputController::ShareAccess()
{
FDocumentAccess Access = FBaseOutputController::ShareAccess();
Access.ConstClassInput = OwningGraphClassInputPtr;
return Access;
}
FConstDocumentAccess FInputNodeOutputController::ShareAccess() const
{
FConstDocumentAccess Access = FBaseOutputController::ShareAccess();
Access.ConstClassInput = OwningGraphClassInputPtr;
return Access;
}
//
// FOutputNodeOutputController
//
FOutputNodeOutputController::FOutputNodeOutputController(const FOutputNodeOutputController::FInitParams& InParams)
: FBaseOutputController({InParams.ID, InParams.NodeVertexPtr, InParams.ClassOutputPtr, InParams.GraphPtr, InParams.OwningNode})
, OwningGraphClassOutputPtr(InParams.OwningGraphClassOutputPtr)
{
}
bool FOutputNodeOutputController::IsValid() const
{
return FBaseOutputController::IsValid() && (nullptr != OwningGraphClassOutputPtr.Get());
}
const FText& FOutputNodeOutputController::GetDisplayName() const
{
if (const FMetasoundFrontendClassOutput* OwningOutput = OwningGraphClassOutputPtr.Get())
{
return OwningOutput->Metadata.DisplayName;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FText& FOutputNodeOutputController::GetTooltip() const
{
if (const FMetasoundFrontendClassOutput* OwningOutput = OwningGraphClassOutputPtr.Get())
{
return OwningOutput->Metadata.Description;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FMetasoundFrontendVertexMetadata& FOutputNodeOutputController::GetMetadata() const
{
if (const FMetasoundFrontendClassOutput* OwningOutput = OwningGraphClassOutputPtr.Get())
{
return OwningOutput->Metadata;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FMetasoundFrontendVertexMetadata>();
}
FConnectability FOutputNodeOutputController::CanConnectTo(const IInputController& InController) const
{
// Cannot connect to a graph's output.
static const FConnectability Connectability = {FConnectability::EConnectable::No};
return Connectability;
}
bool FOutputNodeOutputController::Connect(IInputController& InController)
{
return false;
}
bool FOutputNodeOutputController::ConnectWithConverterNode(IInputController& InController, const FConverterNodeInfo& InNodeClassName)
{
return false;
}
//
// FBaseInputController
//
FBaseInputController::FBaseInputController(const FBaseInputController::FInitParams& InParams)
: ID(InParams.ID)
, NodeVertexPtr(InParams.NodeVertexPtr)
, ClassInputPtr(InParams.ClassInputPtr)
, GraphPtr(InParams.GraphPtr)
, OwningNode(InParams.OwningNode)
{
}
bool FBaseInputController::IsValid() const
{
return OwningNode->IsValid() && (nullptr != NodeVertexPtr.Get()) && (nullptr != GraphPtr.Get());
}
FGuid FBaseInputController::GetID() const
{
return ID;
}
const FName& FBaseInputController::GetDataType() const
{
if (const FMetasoundFrontendVertex* Vertex = NodeVertexPtr.Get())
{
return Vertex->TypeName;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FName>();
}
const FString& FBaseInputController::GetName() const
{
if (const FMetasoundFrontendVertex* Vertex = NodeVertexPtr.Get())
{
return Vertex->Name;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FString>();
}
const FText& FBaseInputController::GetDisplayName() const
{
if (const FMetasoundFrontendClassInput* ClassInput = ClassInputPtr.Get())
{
return ClassInput->Metadata.DisplayName;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FMetasoundFrontendLiteral* FBaseInputController::GetLiteral() const
{
if (const FMetasoundFrontendVertex* Vertex = NodeVertexPtr.Get())
{
return OwningNode->GetInputLiteral(Vertex->VertexID);
}
return nullptr;
}
void FBaseInputController::SetLiteral(const FMetasoundFrontendLiteral& InLiteral)
{
if (const FMetasoundFrontendVertex* Vertex = NodeVertexPtr.Get())
{
if (const FMetasoundFrontendLiteral* ClassLiteral = GetClassDefaultLiteral())
{
// Clear if equivalent to class default as fallback is the class default
if (ClassLiteral->IsEquivalent(InLiteral))
{
OwningNode->ClearInputLiteral(Vertex->VertexID);
return;
}
}
OwningNode->SetInputLiteral(FMetasoundFrontendVertexLiteral{ Vertex->VertexID, InLiteral });
}
}
const FMetasoundFrontendLiteral* FBaseInputController::GetClassDefaultLiteral() const
{
if (const FMetasoundFrontendClassInput* ClassInput = ClassInputPtr.Get())
{
return &(ClassInput->DefaultLiteral);
}
return nullptr;
}
const FText& FBaseInputController::GetTooltip() const
{
if (const FMetasoundFrontendClassInput* ClassInput = ClassInputPtr.Get())
{
return ClassInput->Metadata.Description;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FMetasoundFrontendVertexMetadata& FBaseInputController::GetMetadata() const
{
if (const FMetasoundFrontendClassInput* ClassInput = ClassInputPtr.Get())
{
return ClassInput->Metadata;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FMetasoundFrontendVertexMetadata>();
}
bool FBaseInputController::IsConnected() const
{
return (nullptr != FindEdge());
}
FGuid FBaseInputController::GetOwningNodeID() const
{
return OwningNode->GetID();
}
FNodeHandle FBaseInputController::GetOwningNode()
{
return OwningNode;
}
FConstNodeHandle FBaseInputController::GetOwningNode() const
{
return OwningNode;
}
FOutputHandle FBaseInputController::GetConnectedOutput()
{
if (const FMetasoundFrontendEdge* Edge = FindEdge())
{
// Create output handle from output node.
FGraphHandle Graph = OwningNode->GetOwningGraph();
FNodeHandle OutputNode = Graph->GetNodeWithID(Edge->FromNodeID);
return OutputNode->GetOutputWithID(Edge->FromVertexID);
}
return FInvalidOutputController::GetInvalid();
}
FConstOutputHandle FBaseInputController::GetConnectedOutput() const
{
if (const FMetasoundFrontendEdge* Edge = FindEdge())
{
// Create output handle from output node.
FConstGraphHandle Graph = OwningNode->GetOwningGraph();
FConstNodeHandle OutputNode = Graph->GetNodeWithID(Edge->FromNodeID);
return OutputNode->GetOutputWithID(Edge->FromVertexID);
}
return FInvalidOutputController::GetInvalid();
}
FConnectability FBaseInputController::CanConnectTo(const IOutputController& InController) const
{
FConnectability OutConnectability;
OutConnectability.Connectable = FConnectability::EConnectable::No;
const FName& DataType = GetDataType();
const FName& OtherDataType = InController.GetDataType();
if (!DataType.IsValid())
{
return OutConnectability;
}
if (OtherDataType == DataType)
{
// If data types are equal, connection can happen.
OutConnectability.Connectable = FConnectability::EConnectable::Yes;
return OutConnectability;
}
// If data types are not equal, check for converter nodes which could
// convert data type.
OutConnectability.PossibleConverterNodeClasses = FRegistry::Get()->GetPossibleConverterNodes(OtherDataType, DataType);
if (OutConnectability.PossibleConverterNodeClasses.Num() > 0)
{
OutConnectability.Connectable = FConnectability::EConnectable::YesWithConverterNode;
return OutConnectability;
}
return OutConnectability;
}
bool FBaseInputController::Connect(IOutputController& InController)
{
const FName& DataType = GetDataType();
const FName& OtherDataType = InController.GetDataType();
if (!DataType.IsValid())
{
return false;
}
if (FMetasoundFrontendGraph* Graph = GraphPtr.Get())
{
if (ensureAlwaysMsgf(OtherDataType == DataType, TEXT("Cannot connect incompatible types.")))
{
// Overwrite an existing connection if it exists.
FMetasoundFrontendEdge* Edge = FindEdge();
if (!Edge)
{
Edge = &Graph->Edges.AddDefaulted_GetRef();
Edge->ToNodeID = GetOwningNodeID();
Edge->ToVertexID = GetID();
}
Edge->FromNodeID = InController.GetOwningNodeID();
Edge->FromVertexID = InController.GetID();
return true;
}
}
return false;
}
bool FBaseInputController::ConnectWithConverterNode(IOutputController& InController, const FConverterNodeInfo& InConverterInfo)
{
FGraphHandle OwningGraph = OwningNode->GetOwningGraph();
// Generate the converter node.
FNodeHandle ConverterNode = OwningGraph->AddNode(InConverterInfo.NodeKey);
TArray<FInputHandle> ConverterInputs = ConverterNode->GetInputsWithVertexName(InConverterInfo.PreferredConverterInputPin);
TArray<FOutputHandle> ConverterOutputs = ConverterNode->GetOutputsWithVertexName(InConverterInfo.PreferredConverterOutputPin);
if (ConverterInputs.Num() < 1)
{
UE_LOG(LogMetaSound, Warning, TEXT("Converter node [Name: %s] does not support preferred input vertex [Vertex: %s]"), *ConverterNode->GetNodeName(), *InConverterInfo.PreferredConverterInputPin);
return false;
}
if (ConverterOutputs.Num() < 1)
{
UE_LOG(LogMetaSound, Warning, TEXT("Converter node [Name: %s] does not support preferred output vertex [Vertex: %s]"), *ConverterNode->GetNodeName(), *InConverterInfo.PreferredConverterOutputPin);
return false;
}
FInputHandle ConverterInput = ConverterInputs[0];
FOutputHandle ConverterOutput = ConverterOutputs[0];
// Connect the output InController to the converter, than connect the converter to this input.
if (ConverterInput->Connect(InController) && Connect(*ConverterOutput))
{
return true;
}
return false;
}
bool FBaseInputController::Disconnect(IOutputController& InController)
{
if (FMetasoundFrontendGraph* Graph = GraphPtr.Get())
{
FGuid FromNodeID = InController.GetOwningNodeID();
FGuid FromVertexID = InController.GetID();
FGuid ToNodeID = GetOwningNodeID();
FGuid ToVertexID = GetID();
auto IsMatchingEdge = [&](const FMetasoundFrontendEdge& Edge)
{
return (Edge.FromNodeID == FromNodeID) && (Edge.FromVertexID == FromVertexID) && (Edge.ToNodeID == ToNodeID) && (Edge.ToVertexID == ToVertexID);
};
int32 NumRemoved = Graph->Edges.RemoveAllSwap(IsMatchingEdge);
return NumRemoved > 0;
}
return false;
}
bool FBaseInputController::Disconnect()
{
if (FMetasoundFrontendGraph* Graph = GraphPtr.Get())
{
const FGuid NodeID = GetOwningNodeID();
FGuid VertexID = GetID();
auto EdgeHasMatchingDestination = [&](const FMetasoundFrontendEdge& Edge)
{
return (Edge.ToNodeID == NodeID) && (Edge.ToVertexID == VertexID);
};
int32 NumRemoved = Graph->Edges.RemoveAllSwap(EdgeHasMatchingDestination);
return NumRemoved > 0;
}
return false;
}
const FMetasoundFrontendEdge* FBaseInputController::FindEdge() const
{
if (const FMetasoundFrontendGraph* Graph = GraphPtr.Get())
{
const FGuid NodeID = GetOwningNodeID();
FGuid VertexID = GetID();
auto EdgeHasMatchingDestination = [&](const FMetasoundFrontendEdge& Edge)
{
return (Edge.ToNodeID == NodeID) && (Edge.ToVertexID == VertexID);
};
return Graph->Edges.FindByPredicate(EdgeHasMatchingDestination);
}
return nullptr;
}
FMetasoundFrontendEdge* FBaseInputController::FindEdge()
{
if (FMetasoundFrontendGraph* Graph = GraphPtr.Get())
{
const FGuid NodeID = GetOwningNodeID();
FGuid VertexID = GetID();
auto EdgeHasMatchingDestination = [&](const FMetasoundFrontendEdge& Edge)
{
return (Edge.ToNodeID == NodeID) && (Edge.ToVertexID == VertexID);
};
return Graph->Edges.FindByPredicate(EdgeHasMatchingDestination);
}
return nullptr;
}
FDocumentAccess FBaseInputController::ShareAccess()
{
FDocumentAccess Access;
Access.ConstVertex = NodeVertexPtr;
Access.ConstClassInput = ClassInputPtr;
Access.Graph = GraphPtr;
Access.ConstGraph = GraphPtr;
return Access;
}
FConstDocumentAccess FBaseInputController::ShareAccess() const
{
FConstDocumentAccess Access;
Access.ConstVertex = NodeVertexPtr;
Access.ConstClassInput = ClassInputPtr;
Access.ConstGraph = GraphPtr;
return Access;
}
//
// FOutputNodeInputController
//
FOutputNodeInputController::FOutputNodeInputController(const FOutputNodeInputController::FInitParams& InParams)
: FBaseInputController({InParams.ID, InParams.NodeVertexPtr, InParams.ClassInputPtr, InParams.GraphPtr, InParams.OwningNode})
, OwningGraphClassOutputPtr(InParams.OwningGraphClassOutputPtr)
{
}
bool FOutputNodeInputController::IsValid() const
{
return FBaseInputController::IsValid() && (nullptr != OwningGraphClassOutputPtr.Get());
}
const FText& FOutputNodeInputController::GetDisplayName() const
{
if (const FMetasoundFrontendClassOutput* OwningOutput = OwningGraphClassOutputPtr.Get())
{
if (const FMetasoundFrontendClassInput* ClassInput = ClassInputPtr.Get())
{
// If there the ClassInput exists, combine the variable name and class input name.
// of the variable should be added to the names of the vertices.
CachedDisplayName = FText::Format(LOCTEXT("OutputNodeInputControllerFormat", "{1} {0}"), OwningOutput->Metadata.DisplayName, ClassInput->Metadata.DisplayName);
}
else
{
// If there is not ClassInput, then use the variable name.
CachedDisplayName = OwningOutput->Metadata.DisplayName;
}
}
return CachedDisplayName;
}
const FText& FOutputNodeInputController::GetTooltip() const
{
if (const FMetasoundFrontendClassOutput* OwningOutput = OwningGraphClassOutputPtr.Get())
{
return OwningOutput->Metadata.Description;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FMetasoundFrontendVertexMetadata& FOutputNodeInputController::GetMetadata() const
{
if (const FMetasoundFrontendClassOutput* OwningOutput = OwningGraphClassOutputPtr.Get())
{
return OwningOutput->Metadata;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FMetasoundFrontendVertexMetadata>();
}
FDocumentAccess FOutputNodeInputController::ShareAccess()
{
FDocumentAccess Access = FBaseInputController::ShareAccess();
Access.ConstClassOutput = OwningGraphClassOutputPtr;
return Access;
}
FConstDocumentAccess FOutputNodeInputController::ShareAccess() const
{
FConstDocumentAccess Access = FBaseInputController::ShareAccess();
Access.ConstClassOutput = OwningGraphClassOutputPtr;
return Access;
}
//
// FInputNodeInputController
//
FInputNodeInputController::FInputNodeInputController(const FInputNodeInputController::FInitParams& InParams)
: FBaseInputController({InParams.ID, InParams.NodeVertexPtr, InParams.ClassInputPtr, InParams.GraphPtr, InParams.OwningNode})
, OwningGraphClassInputPtr(InParams.OwningGraphClassInputPtr)
{
}
bool FInputNodeInputController::IsValid() const
{
return FBaseInputController::IsValid() && (nullptr != OwningGraphClassInputPtr.Get());
}
const FText& FInputNodeInputController::GetDisplayName() const
{
if (const FMetasoundFrontendClassInput* OwningInput = OwningGraphClassInputPtr.Get())
{
return OwningInput->Metadata.DisplayName;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FText& FInputNodeInputController::GetTooltip() const
{
if (const FMetasoundFrontendClassInput* OwningInput = OwningGraphClassInputPtr.Get())
{
return OwningInput->Metadata.Description;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FMetasoundFrontendVertexMetadata& FInputNodeInputController::GetMetadata() const
{
if (const FMetasoundFrontendClassInput* OwningInput = OwningGraphClassInputPtr.Get())
{
return OwningInput->Metadata;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FMetasoundFrontendVertexMetadata>();
}
FConnectability FInputNodeInputController::CanConnectTo(const IOutputController& InController) const
{
static const FConnectability Connectability = {FConnectability::EConnectable::No};
return Connectability;
}
bool FInputNodeInputController::Connect(IOutputController& InController)
{
return false;
}
bool FInputNodeInputController::ConnectWithConverterNode(IOutputController& InController, const FConverterNodeInfo& InNodeClassName)
{
return false;
}
//
// FBaseNodeController
//
FBaseNodeController::FBaseNodeController(const FBaseNodeController::FInitParams& InParams)
: NodePtr(InParams.NodePtr)
, ClassPtr(InParams.ClassPtr)
, OwningGraph(InParams.OwningGraph)
{
if (FMetasoundFrontendNode* Node = NodePtr.Get())
{
if (const FMetasoundFrontendClass* Class = ClassPtr.Get())
{
if (Node->ClassID != Class->ID)
{
UE_LOG(LogMetaSound, Warning, TEXT("Changing node's class id from [ClassID:%s] to [ClassID:%s]"), *Node->ClassID.ToString(), *Class->ID.ToString());
Node->ClassID = Class->ID;
}
}
}
}
bool FBaseNodeController::IsValid() const
{
return OwningGraph->IsValid() && (nullptr != NodePtr.Get()) && (nullptr != ClassPtr.Get());
}
FGuid FBaseNodeController::GetOwningGraphClassID() const
{
return OwningGraph->GetClassID();
}
FGraphHandle FBaseNodeController::GetOwningGraph()
{
return OwningGraph;
}
FConstGraphHandle FBaseNodeController::GetOwningGraph() const
{
return OwningGraph;
}
FGuid FBaseNodeController::GetID() const
{
if (const FMetasoundFrontendNode* Node = NodePtr.Get())
{
return Node->ID;
}
return Metasound::FrontendInvalidID;
}
FGuid FBaseNodeController::GetClassID() const
{
if (const FMetasoundFrontendClass* Class = ClassPtr.Get())
{
return Class->ID;
}
return Metasound::FrontendInvalidID;
}
const FMetasoundFrontendLiteral* FBaseNodeController::GetInputLiteral(const FGuid& InVertexID) const
{
if (const FMetasoundFrontendNode* Node = NodePtr.Get())
{
for (const FMetasoundFrontendVertexLiteral& VertexLiteral : Node->InputLiterals)
{
if (VertexLiteral.VertexID == InVertexID)
{
return &VertexLiteral.Value;
}
}
}
return nullptr;
}
void FBaseNodeController::SetInputLiteral(const FMetasoundFrontendVertexLiteral& InVertexLiteral)
{
if (FMetasoundFrontendNode* Node = NodePtr.Get())
{
auto IsInputVertex = [InVertexLiteral] (const FMetasoundFrontendVertex& Vertex)
{
return InVertexLiteral.VertexID == Vertex.VertexID;
};
FMetasoundFrontendNodeInterface& NodeInterface = Node->Interface;
if (!ensure(NodeInterface.Inputs.ContainsByPredicate(IsInputVertex)))
{
return;
}
for (FMetasoundFrontendVertexLiteral& VertexLiteral : Node->InputLiterals)
{
if (VertexLiteral.VertexID == InVertexLiteral.VertexID)
{
if (ensure(VertexLiteral.Value.GetType() == InVertexLiteral.Value.GetType()))
{
VertexLiteral = InVertexLiteral;
}
return;
}
}
Node->InputLiterals.Add(InVertexLiteral);
}
}
bool FBaseNodeController::ClearInputLiteral(FGuid InVertexID)
{
if (FMetasoundFrontendNode* Node = NodePtr.Get())
{
auto IsInputVertex = [InVertexID](const FMetasoundFrontendVertexLiteral& VertexLiteral)
{
return InVertexID == VertexLiteral.VertexID;
};
return Node->InputLiterals.RemoveAllSwap(IsInputVertex, false) > 0;
}
return false;
}
const FMetasoundFrontendClassInterface& FBaseNodeController::GetClassInterface() const
{
if (const FMetasoundFrontendClass* Class = ClassPtr.Get())
{
return Class->Interface;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FMetasoundFrontendClassInterface>();
}
const FMetasoundFrontendClassMetadata& FBaseNodeController::GetClassMetadata() const
{
if (const FMetasoundFrontendClass* Class = ClassPtr.Get())
{
return Class->Metadata;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FMetasoundFrontendClassMetadata>();
}
const FMetasoundFrontendInterfaceStyle& FBaseNodeController::GetInputStyle() const
{
if (const FMetasoundFrontendClass* Class = ClassPtr.Get())
{
return Class->Interface.InputStyle;
}
static const FMetasoundFrontendInterfaceStyle Invalid;
return Invalid;
}
const FMetasoundFrontendInterfaceStyle& FBaseNodeController::GetOutputStyle() const
{
if (const FMetasoundFrontendClass* Class = ClassPtr.Get())
{
return Class->Interface.OutputStyle;
}
static const FMetasoundFrontendInterfaceStyle Invalid;
return Invalid;
}
const FMetasoundFrontendClassStyle& FBaseNodeController::GetClassStyle() const
{
if (const FMetasoundFrontendClass* Class = ClassPtr.Get())
{
return Class->Style;
}
static const FMetasoundFrontendClassStyle Invalid;
return Invalid;
}
const FMetasoundFrontendNodeStyle& FBaseNodeController::GetNodeStyle() const
{
if (const FMetasoundFrontendNode* Node = NodePtr.Get())
{
return Node->Style;
}
static const FMetasoundFrontendNodeStyle Invalid;
return Invalid;
}
void FBaseNodeController::SetNodeStyle(const FMetasoundFrontendNodeStyle& InStyle)
{
if (FMetasoundFrontendNode* Node = NodePtr.Get())
{
Node->Style = InStyle;
}
}
const FText& FBaseNodeController::GetDescription() const
{
if (const FMetasoundFrontendClass* Class = ClassPtr.Get())
{
return Class->Metadata.Description;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FString& FBaseNodeController::GetNodeName() const
{
if (const FMetasoundFrontendNode* Node = NodePtr.Get())
{
return Node->Name;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FString>();
}
bool FBaseNodeController::CanAddInput(const FString& InVertexName) const
{
// TODO: not yet supported
return false;
}
FInputHandle FBaseNodeController::AddInput(const FString& InVertexName, const FMetasoundFrontendLiteral* InDefault)
{
checkNoEntry();
// TODO: not yet supported
return FInvalidInputController::GetInvalid();
}
bool FBaseNodeController::RemoveInput(FGuid InVertexID)
{
checkNoEntry();
// TODO: not yet supported
return false;
}
bool FBaseNodeController::CanAddOutput(const FString& InVertexName) const
{
// TODO: not yet supported
return false;
}
FInputHandle FBaseNodeController::AddOutput(const FString& InVertexName, const FMetasoundFrontendLiteral* InDefault)
{
checkNoEntry();
// TODO: not yet supported
return FInvalidInputController::GetInvalid();
}
bool FBaseNodeController::RemoveOutput(FGuid InVertexID)
{
checkNoEntry();
// TODO: not yet supported
return false;
}
TArray<FInputHandle> FBaseNodeController::GetInputs()
{
TArray<FInputHandle> Inputs;
FNodeHandle ThisNode = this->AsShared();
for (const FInputControllerParams& Params : GetInputControllerParams())
{
FInputHandle InputHandle = CreateInputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassInputPtr, ThisNode);
if (InputHandle->IsValid())
{
Inputs.Add(InputHandle);
}
}
return Inputs;
}
int32 FBaseNodeController::GetNumInputs() const
{
if (const FMetasoundFrontendNode* Node = NodePtr.Get())
{
return Node->Interface.Inputs.Num();
}
return 0;
}
void FBaseNodeController::IterateInputs(TUniqueFunction<void(FInputHandle)> InFunction)
{
for (const FInputControllerParams& Params : GetInputControllerParams())
{
FInputHandle InputHandle = CreateInputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassInputPtr, AsShared());
if (InputHandle->IsValid())
{
InFunction(InputHandle);
}
}
}
TArray<FOutputHandle> FBaseNodeController::GetOutputs()
{
TArray<FOutputHandle> Outputs;
FNodeHandle ThisNode = this->AsShared();
for (const FOutputControllerParams& Params : GetOutputControllerParams())
{
FOutputHandle OutputHandle = CreateOutputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassOutputPtr, ThisNode);
if (OutputHandle->IsValid())
{
Outputs.Add(OutputHandle);
}
}
return Outputs;
}
int32 FBaseNodeController::GetNumOutputs() const
{
if (const FMetasoundFrontendNode* Node = NodePtr.Get())
{
return Node->Interface.Outputs.Num();
}
return 0;
}
TArray<FConstInputHandle> FBaseNodeController::GetConstInputs() const
{
TArray<FConstInputHandle> Inputs;
// If I had a nickle for every time C++ backed me into a corner, I would be sitting
// on a tropical beach next to my mansion sipping strawberry daiquiris instead of
// trying to code using this guileful language. The const cast is generally safe here
// because the FConstInputHandle only allows const access to the internal node controller.
// Ostensibly, there could have been a INodeController and IConstNodeController
// which take different types in their constructor, but it starts to become
// difficult to maintain. So instead of adding 500 lines of nearly duplicate
// code, a ConstCastSharedRef is used here.
FNodeHandle ThisNode = ConstCastSharedRef<INodeController>(this->AsShared());
for (const FInputControllerParams& Params : GetInputControllerParams())
{
FConstInputHandle InputHandle = CreateInputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassInputPtr, ThisNode);
if (InputHandle->IsValid())
{
Inputs.Add(InputHandle);
}
}
return Inputs;
}
void FBaseNodeController::IterateOutputs(TUniqueFunction<void(FOutputHandle)> InFunction)
{
for (const FOutputControllerParams& Params : GetOutputControllerParams())
{
FOutputHandle OutputHandle = CreateOutputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassOutputPtr, AsShared());
if (OutputHandle->IsValid())
{
InFunction(OutputHandle);
}
}
}
const FText& FBaseNodeController::GetDisplayTitle() const
{
if (const FMetasoundFrontendClass* Class = ClassPtr.Get())
{
return Class->Metadata.DisplayName;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FText& FBaseNodeController::GetDisplayName() const
{
if (const FMetasoundFrontendClass* Class = ClassPtr.Get())
{
return Class->Metadata.DisplayName;
}
return FText::GetEmpty();
}
void FBaseNodeController::IterateConstInputs(TUniqueFunction<void(FConstInputHandle)> InFunction) const
{
// See early use of ConstCastSharedRef in this class for discussion.
FNodeHandle ThisNode = ConstCastSharedRef<INodeController>(this->AsShared());
for (const FInputControllerParams& Params : GetInputControllerParams())
{
FConstInputHandle InputHandle = CreateInputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassInputPtr, ThisNode);
if (InputHandle->IsValid())
{
InFunction(InputHandle);
}
}
}
TArray<FConstOutputHandle> FBaseNodeController::GetConstOutputs() const
{
TArray<FConstOutputHandle> Outputs;
// See early use of ConstCastSharedRef in this class for discussion.
FNodeHandle ThisNode = ConstCastSharedRef<INodeController>(this->AsShared());
for (const FOutputControllerParams& Params : GetOutputControllerParams())
{
FConstOutputHandle OutputHandle = CreateOutputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassOutputPtr, ThisNode);
if (OutputHandle->IsValid())
{
Outputs.Add(OutputHandle);
}
}
return Outputs;
}
void FBaseNodeController::IterateConstOutputs(TUniqueFunction<void(FConstOutputHandle)> InFunction) const
{
// See early use of ConstCastSharedRef in this class for discussion.
FNodeHandle ThisNode = ConstCastSharedRef<INodeController>(this->AsShared());
for (const FOutputControllerParams& Params : GetOutputControllerParams())
{
FConstOutputHandle OutputHandle = CreateOutputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassOutputPtr, ThisNode);
if (OutputHandle->IsValid())
{
InFunction(OutputHandle);
}
}
}
TArray<FInputHandle> FBaseNodeController::GetInputsWithVertexName(const FString& InName)
{
TArray<FInputHandle> Inputs;
FNodeHandle ThisNode = this->AsShared();
for (const FInputControllerParams& Params : GetInputControllerParamsWithVertexName(InName))
{
FInputHandle InputHandle = CreateInputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassInputPtr, ThisNode);
if (InputHandle->IsValid())
{
Inputs.Add(InputHandle);
}
}
return Inputs;
}
TArray<FConstInputHandle> FBaseNodeController::GetConstInputsWithVertexName(const FString& InName) const
{
TArray<FConstInputHandle> Inputs;
// See early use of ConstCastSharedRef in this class for discussion.
FNodeHandle ThisNode = ConstCastSharedRef<INodeController>(this->AsShared());
for (const FInputControllerParams& Params : GetInputControllerParamsWithVertexName(InName))
{
FInputHandle InputHandle = CreateInputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassInputPtr, ThisNode);
if (InputHandle->IsValid())
{
Inputs.Add(InputHandle);
}
}
return Inputs;
}
TArray<FOutputHandle> FBaseNodeController::GetOutputsWithVertexName(const FString& InName)
{
TArray<FOutputHandle> Outputs;
FNodeHandle ThisNode = this->AsShared();
for (const FOutputControllerParams& Params : GetOutputControllerParamsWithVertexName(InName))
{
FOutputHandle OutputHandle = CreateOutputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassOutputPtr, ThisNode);
if (OutputHandle->IsValid())
{
Outputs.Add(OutputHandle);
}
}
return Outputs;
}
TArray<FConstOutputHandle> FBaseNodeController::GetConstOutputsWithVertexName(const FString& InName) const
{
TArray<FConstOutputHandle> Outputs;
// See early use of ConstCastSharedRef in this class for discussion.
FNodeHandle ThisNode = ConstCastSharedRef<INodeController>(this->AsShared());
for (const FOutputControllerParams& Params : GetOutputControllerParamsWithVertexName(InName))
{
FConstOutputHandle OutputHandle = CreateOutputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassOutputPtr, ThisNode);
if (OutputHandle->IsValid())
{
Outputs.Add(OutputHandle);
}
}
return Outputs;
}
FInputHandle FBaseNodeController::GetInputWithID(FGuid InVertexID)
{
FInputControllerParams Params;
if (FindInputControllerParamsWithID(InVertexID, Params))
{
FNodeHandle ThisNode = this->AsShared();
return CreateInputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassInputPtr, ThisNode);
}
return FInvalidInputController::GetInvalid();
}
bool FBaseNodeController::IsRequired() const
{
return false;
}
FConstInputHandle FBaseNodeController::GetInputWithID(FGuid InVertexID) const
{
FInputControllerParams Params;
if (FindInputControllerParamsWithID(InVertexID, Params))
{
// See early use of ConstCastSharedRef in this class for discussion.
FNodeHandle ThisNode = ConstCastSharedRef<INodeController>(this->AsShared());
return CreateInputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassInputPtr, ThisNode);
}
return FInvalidInputController::GetInvalid();
}
FOutputHandle FBaseNodeController::GetOutputWithID(FGuid InVertexID)
{
FOutputControllerParams Params;
if (FindOutputControllerParamsWithID(InVertexID, Params))
{
FNodeHandle ThisNode = this->AsShared();
return CreateOutputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassOutputPtr, ThisNode);
}
return FInvalidOutputController::GetInvalid();
}
FConstOutputHandle FBaseNodeController::GetOutputWithID(FGuid InVertexID) const
{
FOutputControllerParams Params;
if (FindOutputControllerParamsWithID(InVertexID, Params))
{
// See early use of ConstCastSharedRef in this class for discussion.
FNodeHandle ThisNode = ConstCastSharedRef<INodeController>(this->AsShared());
return CreateOutputController(Params.VertexID, Params.NodeVertexPtr, Params.ClassOutputPtr, ThisNode);
}
return FInvalidOutputController::GetInvalid();
}
TArray<FBaseNodeController::FInputControllerParams> FBaseNodeController::GetInputControllerParams() const
{
TArray<FBaseNodeController::FInputControllerParams> Inputs;
if (const FMetasoundFrontendNode* Node = NodePtr.Get())
{
for (const FMetasoundFrontendVertex& NodeInputVertex : Node->Interface.Inputs)
{
FConstVertexAccessPtr NodeVertexPtr = NodePtr.GetInputWithName(NodeInputVertex.Name);
FConstClassInputAccessPtr ClassInputPtr = ClassPtr.GetInputWithName(NodeInputVertex.Name);
Inputs.Add({NodeInputVertex.VertexID, NodeVertexPtr, ClassInputPtr});
}
}
return Inputs;
}
TArray<FBaseNodeController::FOutputControllerParams> FBaseNodeController::GetOutputControllerParams() const
{
TArray<FBaseNodeController::FOutputControllerParams> Outputs;
if (const FMetasoundFrontendNode* Node = NodePtr.Get())
{
for (const FMetasoundFrontendVertex& NodeOutputVertex : Node->Interface.Outputs)
{
const FString& VertexName = NodeOutputVertex.Name;
FConstVertexAccessPtr NodeVertexPtr = NodePtr.GetOutputWithName(VertexName);
FConstClassOutputAccessPtr ClassOutputPtr = ClassPtr.GetOutputWithName(VertexName);
Outputs.Add({NodeOutputVertex.VertexID, NodeVertexPtr, ClassOutputPtr});
}
}
return Outputs;
}
TArray<FBaseNodeController::FInputControllerParams> FBaseNodeController::GetInputControllerParamsWithVertexName(const FString& InName) const
{
TArray<FBaseNodeController::FInputControllerParams> Inputs;
FConstVertexAccessPtr NodeVertexPtr = NodePtr.GetInputWithName(InName);
if (const FMetasoundFrontendVertex* Vertex = NodeVertexPtr.Get())
{
FConstClassInputAccessPtr ClassInputPtr = ClassPtr.GetInputWithName(InName);
Inputs.Add({Vertex->VertexID, NodeVertexPtr, ClassInputPtr});
}
return Inputs;
}
TArray<FBaseNodeController::FOutputControllerParams> FBaseNodeController::GetOutputControllerParamsWithVertexName(const FString& InName) const
{
TArray<FBaseNodeController::FOutputControllerParams> Outputs;
FConstVertexAccessPtr NodeVertexPtr = NodePtr.GetOutputWithName(InName);
if (const FMetasoundFrontendVertex* Vertex = NodeVertexPtr.Get())
{
FConstClassOutputAccessPtr ClassOutputPtr = ClassPtr.GetOutputWithName(InName);
Outputs.Add({Vertex->VertexID, NodeVertexPtr, ClassOutputPtr});
}
return Outputs;
}
bool FBaseNodeController::FindInputControllerParamsWithID(FGuid InVertexID, FInputControllerParams& OutParams) const
{
FConstVertexAccessPtr NodeVertexPtr = NodePtr.GetInputWithVertexID(InVertexID);
if (const FMetasoundFrontendVertex* Vertex = NodeVertexPtr.Get())
{
FConstClassInputAccessPtr ClassInputPtr = ClassPtr.GetInputWithName(Vertex->Name);
OutParams = FInputControllerParams{InVertexID, NodeVertexPtr, ClassInputPtr};
return true;
}
return false;
}
bool FBaseNodeController::FindOutputControllerParamsWithID(FGuid InVertexID, FOutputControllerParams& OutParams) const
{
FConstVertexAccessPtr NodeVertexPtr = NodePtr.GetOutputWithVertexID(InVertexID);
if (const FMetasoundFrontendVertex* Vertex = NodeVertexPtr.Get())
{
FConstClassOutputAccessPtr ClassOutputPtr = ClassPtr.GetOutputWithName(Vertex->Name);
OutParams = FOutputControllerParams{InVertexID, NodeVertexPtr, ClassOutputPtr};
return true;
}
return false;
}
FGraphHandle FBaseNodeController::AsGraph()
{
// TODO: consider adding support for external graph owned in another document.
// Will require lookup support for external subgraphs..
if (const FMetasoundFrontendClass* Class = ClassPtr.Get())
{
return GetOwningGraph()->GetOwningDocument()->GetSubgraphWithClassID(Class->ID);
}
return FInvalidGraphController::GetInvalid();
}
FConstGraphHandle FBaseNodeController::AsGraph() const
{
// TODO: add support for graph owned in another asset.
// Will require lookup support for external subgraphs..
if (const FMetasoundFrontendClass* Class = ClassPtr.Get())
{
return GetOwningGraph()->GetOwningDocument()->GetSubgraphWithClassID(Class->ID);
}
return FInvalidGraphController::GetInvalid();
}
FDocumentAccess FBaseNodeController::ShareAccess()
{
FDocumentAccess Access;
Access.Node = NodePtr;
Access.ConstNode = NodePtr;
Access.ConstClass = ClassPtr;
return Access;
}
FConstDocumentAccess FBaseNodeController::ShareAccess() const
{
FConstDocumentAccess Access;
Access.ConstNode = NodePtr;
Access.ConstClass = ClassPtr;
return Access;
}
//
// FNodeController
//
FNodeController::FNodeController(EPrivateToken InToken, const FNodeController::FInitParams& InParams)
: FBaseNodeController({InParams.NodePtr, InParams.ClassPtr, InParams.OwningGraph})
, GraphPtr(InParams.GraphPtr)
{
}
FNodeHandle FNodeController::CreateNodeHandle(const FNodeController::FInitParams& InParams)
{
if (const FMetasoundFrontendNode* Node = InParams.NodePtr.Get())
{
if (const FMetasoundFrontendClass* Class = InParams.ClassPtr.Get())
{
// Cannot make a valid node handle if the node description and class description differ
if (Node->ClassID == Class->ID)
{
return MakeShared<FNodeController>(EPrivateToken::Token, InParams);
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Frontend Node [NodeID:%s, ClassID:%s] is not of expected class class [ClassID:%s]"), *Node->ID.ToString(), *Node->ClassID.ToString(), *Class->ID.ToString());
}
}
}
return FInvalidNodeController::GetInvalid();
}
FConstNodeHandle FNodeController::CreateConstNodeHandle(const FNodeController::FInitParams& InParams)
{
if (const FMetasoundFrontendNode* Node = InParams.NodePtr.Get())
{
if (const FMetasoundFrontendClass* Class = InParams.ClassPtr.Get())
{
// Cannot make a valid node handle if the node description and class description differ
if (Node->ClassID == Class->ID)
{
return MakeShared<const FNodeController>(EPrivateToken::Token, InParams);
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Frontend Node [NodeID:%s, ClassID:%s] is not of expected class class [ClassID:%s]"), *Node->ID.ToString(), *Node->ClassID.ToString(), *Class->ID.ToString());
}
}
}
return FInvalidNodeController::GetInvalid();
}
bool FNodeController::IsValid() const
{
return FBaseNodeController::IsValid() && (nullptr != GraphPtr.Get());
}
FInputHandle FNodeController::CreateInputController(FGuid InVertexID, FConstVertexAccessPtr InNodeVertexPtr, FConstClassInputAccessPtr InClassInputPtr, FNodeHandle InOwningNode) const
{
return MakeShared<FBaseInputController>(FBaseInputController::FInitParams{InVertexID, InNodeVertexPtr, InClassInputPtr, GraphPtr, InOwningNode});
}
FOutputHandle FNodeController::CreateOutputController(FGuid InVertexID, FConstVertexAccessPtr InNodeVertexPtr, FConstClassOutputAccessPtr InClassOutputPtr, FNodeHandle InOwningNode) const
{
return MakeShared<FBaseOutputController>(FBaseOutputController::FInitParams{InVertexID, InNodeVertexPtr, InClassOutputPtr, GraphPtr, InOwningNode});
}
FDocumentAccess FNodeController::ShareAccess()
{
FDocumentAccess Access = FBaseNodeController::ShareAccess();
Access.Graph = GraphPtr;
Access.ConstGraph = GraphPtr;
return Access;
}
FConstDocumentAccess FNodeController::ShareAccess() const
{
FConstDocumentAccess Access = FBaseNodeController::ShareAccess();
Access.ConstGraph = GraphPtr;
return Access;
}
//
// FOutputNodeController
//
FOutputNodeController::FOutputNodeController(FOutputNodeController::EPrivateToken InToken, const FOutputNodeController::FInitParams& InParams)
: FBaseNodeController({InParams.NodePtr, InParams.ClassPtr, InParams.OwningGraph})
, GraphPtr(InParams.GraphPtr)
, OwningGraphClassOutputPtr(InParams.OwningGraphClassOutputPtr)
{
}
FNodeHandle FOutputNodeController::CreateOutputNodeHandle(const FOutputNodeController::FInitParams& InParams)
{
if (const FMetasoundFrontendNode* Node = InParams.NodePtr.Get())
{
if (const FMetasoundFrontendClass* Class = InParams.ClassPtr.Get())
{
if (EMetasoundFrontendClassType::Output == Class->Metadata.Type)
{
if (Class->ID == Node->ClassID)
{
return MakeShared<FOutputNodeController>(EPrivateToken::Token, InParams);
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Frontend Node [NodeID:%s, ClassID:%s] is not of expected class class [ClassID:%s]"), *Node->ID.ToString(), *Node->ClassID.ToString(), *Class->ID.ToString());
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Frontend Class of incorrect class type [ClassID:%s] while creating output node.. Must be EMetasoundFrontendClassType::Output."), *Class->ID.ToString());
}
}
}
return FInvalidNodeController::GetInvalid();
}
const FText& FOutputNodeController::GetDescription() const
{
if (const FMetasoundFrontendClassOutput* OwningOutput = OwningGraphClassOutputPtr.Get())
{
return OwningOutput->Metadata.Description;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FText& FOutputNodeController::GetDisplayName() const
{
if (const FMetasoundFrontendClassOutput* OwningOutput = OwningGraphClassOutputPtr.Get())
{
return OwningOutput->Metadata.DisplayName;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
void FOutputNodeController::SetDescription(const FText& InDescription)
{
// TODO: can we remove the const cast by constructing output nodes with a non-const access to class outputs?
if (FMetasoundFrontendClassOutput* ClassOutput = ConstCastAccessPtr<FClassOutputAccessPtr>(OwningGraphClassOutputPtr).Get())
{
ClassOutput->Metadata.Description = InDescription;
}
}
void FOutputNodeController::SetDisplayName(const FText& InDisplayName)
{
// TODO: can we remove the const cast by constructing output nodes with a non-const access to class outputs?
if (FMetasoundFrontendClassOutput* ClassOutput = ConstCastAccessPtr<FClassOutputAccessPtr>(OwningGraphClassOutputPtr).Get())
{
ClassOutput->Metadata.DisplayName = InDisplayName;
}
}
FConstNodeHandle FOutputNodeController::CreateConstOutputNodeHandle(const FOutputNodeController::FInitParams& InParams)
{
if (const FMetasoundFrontendNode* Node = InParams.NodePtr.Get())
{
if (const FMetasoundFrontendClass* Class = InParams.ClassPtr.Get())
{
if (EMetasoundFrontendClassType::Output == Class->Metadata.Type)
{
if (Class->ID == Node->ClassID)
{
return MakeShared<const FOutputNodeController>(EPrivateToken::Token, InParams);
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Frontend Node [NodeID:%s, ClassID:%s] is not of expected class class [ClassID:%s]"), *Node->ID.ToString(), *Node->ClassID.ToString(), *Class->ID.ToString());
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Frontend Class of incorrect class type [ClassID:%s] while creating output node.. Must be EMetasoundFrontendClassType::Output."), *Class->ID.ToString());
}
}
}
return FInvalidNodeController::GetInvalid();
}
const FText& FOutputNodeController::GetDisplayTitle() const
{
static FText OutputDisplayTitle = LOCTEXT("OutputNode_Title", "Output");
return OutputDisplayTitle;
}
bool FOutputNodeController::IsRequired() const
{
FConstDocumentHandle OwningDocument = OwningGraph->GetOwningDocument();
FConstGraphHandle RootGraph = OwningDocument->GetRootGraph();
// Test if this node exists on the document's root graph.
const bool bIsNodeOnRootGraph = OwningGraph->IsValid() && (RootGraph->GetClassID() == OwningGraph->GetClassID());
if (bIsNodeOnRootGraph)
{
// If the node is on the root graph, test if it is in the archetypes
// required inputs or outputs.
FMetasoundFrontendArchetype Archetype;
FArchetypeRegistryKey ArchetypeKey = GetArchetypeRegistryKey(OwningDocument->GetArchetypeVersion());
bool bFoundArchetype = IArchetypeRegistry::Get().FindArchetype(ArchetypeKey, Archetype);
if (bFoundArchetype)
{
if (const FMetasoundFrontendNode* Node = NodePtr.Get())
{
const FString& Name = Node->Name;
auto IsVertexWithSameName = [&Name](const FMetasoundFrontendClassVertex& InVertex)
{
return InVertex.Name == Name;
};
return Archetype.Interface.Outputs.ContainsByPredicate(IsVertexWithSameName);
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Document using unregistered archetype [ArchetypeVersion:%s]"), *OwningDocument->GetArchetypeVersion().ToString());
}
}
return false;
}
bool FOutputNodeController::IsValid() const
{
return OwningGraph->IsValid() && (nullptr != OwningGraphClassOutputPtr.Get()) && (nullptr != GraphPtr.Get());
}
FInputHandle FOutputNodeController::CreateInputController(FGuid InVertexID, FConstVertexAccessPtr InNodeVertexPtr, FConstClassInputAccessPtr InClassInputPtr, FNodeHandle InOwningNode) const
{
return MakeShared<FOutputNodeInputController>(FOutputNodeInputController::FInitParams{InVertexID, InNodeVertexPtr, InClassInputPtr, OwningGraphClassOutputPtr, GraphPtr, InOwningNode});
}
FOutputHandle FOutputNodeController::CreateOutputController(FGuid InVertexID, FConstVertexAccessPtr InNodeVertexPtr, FConstClassOutputAccessPtr InClassOutputPtr, FNodeHandle InOwningNode) const
{
return MakeShared<FOutputNodeOutputController>(FOutputNodeOutputController::FInitParams{InVertexID, InNodeVertexPtr, InClassOutputPtr, OwningGraphClassOutputPtr, GraphPtr, InOwningNode});
}
FDocumentAccess FOutputNodeController::ShareAccess()
{
FDocumentAccess Access = FBaseNodeController::ShareAccess();
Access.Graph = GraphPtr;
Access.ConstGraph = GraphPtr;
Access.ConstClassOutput = OwningGraphClassOutputPtr;
return Access;
}
FConstDocumentAccess FOutputNodeController::ShareAccess() const
{
FConstDocumentAccess Access = FBaseNodeController::ShareAccess();
Access.ConstGraph = GraphPtr;
Access.ConstClassOutput = OwningGraphClassOutputPtr;
return Access;
}
//
// FInputNodeController
//
FInputNodeController::FInputNodeController(EPrivateToken InToken, const FInputNodeController::FInitParams& InParams)
: FBaseNodeController({InParams.NodePtr, InParams.ClassPtr, InParams.OwningGraph})
, OwningGraphClassInputPtr(InParams.OwningGraphClassInputPtr)
, GraphPtr(InParams.GraphPtr)
{
}
FNodeHandle FInputNodeController::CreateInputNodeHandle(const FInputNodeController::FInitParams& InParams)
{
if (const FMetasoundFrontendNode* Node = InParams.NodePtr.Get())
{
if (const FMetasoundFrontendClass* Class = InParams.ClassPtr.Get())
{
if (EMetasoundFrontendClassType::Input == Class->Metadata.Type)
{
if (Class->ID == Node->ClassID)
{
return MakeShared<FInputNodeController>(EPrivateToken::Token, InParams);
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Frontend Node [NodeID:%s, ClassID:%s] is not of expected class class [ClassID:%s]"), *Node->ID.ToString(), *Node->ClassID.ToString(), *Class->ID.ToString());
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Frontend Class of incorrect class type [ClassID:%s] while creating input node. Must be EMetasoundFrontendClassType::Input."), *Class->ID.ToString());
}
}
}
return FInvalidNodeController::GetInvalid();
}
FConstNodeHandle FInputNodeController::CreateConstInputNodeHandle(const FInputNodeController::FInitParams& InParams)
{
if (const FMetasoundFrontendNode* Node = InParams.NodePtr.Get())
{
if (const FMetasoundFrontendClass* Class = InParams.ClassPtr.Get())
{
if (EMetasoundFrontendClassType::Input == Class->Metadata.Type)
{
if (Class->ID == Node->ClassID)
{
return MakeShared<const FInputNodeController>(EPrivateToken::Token, InParams);
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Frontend Node [NodeID:%s, ClassID:%s] is not of expected class class [ClassID:%s]"), *Node->ID.ToString(), *Node->ClassID.ToString(), *Class->ID.ToString());
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Frontend Class of incorrect class type [ClassID:%s] while creating input node. Must be EMetasoundFrontendClassType::Input."), *Class->ID.ToString());
}
}
}
return FInvalidNodeController::GetInvalid();
}
bool FInputNodeController::IsValid() const
{
return OwningGraph->IsValid() && (nullptr != OwningGraphClassInputPtr.Get()) && (nullptr != GraphPtr.Get());
}
FInputHandle FInputNodeController::CreateInputController(FGuid InVertexID, FConstVertexAccessPtr InNodeVertexPtr, FConstClassInputAccessPtr InClassInputPtr, FNodeHandle InOwningNode) const
{
return MakeShared<FInputNodeInputController>(FInputNodeInputController::FInitParams{InVertexID, InNodeVertexPtr, InClassInputPtr, OwningGraphClassInputPtr, GraphPtr, InOwningNode});
}
FOutputHandle FInputNodeController::CreateOutputController(FGuid InVertexID, FConstVertexAccessPtr InNodeVertexPtr, FConstClassOutputAccessPtr InClassOutputPtr, FNodeHandle InOwningNode) const
{
return MakeShared<FInputNodeOutputController>(FInputNodeOutputController::FInitParams{InVertexID, InNodeVertexPtr, InClassOutputPtr, OwningGraphClassInputPtr, GraphPtr, InOwningNode});
}
const FText& FInputNodeController::GetDescription() const
{
if (const FMetasoundFrontendClassInput* OwningInput = OwningGraphClassInputPtr.Get())
{
return OwningInput->Metadata.Description;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FText& FInputNodeController::GetDisplayName() const
{
if (const FMetasoundFrontendClassInput* OwningInput = OwningGraphClassInputPtr.Get())
{
return OwningInput->Metadata.DisplayName;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
const FText& FInputNodeController::GetDisplayTitle() const
{
static FText InputDisplayTitle = LOCTEXT("InputNode_Title", "Input");
return InputDisplayTitle;
}
bool FInputNodeController::IsRequired() const
{
FConstDocumentHandle OwningDocument = OwningGraph->GetOwningDocument();
FConstGraphHandle RootGraph = OwningDocument->GetRootGraph();
// Test if this node exists on the document's root graph.
const bool bIsNodeOnRootGraph = OwningGraph->IsValid() && (RootGraph->GetClassID() == OwningGraph->GetClassID());
if (bIsNodeOnRootGraph)
{
// If the node is on the root graph, test if it is in the archetypes
// required inputs or outputs.
FMetasoundFrontendArchetype Archetype;
FArchetypeRegistryKey ArchetypeKey = GetArchetypeRegistryKey(OwningDocument->GetArchetypeVersion());
bool bFoundArchetype = IArchetypeRegistry::Get().FindArchetype(ArchetypeKey, Archetype);
if (bFoundArchetype)
{
if (const FMetasoundFrontendNode* Node = NodePtr.Get())
{
const FString& Name = Node->Name;
auto IsVertexWithSameName = [&Name](const FMetasoundFrontendClassVertex& InVertex)
{
return InVertex.Name == Name;
};
return Archetype.Interface.Inputs.ContainsByPredicate(IsVertexWithSameName);
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Document using unregistered archetype [ArchetypeVersion:%s]"), *OwningDocument->GetArchetypeVersion().ToString());
}
}
return false;
}
void FInputNodeController::SetDescription(const FText& InDescription)
{
// TODO: can we remove these const casts by constructing FINputNodeController with non-const access to the class input?
if (FMetasoundFrontendClassInput* ClassInput = ConstCastAccessPtr<FClassInputAccessPtr>(OwningGraphClassInputPtr).Get())
{
ClassInput->Metadata.Description = InDescription;
}
}
void FInputNodeController::SetDisplayName(const FText& InDisplayName)
{
// TODO: can we remove these const casts by constructing FINputNodeController with non-const access to the class input?
if (FMetasoundFrontendClassInput* ClassInput = ConstCastAccessPtr<FClassInputAccessPtr>(OwningGraphClassInputPtr).Get())
{
ClassInput->Metadata.DisplayName = InDisplayName;
}
}
FDocumentAccess FInputNodeController::ShareAccess()
{
FDocumentAccess Access = FBaseNodeController::ShareAccess();
Access.Graph = GraphPtr;
Access.ConstGraph = GraphPtr;
Access.ConstClassInput = OwningGraphClassInputPtr;
return Access;
}
FConstDocumentAccess FInputNodeController::ShareAccess() const
{
FConstDocumentAccess Access = FBaseNodeController::ShareAccess();
Access.ConstGraph = GraphPtr;
Access.ConstClassInput = OwningGraphClassInputPtr;
return Access;
}
//
// 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.Type == 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 FInvalidGraphController::GetInvalid();
}
FConstGraphHandle FGraphController::CreateConstGraphHandle(const FGraphController::FInitParams& InParams)
{
if (const FMetasoundFrontendGraphClass* GraphClass = InParams.GraphClassPtr.Get())
{
if (GraphClass->Metadata.Type == 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 FInvalidGraphController::GetInvalid();
}
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;
}
const FText& FGraphController::GetDisplayName() const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->Metadata.DisplayName;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FText>();
}
TArray<FString> FGraphController::GetInputVertexNames() const
{
TArray<FString> Names;
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
for (const FMetasoundFrontendClassInput& Input : GraphClass->Interface.Inputs)
{
Names.Add(Input.Name);
}
}
return Names;
}
TArray<FString> FGraphController::GetOutputVertexNames() const
{
TArray<FString> Names;
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
for (const FMetasoundFrontendClassOutput& Output : GraphClass->Interface.Outputs)
{
Names.Add(Output.Name);
}
}
return Names;
}
FConstClassInputAccessPtr FGraphController::FindClassInputWithName(const FString& InName) const
{
return GraphClassPtr.GetInputWithName(InName);
}
FConstClassOutputAccessPtr FGraphController::FindClassOutputWithName(const FString& InName) const
{
return GraphClassPtr.GetOutputWithName(InName);
}
FGuid FGraphController::GetVertexIDForInputVertex(const FString& InInputName) const
{
if (const FMetasoundFrontendClassInput* Input = FindClassInputWithName(InInputName).Get())
{
return Input->VertexID;
}
return Metasound::FrontendInvalidID;
}
FGuid FGraphController::GetVertexIDForOutputVertex(const FString& 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.ID == InNodeID;
};
return GetNodeByPredicate(IsNodeWithSameID);
}
FNodeHandle FGraphController::GetNodeWithID(FGuid InNodeID)
{
auto IsNodeWithSameID = [&](const FMetasoundFrontendClass& NodeClas, const FMetasoundFrontendNode& Node)
{
return Node.ID == InNodeID;
};
return GetNodeByPredicate(IsNodeWithSameID);
}
const FMetasoundFrontendGraphStyle& FGraphController::GetGraphStyle() const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->Graph.Style;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FMetasoundFrontendGraphStyle>();
}
void FGraphController::SetGraphStyle(const FMetasoundFrontendGraphStyle& InStyle)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
GraphClass->Graph.Style = InStyle;
}
}
TArray<FNodeHandle> FGraphController::GetOutputNodes()
{
auto IsOutputNode = [](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return NodeClass.Metadata.Type == EMetasoundFrontendClassType::Output;
};
return GetNodesByPredicate(IsOutputNode);
}
TArray<FNodeHandle> FGraphController::GetInputNodes()
{
auto IsInputNode = [](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return NodeClass.Metadata.Type == EMetasoundFrontendClassType::Input;
};
return GetNodesByPredicate(IsInputNode);
}
void FGraphController::IterateNodes(TUniqueFunction<void(FNodeHandle)> InFunction, EMetasoundFrontendClassType InClassType)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
for (const FMetasoundFrontendNode& Node : GraphClass->Graph.Nodes)
{
FConstClassAccessPtr NodeClassPtr = OwningDocument->FindClassWithID(Node.ClassID);
if (const FMetasoundFrontendClass* NodeClass = NodeClassPtr.Get())
{
if (InClassType == EMetasoundFrontendClassType::Invalid || NodeClass->Metadata.Type == InClassType)
{
FNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.ID);
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.ID.ToString(), *Node.ClassID.ToString());
}
}
}
}
void FGraphController::IterateConstNodes(TUniqueFunction<void(FConstNodeHandle)> InFunction, EMetasoundFrontendClassType InClassType) const
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
for (const FMetasoundFrontendNode& Node : GraphClass->Graph.Nodes)
{
FConstClassAccessPtr NodeClassPtr = OwningDocument->FindClassWithID(Node.ClassID);
if (const FMetasoundFrontendClass* NodeClass = NodeClassPtr.Get())
{
if (InClassType == EMetasoundFrontendClassType::Invalid || NodeClass->Metadata.Type == InClassType)
{
FConstNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.ID);
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.ID.ToString(), *Node.ClassID.ToString());
}
}
}
}
TArray<FConstNodeHandle> FGraphController::GetConstOutputNodes() const
{
auto IsOutputNode = [](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return NodeClass.Metadata.Type == EMetasoundFrontendClassType::Output;
};
return GetNodesByPredicate(IsOutputNode);
}
TArray<FConstNodeHandle> FGraphController::GetConstInputNodes() const
{
auto IsInputNode = [](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return NodeClass.Metadata.Type == EMetasoundFrontendClassType::Input;
};
return GetNodesByPredicate(IsInputNode);
}
bool FGraphController::ContainsOutputVertexWithName(const FString& InName) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsOutputVertexWithSameName = [&](const FMetasoundFrontendClassOutput& ClassOutput)
{
return ClassOutput.Name == InName;
};
return GraphClass->Interface.Outputs.ContainsByPredicate(IsOutputVertexWithSameName);
}
return false;
}
bool FGraphController::ContainsInputVertexWithName(const FString& InName) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsInputVertexWithSameName = [&](const FMetasoundFrontendClassInput& ClassInput)
{
return ClassInput.Name == InName;
};
return GraphClass->Interface.Inputs.ContainsByPredicate(IsInputVertexWithSameName);
}
return false;
}
FConstNodeHandle FGraphController::GetOutputNodeWithName(const FString& InName) const
{
auto IsOutputNodeWithSameName = [&](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return (NodeClass.Metadata.Type == EMetasoundFrontendClassType::Output) && (Node.Name == InName);
};
return GetNodeByPredicate(IsOutputNodeWithSameName);
}
FConstNodeHandle FGraphController::GetInputNodeWithName(const FString& InName) const
{
auto IsInputNodeWithSameName = [&](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return (NodeClass.Metadata.Type == EMetasoundFrontendClassType::Input) && (Node.Name == InName);
};
return GetNodeByPredicate(IsInputNodeWithSameName);
}
FNodeHandle FGraphController::GetOutputNodeWithName(const FString& InName)
{
auto IsOutputNodeWithSameName = [&](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return (NodeClass.Metadata.Type == EMetasoundFrontendClassType::Output) && (Node.Name == InName);
};
return GetNodeByPredicate(IsOutputNodeWithSameName);
}
FNodeHandle FGraphController::GetInputNodeWithName(const FString& InName)
{
auto IsInputNodeWithSameName = [&](const FMetasoundFrontendClass& NodeClass, const FMetasoundFrontendNode& Node)
{
return (NodeClass.Metadata.Type == EMetasoundFrontendClassType::Input) && (Node.Name == InName);
};
return GetNodeByPredicate(IsInputNodeWithSameName);
}
FNodeHandle FGraphController::AddInputVertex(const FMetasoundFrontendClassInput& InClassInput)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsInputWithSameName = [&](const FMetasoundFrontendClassInput& ExistingDesc) { return ExistingDesc.Name == InClassInput.Name; };
if (Algo::NoneOf(GraphClass->Interface.Inputs, IsInputWithSameName))
{
FNodeRegistryKey Key;
if (FRegistry::GetInputNodeRegistryKeyForDataType(InClassInput.TypeName, Key))
{
FConstClassAccessPtr InputClassPtr = OwningDocument->FindOrAddClass(Key);
if (const FMetasoundFrontendClass* InputClass = InputClassPtr.Get())
{
FMetasoundFrontendNode& Node = GraphClass->Graph.Nodes.Emplace_GetRef(*InputClass);
Node.Name = InClassInput.Name;
Node.ID = FGuid::NewGuid();
auto IsVertexWithTypeName = [&](FMetasoundFrontendVertex& Vertex) { return Vertex.TypeName == InClassInput.TypeName; };
if (FMetasoundFrontendVertex* InputVertex = Node.Interface.Inputs.FindByPredicate(IsVertexWithTypeName))
{
InputVertex->Name = InClassInput.Name;
}
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 (Node.Interface.Outputs.Num() == 1)
{
Node.Interface.Outputs[0].Name = InClassInput.Name;
}
else if (FMetasoundFrontendVertex* OutputVertex = Node.Interface.Outputs.FindByPredicate(IsVertexWithTypeName))
{
OutputVertex->Name = InClassInput.Name;
}
FMetasoundFrontendClassInput& NewInput = GraphClass->Interface.Inputs.Add_GetRef(InClassInput);
NewInput.NodeID = Node.ID;
FNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.ID);
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, *GraphClass->ID.ToString());
}
}
return FInvalidNodeController::GetInvalid();
}
bool FGraphController::RemoveInputVertex(const FString& InName)
{
auto IsInputNodeWithSameName = [&](const FMetasoundFrontendClass& InClass, const FMetasoundFrontendNode& InNode)
{
return (InClass.Metadata.Type == EMetasoundFrontendClassType::Input) && (InNode.Name == InName);
};
for (const FNodeAndClass& NodeAndClass : GetNodesAndClassesByPredicate(IsInputNodeWithSameName))
{
if (const FMetasoundFrontendNode* Node = NodeAndClass.Node.Get())
{
return RemoveInput(*Node);
}
}
return false;
}
FNodeHandle FGraphController::AddOutputVertex(const FMetasoundFrontendClassOutput& InClassOutput)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsOutputWithSameName = [&](const FMetasoundFrontendClassOutput& ExistingDesc) { return ExistingDesc.Name == InClassOutput.Name; };
if (Algo::NoneOf(GraphClass->Interface.Outputs, IsOutputWithSameName))
{
FNodeRegistryKey Key;
if (FRegistry::GetOutputNodeRegistryKeyForDataType(InClassOutput.TypeName, Key))
{
FConstClassAccessPtr OutputClassPtr = OwningDocument->FindOrAddClass(Key);
if (const FMetasoundFrontendClass* OutputClass = OutputClassPtr.Get())
{
FMetasoundFrontendNode& Node = GraphClass->Graph.Nodes.Emplace_GetRef(*OutputClass);
Node.Name = InClassOutput.Name;
Node.ID = FGuid::NewGuid();
// TODO: have something that checks if input node has valid interface.
auto IsVertexWithTypeName = [&](FMetasoundFrontendVertex& Vertex) { return Vertex.TypeName == InClassOutput.TypeName; };
if (Node.Interface.Inputs.Num() == 1)
{
Node.Interface.Inputs[0].Name = InClassOutput.Name;
}
else if (FMetasoundFrontendVertex* InputVertex = Node.Interface.Inputs.FindByPredicate(IsVertexWithTypeName))
{
InputVertex->Name = InClassOutput.Name;
}
if (FMetasoundFrontendVertex* OutputVertex = Node.Interface.Outputs.FindByPredicate(IsVertexWithTypeName))
{
OutputVertex->Name = InClassOutput.Name;
}
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());
}
FMetasoundFrontendClassOutput& NewOutput = GraphClass->Interface.Outputs.Add_GetRef(InClassOutput);
NewOutput.NodeID = Node.ID;
FNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.ID);
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, *GraphClass->ID.ToString());
}
}
return FInvalidNodeController::GetInvalid();
}
bool FGraphController::RemoveOutputVertex(const FString& InName)
{
auto IsOutputNodeWithSameName = [&](const FMetasoundFrontendClass& InClass, const FMetasoundFrontendNode& InNode)
{
return (InClass.Metadata.Type == EMetasoundFrontendClassType::Output) && (InNode.Name == InName);
};
for (const FNodeAndClass& NodeAndClass : GetNodesAndClassesByPredicate(IsOutputNodeWithSameName))
{
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 FString& InInputName) const
{
if (const FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithName(InInputName))
{
return FRegistry::Get()->GetDesiredLiteralTypeForDataType(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 FString& InInputName)
{
if (const FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithName(InInputName))
{
return FRegistry::Get()->GetLiteralUClassForDataType(Desc->TypeName);
}
return nullptr;
}
FMetasoundFrontendLiteral FGraphController::GetDefaultInput(const FGuid& InVertexID) const
{
if (const FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithVertexID(InVertexID))
{
return Desc->DefaultLiteral;
}
return FMetasoundFrontendLiteral{};
}
bool FGraphController::SetDefaultInput(const FGuid& InVertexID, const FMetasoundFrontendLiteral& InLiteral)
{
if (FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithVertexID(InVertexID))
{
if (ensure(DoesDataTypeSupportLiteralType(Desc->TypeName, InLiteral.GetType())))
{
Desc->DefaultLiteral = InLiteral;
return true;
}
else
{
SetDefaultInputToDefaultLiteralOfType(InVertexID);
}
}
return false;
}
bool FGraphController::SetDefaultInputToDefaultLiteralOfType(const FGuid& InVertexID)
{
if (FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithVertexID(InVertexID))
{
Metasound::FLiteral Literal = Frontend::GetDefaultParamForDataType(Desc->TypeName);
Desc->DefaultLiteral.SetFromLiteral(Literal);
return Desc->DefaultLiteral.IsValid();
}
return false;
}
const FText& FGraphController::GetInputDescription(const FString& InName) const
{
if (const FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithName(InName))
{
return Desc->Metadata.Description;
}
return FText::GetEmpty();
}
const FText& FGraphController::GetOutputDescription(const FString& InName) const
{
if (const FMetasoundFrontendClassOutput* Desc = FindOutputDescriptionWithName(InName))
{
return Desc->Metadata.Description;
}
return FText::GetEmpty();
}
void FGraphController::SetInputDisplayName(const FString& InName, const FText& InDisplayName)
{
if (FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithName(InName))
{
Desc->Metadata.DisplayName = InDisplayName;
}
}
void FGraphController::SetOutputDisplayName(const FString& InName, const FText& InDisplayName)
{
if (FMetasoundFrontendClassOutput* Desc = FindOutputDescriptionWithName(InName))
{
Desc->Metadata.DisplayName = InDisplayName;
}
}
void FGraphController::SetInputDescription(const FString& InName, const FText& InDescription)
{
if (FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithName(InName))
{
Desc->Metadata.Description = InDescription;
}
}
void FGraphController::SetOutputDescription(const FString& InName, const FText& InDescription)
{
if (FMetasoundFrontendClassOutput* Desc = FindOutputDescriptionWithName(InName))
{
Desc->Metadata.Description = InDescription;
}
}
// 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 FString& InInputName, FGuid InVertexID)
{
if (FMetasoundFrontendClassInput* Desc = FindInputDescriptionWithName(InInputName))
{
Desc->DefaultLiteral.Clear();
}
return false;
}
FNodeHandle FGraphController::AddNode(const FNodeRegistryKey& InKey)
{
// Construct a FNodeClassInfo from this lookup key.
FConstClassAccessPtr Class = OwningDocument->FindOrAddClass(InKey);
const bool bIsValidClass = (nullptr != Class.Get());
if (bIsValidClass)
{
return AddNode(Class);
}
UE_LOG(LogMetaSound, Warning, TEXT("Failed to find or add node class info with registry key [Key:%s]"), *InKey);
return INodeController::GetInvalidHandle();
}
FNodeHandle FGraphController::AddNode(const FMetasoundFrontendClassMetadata& InClassMetadata)
{
return AddNode(NodeRegistryKey::CreateKey(InClassMetadata));
}
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.Type)
{
// 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);
}
// Remove the node corresponding to this node handle.
// On success, invalidates the received node handle.
bool FGraphController::RemoveNode(INodeController& InNode)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
FGuid NodeID = InNode.GetID();
auto IsNodeWithSameID = [&](const FMetasoundFrontendNode& InDesc) { return InDesc.ID == NodeID; };
if (const FMetasoundFrontendNode* Desc = GraphClass->Graph.Nodes.FindByPredicate(IsNodeWithSameID))
{
switch(InNode.GetClassMetadata().Type)
{
case EMetasoundFrontendClassType::Input:
{
return RemoveInput(*Desc);
}
case EMetasoundFrontendClassType::Output:
{
return RemoveOutput(*Desc);
}
case EMetasoundFrontendClassType::Variable:
case EMetasoundFrontendClassType::External:
case EMetasoundFrontendClassType::Graph:
{
return RemoveNode(*Desc);
}
default:
case EMetasoundFrontendClassType::Invalid:
{
static_assert(static_cast<int32>(EMetasoundFrontendClassType::Invalid) == 5, "Possible missing switch case coverage for EMetasoundFrontendClassType.");
checkNoEntry();
}
}
}
}
return false;
}
// Returns the metadata for the current graph, including the name, description and author.
const FMetasoundFrontendClassMetadata& FGraphController::GetGraphMetadata() const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->Metadata;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FMetasoundFrontendClassMetadata>();
}
void FGraphController::SetGraphMetadata(const FMetasoundFrontendClassMetadata& InMetadata)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
GraphClass->Metadata = InMetadata;
}
}
FNodeHandle FGraphController::CreateEmptySubgraph(const FMetasoundFrontendClassMetadata& InInfo)
{
if (InInfo.Type == EMetasoundFrontendClassType::Graph)
{
if (const FMetasoundFrontendClass* ExistingDependency = OwningDocument->FindClass(InInfo).Get())
{
UE_LOG(LogMetaSound, Error, TEXT("Cannot add new subgraph. Metasound class already exists with matching metadata Name: \"%s\", Version %d.%d"), *(ExistingDependency->Metadata.ClassName.GetFullName().ToString()), ExistingDependency->Metadata.Version.Major, ExistingDependency->Metadata.Version.Minor);
}
else
{
return AddNode(OwningDocument->FindOrAddClass(InInfo));
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Incompatible Metasound NodeType encountered when attempting to create an empty subgraph. NodeType must equal EMetasoundFrontendClassType::Graph"));
}
return FInvalidNodeController::GetInvalid();
}
TUniquePtr<IOperator> FGraphController::BuildOperator(const FOperatorSettings& InSettings, const FMetasoundEnvironment& InEnvironment, TArray<IOperatorBuilder::FBuildErrorPtr>& OutBuildErrors) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
// TODO: Implement subgraph inflation step here.
// TODO: bubble up errors.
TArray<FMetasoundFrontendGraphClass> Subgraphs = OwningDocument->GetSubgraphs();
TArray<FMetasoundFrontendClass> Dependencies = OwningDocument->GetDependencies();
TUniquePtr<FFrontendGraph> Graph = FFrontendGraphBuilder::CreateGraph(*GraphClass, Subgraphs, Dependencies);
if (!Graph.IsValid())
{
return TUniquePtr<IOperator>(nullptr);
}
FOperatorBuilder OperatorBuilder(FOperatorBuilderSettings::GetDefaultSettings());
FBuildGraphParams BuildParams{*Graph, InSettings, FDataReferenceCollection{}, InEnvironment};
return OperatorBuilder.BuildGraphOperator(BuildParams, OutBuildErrors);
}
else
{
return TUniquePtr<IOperator>(nullptr);
}
}
FDocumentHandle FGraphController::GetOwningDocument()
{
return OwningDocument;
}
FConstDocumentHandle FGraphController::GetOwningDocument() const
{
return OwningDocument;
}
FNodeHandle FGraphController::AddNode(FConstClassAccessPtr InExistingDependency)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
if (const FMetasoundFrontendClass* NodeClass = InExistingDependency.Get())
{
FMetasoundFrontendNode& Node = GraphClass->Graph.Nodes.Emplace_GetRef(*NodeClass);
Node.ID = FGuid::NewGuid();
FNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.ID);
return GetNodeHandle(FGraphController::FNodeAndClass{NodePtr, InExistingDependency});
}
}
return FInvalidNodeController::GetInvalid();
}
bool FGraphController::RemoveNode(const FMetasoundFrontendNode& InDesc)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
auto IsEdgeForThisNode = [&](const FMetasoundFrontendEdge& ConDesc) { return (ConDesc.FromNodeID == InDesc.ID) || (ConDesc.ToNodeID == InDesc.ID); };
// Remove any reference connections
int32 NumRemoved = GraphClass->Graph.Edges.RemoveAll(IsEdgeForThisNode);
auto IsNodeWithID = [&](const FMetasoundFrontendNode& Desc) { return InDesc.ID == Desc.ID; };
NumRemoved += GraphClass->Graph.Nodes.RemoveAll(IsNodeWithID);
OwningDocument->SynchronizeDependencies();
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.ID; };
int32 NumInputsRemoved = GraphClass->Interface.Inputs.RemoveAll(IsInputWithSameNodeID);
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.ID; };
int32 NumOutputsRemoved = GraphClass->Interface.Outputs.RemoveAll(IsOutputWithSameNodeID);
bool bDidRemoveNode = RemoveNode(InNode);
return (NumOutputsRemoved > 0) || bDidRemoveNode;
}
return false;
}
bool FGraphController::ContainsNodesAndClassesByPredicate(TFunctionRef<bool (const FMetasoundFrontendClass&, const FMetasoundFrontendNode&)> InPredicate) const
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
for (FMetasoundFrontendNode& Node : GraphClass->Graph.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.ID.ToString(), *Node.ClassID.ToString());
}
}
}
return false;
}
TArray<FGraphController::FNodeAndClass> FGraphController::GetNodesAndClasses()
{
TArray<FNodeAndClass> NodesAndClasses;
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
for (FMetasoundFrontendNode& Node : GraphClass->Graph.Nodes)
{
FNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.ID);
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.ID.ToString(), *Node.ClassID.ToString());
}
}
}
return NodesAndClasses;
}
TArray<FGraphController::FConstNodeAndClass> FGraphController::GetNodesAndClasses() const
{
TArray<FConstNodeAndClass> NodesAndClasses;
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
for (const FMetasoundFrontendNode& Node : GraphClass->Graph.Nodes)
{
FConstNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.ID);
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.ID.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 : GraphClass->Graph.Nodes)
{
FConstClassAccessPtr NodeClassPtr = OwningDocument->FindClassWithID(Node.ClassID);
if (const FMetasoundFrontendClass* NodeClass = NodeClassPtr.Get())
{
if (InPredicate(*NodeClass, Node))
{
FNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.ID);
NodesAndClasses.Add(FGraphController::FNodeAndClass{ NodePtr, NodeClassPtr });
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Failed to find class for node [NodeID:%s, ClassID:%s]"), *Node.ID.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())
{
for (const FMetasoundFrontendNode& Node : GraphClass->Graph.Nodes)
{
FConstClassAccessPtr NodeClassPtr = OwningDocument->FindClassWithID(Node.ClassID);
if (const FMetasoundFrontendClass* NodeClass = NodeClassPtr.Get())
{
if (InPredicate(*NodeClass, Node))
{
FConstNodeAccessPtr NodePtr = GraphClassPtr.GetNodeWithNodeID(Node.ID);
NodesAndClasses.Add(FGraphController::FConstNodeAndClass{ NodePtr, NodeClassPtr });
}
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("Failed to find class for node [NodeID:%s, ClassID:%s]"), *Node.ID.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 FInvalidNodeController::GetInvalid();
}
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 FInvalidNodeController::GetInvalid();
}
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.Type)
{
case EMetasoundFrontendClassType::Input:
{
FClassInputAccessPtr OwningGraphClassInputPtr = FindInputDescriptionWithNodeID(Node->ID);
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);
}
}
break;
case EMetasoundFrontendClassType::Output:
{
FClassOutputAccessPtr OwningGraphClassOutputPtr = FindOutputDescriptionWithNodeID(Node->ID);
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);
}
}
break;
case EMetasoundFrontendClassType::External:
{
FNodeController::FInitParams InitParams
{
InNodeAndClass.Node,
InNodeAndClass.Class,
GraphPtr,
OwningGraph
};
return FNodeController::CreateNodeHandle(InitParams);
}
break;
case EMetasoundFrontendClassType::Graph:
{
FSubgraphNodeController::FInitParams InitParams
{
InNodeAndClass.Node,
InNodeAndClass.Class,
GraphPtr,
OwningGraph
};
return FSubgraphNodeController::CreateNodeHandle(InitParams);
}
break;
default:
checkNoEntry();
}
}
return FInvalidNodeController::GetInvalid();
}
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.Type)
{
case EMetasoundFrontendClassType::Input:
{
FConstClassInputAccessPtr OwningGraphClassInputPtr = FindInputDescriptionWithNodeID(Node->ID);
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->ID);
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::External:
case EMetasoundFrontendClassType::Graph:
{
FNodeController::FInitParams InitParams
{
ConstCastAccessPtr<FNodeAccessPtr>(InNodeAndClass.Node),
InNodeAndClass.Class,
ConstCastAccessPtr<FGraphAccessPtr>(GraphPtr),
ConstCastSharedRef<IGraphController>(OwningGraph)
};
return FNodeController::CreateConstNodeHandle(InitParams);
}
break;
default:
checkNoEntry();
}
}
return FInvalidNodeController::GetInvalid();
}
FMetasoundFrontendClassInput* FGraphController::FindInputDescriptionWithName(const FString& InName)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->Interface.Inputs.FindByPredicate([&](const FMetasoundFrontendClassInput& Desc) { return Desc.Name == InName; });
}
return nullptr;
}
const FMetasoundFrontendClassInput* FGraphController::FindInputDescriptionWithName(const FString& InName) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->Interface.Inputs.FindByPredicate([&](const FMetasoundFrontendClassInput& Desc) { return Desc.Name == InName; });
}
return nullptr;
}
FMetasoundFrontendClassOutput* FGraphController::FindOutputDescriptionWithName(const FString& InName)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->Interface.Outputs.FindByPredicate([&](const FMetasoundFrontendClassOutput& Desc) { return Desc.Name == InName; });
}
return nullptr;
}
const FMetasoundFrontendClassOutput* FGraphController::FindOutputDescriptionWithName(const FString& InName) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->Interface.Outputs.FindByPredicate([&](const FMetasoundFrontendClassOutput& Desc) { return Desc.Name == InName; });
}
return nullptr;
}
FMetasoundFrontendClassInput* FGraphController::FindInputDescriptionWithVertexID(const FGuid& InVertexID)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->Interface.Inputs.FindByPredicate([&](const FMetasoundFrontendClassInput& Desc) { return Desc.VertexID == InVertexID; });
}
return nullptr;
}
const FMetasoundFrontendClassInput* FGraphController::FindInputDescriptionWithVertexID(const FGuid& InVertexID) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->Interface.Inputs.FindByPredicate([&](const FMetasoundFrontendClassInput& Desc) { return Desc.VertexID == InVertexID; });
}
return nullptr;
}
FMetasoundFrontendClassOutput* FGraphController::FindOutputDescriptionWithVertexID(const FGuid& InVertexID)
{
if (FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->Interface.Outputs.FindByPredicate([&](const FMetasoundFrontendClassOutput& Desc) { return Desc.VertexID == InVertexID; });
}
return nullptr;
}
const FMetasoundFrontendClassOutput* FGraphController::FindOutputDescriptionWithVertexID(const FGuid& InVertexID) const
{
if (const FMetasoundFrontendGraphClass* GraphClass = GraphClassPtr.Get())
{
return GraphClass->Interface.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;
}
//
// FDocumentController
//
FDocumentController::FDocumentController(FDocumentAccessPtr InDocumentPtr)
: DocumentPtr(InDocumentPtr)
{
}
bool FDocumentController::IsValid() const
{
return (nullptr != DocumentPtr.Get());
}
TArray<FMetasoundFrontendClass> FDocumentController::GetDependencies() const
{
if (const FMetasoundFrontendDocument* Document = DocumentPtr.Get())
{
return Document->Dependencies;
}
return TArray<FMetasoundFrontendClass>();
}
TArray<FMetasoundFrontendGraphClass> FDocumentController::GetSubgraphs() const
{
if (const FMetasoundFrontendDocument* Document = DocumentPtr.Get())
{
return Document->Subgraphs;
}
return TArray<FMetasoundFrontendGraphClass>();
}
TArray<FMetasoundFrontendClass> FDocumentController::GetClasses() const
{
TArray<FMetasoundFrontendClass> Classes = GetDependencies();
Classes.Append(GetSubgraphs());
return Classes;
}
const FMetasoundFrontendGraphClass& FDocumentController::GetRootGraphClass() const
{
if (const FMetasoundFrontendDocument* Doc = DocumentPtr.Get())
{
return Doc->RootGraph;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FMetasoundFrontendGraphClass>();
}
bool FDocumentController::AddDuplicateSubgraph(const FMetasoundFrontendGraphClass& InGraphToCopy, const FMetasoundFrontendDocument& InOtherDocument)
{
if (FMetasoundFrontendDocument* Document = DocumentPtr.Get())
{
// Direct copy of subgraph
bool bSuccess = true;
FMetasoundFrontendGraphClass SubgraphCopy(InGraphToCopy);
for (FMetasoundFrontendNode& Node : SubgraphCopy.Graph.Nodes)
{
const FGuid OriginalClassID = Node.ClassID;
auto IsClassWithClassID = [&](const FMetasoundFrontendClass& InClass) -> bool
{
return InClass.ID == OriginalClassID;
};
if (const FMetasoundFrontendClass* OriginalNodeClass = InOtherDocument.Dependencies.FindByPredicate(IsClassWithClassID))
{
// Should not be a graph class since it's in the dependencies list
check(EMetasoundFrontendClassType::Graph != OriginalNodeClass->Metadata.Type);
if (const FMetasoundFrontendClass* NodeClass = FindOrAddClass(OriginalNodeClass->Metadata).Get())
{
// All this just to update this ID. Maybe having globally
// consistent class IDs would help. Or using the classname & version as
// a class ID.
Node.ClassID = NodeClass->ID;
}
else
{
UE_LOG(LogMetaSound, Error, TEXT("Failed to add subgraph dependency [Class:%s]"), *OriginalNodeClass->Metadata.ClassName.ToString());
bSuccess = false;
}
}
else if (const FMetasoundFrontendGraphClass* OriginalNodeGraphClass = InOtherDocument.Subgraphs.FindByPredicate(IsClassWithClassID))
{
bSuccess = bSuccess && AddDuplicateSubgraph(*OriginalNodeGraphClass, InOtherDocument);
if (!bSuccess)
{
break;
}
}
else
{
bSuccess = false;
UE_LOG(LogMetaSound, Error, TEXT("Failed to copy subgraph. Subgraph document is missing dependency info for node [Node:%s, NodeID:%s]"), *Node.Name, *Node.ID.ToString());
}
}
if (bSuccess)
{
Document->Subgraphs.Add(SubgraphCopy);
}
return bSuccess;
}
return false;
}
const FMetasoundFrontendVersion& FDocumentController::GetArchetypeVersion() const
{
if (const FMetasoundFrontendDocument* Doc = DocumentPtr.Get())
{
return Doc->ArchetypeVersion;
}
return FMetasoundFrontendVersion::GetInvalid();
}
void FDocumentController::SetArchetypeVersion(const FMetasoundFrontendVersion& InVersion)
{
if (FMetasoundFrontendDocument* Doc = DocumentPtr.Get())
{
Doc->ArchetypeVersion = InVersion;
}
}
FGraphHandle FDocumentController::AddDuplicateSubgraph(const IGraphController& InGraph)
{
// TODO: class IDs have issues..
// Currently ClassIDs are just used for internal linking. They need to be fixed up
// here if swapping documents. In the future, ClassIDs should be unique and consistent
// across documents and platforms.
FConstDocumentAccess GraphDocumentAccess = GetSharedAccess(*InGraph.GetOwningDocument());
const FMetasoundFrontendDocument* OtherDocument = GraphDocumentAccess.ConstDocument.Get();
if (nullptr == OtherDocument)
{
UE_LOG(LogMetaSound, Error, TEXT("Cannot add subgraph from invalid document"));
return IGraphController::GetInvalidHandle();
}
FConstDocumentAccess GraphAccess = GetSharedAccess(InGraph);
const FMetasoundFrontendGraphClass* OtherGraph = GraphAccess.ConstGraphClass.Get();
if (nullptr == OtherGraph)
{
UE_LOG(LogMetaSound, Error, TEXT("Cannot add invalid subgraph to document"));
return IGraphController::GetInvalidHandle();
}
if (AddDuplicateSubgraph(*OtherGraph, *OtherDocument))
{
if (const FMetasoundFrontendClass* SubgraphClass = FindClass(OtherGraph->Metadata).Get())
{
return GetSubgraphWithClassID(SubgraphClass->ID);
}
}
return IGraphController::GetInvalidHandle();
}
FConstClassAccessPtr FDocumentController::FindDependencyWithID(FGuid InClassID) const
{
return DocumentPtr.GetDependencyWithID(InClassID);
}
FConstGraphClassAccessPtr FDocumentController::FindSubgraphWithID(FGuid InClassID) const
{
return DocumentPtr.GetSubgraphWithID(InClassID);
}
FConstClassAccessPtr FDocumentController::FindClassWithID(FGuid InClassID) const
{
FConstClassAccessPtr MetasoundClass = FindDependencyWithID(InClassID);
if (nullptr == MetasoundClass.Get())
{
MetasoundClass = FindSubgraphWithID(InClassID);
}
return MetasoundClass;
}
void FDocumentController::SetMetadata(const FMetasoundFrontendDocumentMetadata& InMetadata)
{
if (FMetasoundFrontendDocument* Document = DocumentPtr.Get())
{
Document->Metadata = InMetadata;
}
}
const FMetasoundFrontendDocumentMetadata& FDocumentController::GetMetadata() const
{
if (const FMetasoundFrontendDocument* Document = DocumentPtr.Get())
{
return Document->Metadata;
}
return FrontendControllerIntrinsics::GetInvalidValueConstRef<FMetasoundFrontendDocumentMetadata>();
}
FConstClassAccessPtr FDocumentController::FindClass(const FNodeRegistryKey& InKey) const
{
return DocumentPtr.GetClassWithRegistryKey(InKey);
}
FConstClassAccessPtr FDocumentController::FindOrAddClass(const FNodeRegistryKey& InKey)
{
if (FMetasoundFrontendDocument* Document = DocumentPtr.Get())
{
FConstClassAccessPtr ClassPtr = FindClass(InKey);
auto AddClass = [=](FMetasoundFrontendClass&& NewClassDescription)
{
FConstClassAccessPtr NewClassPtr;
// Cannot add a subgraph using this method because dependencies
// of external graph are not added in this method.
check(EMetasoundFrontendClassType::Graph != NewClassDescription.Metadata.Type);
NewClassDescription.ID = FGuid::NewGuid();
Document->Dependencies.Add(MoveTemp(NewClassDescription));
NewClassPtr = FindClass(InKey);
return NewClassPtr;
};
if (const FMetasoundFrontendClass* MetasoundClass = ClassPtr.Get())
{
// External node classes must match version to return shared definition.
if (MetasoundClass->Metadata.Type == EMetasoundFrontendClassType::External)
{
// TODO: Assuming we want to recheck classes when they add another
// node, this should be replace with a call to synchronize a
// single class.
FMetasoundFrontendClass NewClass = GenerateClassDescription(InKey);
if (NewClass.Metadata.Version.Major != MetasoundClass->Metadata.Version.Major)
{
return AddClass(MoveTemp(NewClass));
}
}
return ClassPtr;
}
FMetasoundFrontendClass NewClass = GenerateClassDescription(InKey);
return AddClass(MoveTemp(NewClass));
}
return FConstClassAccessPtr();
}
FConstClassAccessPtr FDocumentController::FindClass(const FMetasoundFrontendClassMetadata& InMetadata) const
{
return DocumentPtr.GetClassWithMetadata(InMetadata);
}
FConstClassAccessPtr FDocumentController::FindOrAddClass(const FMetasoundFrontendClassMetadata& InMetadata)
{
FConstClassAccessPtr ClassPtr = FindClass(InMetadata);
if (const FMetasoundFrontendClass* Class = ClassPtr.Get())
{
// External node classes must match major version to return shared definition.
if (EMetasoundFrontendClassType::External == InMetadata.Type)
{
if (InMetadata.Version.Major != Class->Metadata.Version.Major)
{
// Mismatched major version. Reset class pointer to null.
ClassPtr = FConstClassAccessPtr();
}
}
}
const bool bNoMatchingClassFoundInDocument = (nullptr == ClassPtr.Get());
if (bNoMatchingClassFoundInDocument)
{
// If no matching class found, attempt to add a class matching the metadata.
if (FMetasoundFrontendDocument* Document = DocumentPtr.Get())
{
switch (InMetadata.Type)
{
case EMetasoundFrontendClassType::External:
case EMetasoundFrontendClassType::Input:
case EMetasoundFrontendClassType::Output:
{
FMetasoundFrontendClass NewClass;
FNodeRegistryKey Key = NodeRegistryKey::CreateKey(InMetadata);
if (FRegistry::GetFrontendClassFromRegistered(Key, NewClass))
{
NewClass.ID = FGuid::NewGuid();
Document->Dependencies.Add(NewClass);
}
else
{
UE_LOG(LogMetaSound, Error,
TEXT("Cannot add external dependency. No Metasound class found with matching registry key [Key:%s, Name:%s, Version:%s]. Suggested solution \"%s\" by %s."),
*Key,
*InMetadata.ClassName.GetFullName().ToString(),
*InMetadata.Version.ToString(),
*InMetadata.PromptIfMissing.ToString(),
*InMetadata.Author.ToString());
}
}
break;
case EMetasoundFrontendClassType::Graph:
{
FMetasoundFrontendGraphClass NewClass;
NewClass.ID = FGuid::NewGuid();
NewClass.Metadata = InMetadata;
Document->Subgraphs.Add(NewClass);
}
break;
default:
{
UE_LOG(LogMetaSound, Error, TEXT(
"Unsupported metasound class type for node: \"%s\" (%s)."),
*InMetadata.ClassName.GetFullName().ToString(),
*InMetadata.Version.ToString());
checkNoEntry();
}
}
ClassPtr = FindClass(InMetadata);
}
}
return ClassPtr;
}
void FDocumentController::SynchronizeDependencies()
{
if (FMetasoundFrontendDocument* Document = DocumentPtr.Get())
{
int32 NumDependenciesRemovedThisItr = 0;
// Repeatedly remove unreferenced dependencies until there are
// no unreferenced dependencies left.
do
{
TSet<FGuid> ReferencedDependencyIDs;
auto AddNodeClassIDToSet = [&](const FMetasoundFrontendNode& Node)
{
ReferencedDependencyIDs.Add(Node.ClassID);
};
auto AddGraphNodeClassIDsToSet = [&](const FMetasoundFrontendGraphClass& GraphClass)
{
Algo::ForEach(GraphClass.Graph.Nodes, AddNodeClassIDToSet);
};
// Referenced dependencies in root class
Algo::ForEach(Document->RootGraph.Graph.Nodes, AddNodeClassIDToSet);
// Referenced dependencies in subgraphs
Algo::ForEach(Document->Subgraphs, AddGraphNodeClassIDsToSet);
auto IsDependencyUnreferenced = [&](const FMetasoundFrontendClass& ClassDependency)
{
return !ReferencedDependencyIDs.Contains(ClassDependency.ID);
};
NumDependenciesRemovedThisItr = Document->Dependencies.RemoveAllSwap(IsDependencyUnreferenced);
}
while (NumDependenciesRemovedThisItr > 0);
/*
FRegistry* Registry = FRegistry::Get();
if (ensure(Registry))
{
// Make sure classes are up-to-date with registered versions of class
for (FMetasoundFrontendClass& Class : DocumentPtr->Dependencies)
{
FMetasoundFrontendClass RegisteredClass;
FNodeRegistryKey Key = FRegistry::GetRegistryKey(Class.Metadata);
if (Registry->FindFrontendClassFromRegistered(Key, RegisteredClass))
{
Class = RegisteredClass;
// TODO: update nodes for class.
// TODO: perform minor version updating.
}
}
}
*/
}
}
FGraphHandle FDocumentController::GetRootGraph()
{
if (IsValid())
{
FGraphClassAccessPtr GraphClass = DocumentPtr.GetRootGraph();
return FGraphController::CreateGraphHandle(FGraphController::FInitParams{GraphClass, this->AsShared()});
}
return FInvalidGraphController::GetInvalid();
}
FConstGraphHandle FDocumentController::GetRootGraph() const
{
if (IsValid())
{
FConstGraphClassAccessPtr GraphClass = DocumentPtr.GetRootGraph();
return FGraphController::CreateConstGraphHandle(FGraphController::FInitParams
{
ConstCastAccessPtr<FGraphClassAccessPtr>(GraphClass),
ConstCastSharedRef<IDocumentController>(this->AsShared())
});
}
return FInvalidGraphController::GetInvalid();
}
TArray<FGraphHandle> FDocumentController::GetSubgraphHandles()
{
TArray<FGraphHandle> Subgraphs;
if (FMetasoundFrontendDocument* Document = DocumentPtr.Get())
{
for (FMetasoundFrontendGraphClass& GraphClass : Document->Subgraphs)
{
Subgraphs.Add(GetSubgraphWithClassID(GraphClass.ID));
}
}
return Subgraphs;
}
TArray<FConstGraphHandle> FDocumentController::GetSubgraphHandles() const
{
TArray<FConstGraphHandle> Subgraphs;
if (const FMetasoundFrontendDocument* Document = DocumentPtr.Get())
{
for (const FMetasoundFrontendGraphClass& GraphClass : Document->Subgraphs)
{
Subgraphs.Add(GetSubgraphWithClassID(GraphClass.ID));
}
}
return Subgraphs;
}
FGraphHandle FDocumentController::GetSubgraphWithClassID(FGuid InClassID)
{
FGraphClassAccessPtr GraphClassPtr = DocumentPtr.GetSubgraphWithID(InClassID);
return FGraphController::CreateGraphHandle(FGraphController::FInitParams{GraphClassPtr, this->AsShared()});
}
FConstGraphHandle FDocumentController::GetSubgraphWithClassID(FGuid InClassID) const
{
FConstGraphClassAccessPtr GraphClassPtr = DocumentPtr.GetSubgraphWithID(InClassID);
return FGraphController::CreateConstGraphHandle(FGraphController::FInitParams{ConstCastAccessPtr<FGraphClassAccessPtr>(GraphClassPtr), ConstCastSharedRef<IDocumentController>(this->AsShared())});
}
bool FDocumentController::ExportToJSONAsset(const FString& InAbsolutePath) const
{
if (const FMetasoundFrontendDocument* Document = DocumentPtr.Get())
{
if (TUniquePtr<FArchive> FileWriter = TUniquePtr<FArchive>(IFileManager::Get().CreateFileWriter(*InAbsolutePath)))
{
TJsonStructSerializerBackend<DefaultCharType> Backend(*FileWriter, EStructSerializerBackendFlags::Default);
FStructSerializer::Serialize<FMetasoundFrontendDocument>(*Document, Backend);
FileWriter->Close();
return true;
}
else
{
UE_LOG(LogMetaSound, Error, TEXT("Failed to export Metasound json asset. Could not write to path \"%s\"."), *InAbsolutePath);
}
}
return false;
}
FString FDocumentController::ExportToJSON() const
{
FString Output;
if (const FMetasoundFrontendDocument* Document = DocumentPtr.Get())
{
TArray<uint8> WriterBuffer;
FMemoryWriter MemWriter(WriterBuffer);
Metasound::TJsonStructSerializerBackend<Metasound::DefaultCharType> Backend(MemWriter, EStructSerializerBackendFlags::Default);
FStructSerializer::Serialize<FMetasoundFrontendDocument>(*Document, Backend);
MemWriter.Close();
// null terminator
WriterBuffer.AddZeroed(sizeof(ANSICHAR));
Output.AppendChars(reinterpret_cast<ANSICHAR*>(WriterBuffer.GetData()), WriterBuffer.Num() / sizeof(ANSICHAR));
}
return Output;
}
FDocumentAccess FDocumentController::ShareAccess()
{
FDocumentAccess Access;
Access.Document = DocumentPtr;
Access.ConstDocument = DocumentPtr;
return Access;
}
FConstDocumentAccess FDocumentController::ShareAccess() const
{
FConstDocumentAccess Access;
Access.ConstDocument = DocumentPtr;
return Access;
}
}
}
#undef LOCTEXT_NAMESPACE