2020-12-04 11:19:17 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "MetasoundFrontendGraph.h"
# include "Algo/AllOf.h"
# include "Algo/AnyOf.h"
2021-05-20 19:33:21 -04:00
# include "Algo/TopologicalSort.h"
2020-12-04 11:19:17 -04:00
# include "Algo/Transform.h"
# include "CoreMinimal.h"
# include "MetasoundFrontend.h"
# include "MetasoundGraph.h"
2021-01-13 10:48:59 -04:00
# include "MetasoundLog.h"
2020-12-04 11:19:17 -04:00
# include "MetasoundNodeInterface.h"
namespace Metasound
{
2021-05-28 14:09:45 -04:00
namespace FrontendGraphPrivate
{
FNodeInitData CreateNodeInitData ( const FMetasoundFrontendNode & InNode )
{
FNodeInitData InitData ;
InitData . InstanceName . Append ( InNode . Name ) ;
InitData . InstanceName . AppendChar ( ' _ ' ) ;
InitData . InstanceName . Append ( InNode . ID . ToString ( ) ) ;
InitData . InstanceID = InNode . ID ;
return InitData ;
}
}
2021-05-20 19:33:21 -04:00
2021-01-28 19:02:51 -04:00
FFrontendGraph : : FFrontendGraph ( const FString & InInstanceName , const FGuid & InInstanceID )
: FGraph ( InInstanceName , InInstanceID )
2020-12-04 11:19:17 -04:00
{
}
2021-05-20 19:33:21 -04:00
void FFrontendGraph : : AddInputNode ( FGuid InDependencyId , int32 InIndex , const FVertexKey & InVertexKey , TSharedPtr < const INode > InNode )
2020-12-04 11:19:17 -04:00
{
if ( InNode . IsValid ( ) )
{
// There shouldn't be duplicate IDs.
check ( ! InputNodes . Contains ( InIndex ) ) ;
// Input nodes need an extra Index value to keep track of their position in the graph's inputs.
InputNodes . Add ( InIndex , InNode . Get ( ) ) ;
AddInputDataDestination ( * InNode , InVertexKey ) ;
2021-05-20 19:33:21 -04:00
AddNode ( InDependencyId , InNode ) ;
2020-12-04 11:19:17 -04:00
}
}
2021-05-20 19:33:21 -04:00
void FFrontendGraph : : AddOutputNode ( FGuid InNodeID , int32 InIndex , const FVertexKey & InVertexKey , TSharedPtr < const INode > InNode )
2020-12-04 11:19:17 -04:00
{
if ( InNode . IsValid ( ) )
{
// There shouldn't be duplicate IDs.
check ( ! OutputNodes . Contains ( InIndex ) ) ;
// Output nodes need an extra Index value to keep track of their position in the graph's inputs.
OutputNodes . Add ( InIndex , InNode . Get ( ) ) ;
AddOutputDataSource ( * InNode , InVertexKey ) ;
2021-05-20 19:33:21 -04:00
AddNode ( InNodeID , InNode ) ;
2020-12-04 11:19:17 -04:00
}
}
/** Store a node on this graph. */
2021-05-20 19:33:21 -04:00
void FFrontendGraph : : AddNode ( FGuid InNodeID , TSharedPtr < const INode > InNode )
2020-12-04 11:19:17 -04:00
{
if ( InNode . IsValid ( ) )
{
// There shouldn't be duplicate IDs.
check ( ! NodeMap . Contains ( InNodeID ) ) ;
NodeMap . Add ( InNodeID , InNode . Get ( ) ) ;
2021-05-20 19:33:21 -04:00
StoreNode ( InNode ) ;
2020-12-04 11:19:17 -04:00
}
}
2021-01-20 17:26:40 -04:00
const INode * FFrontendGraph : : FindNode ( FGuid InNodeID ) const
2020-12-04 11:19:17 -04:00
{
2021-05-20 19:33:21 -04:00
const INode * const * NodePtr = NodeMap . Find ( InNodeID ) ;
2020-12-04 11:19:17 -04:00
if ( nullptr ! = NodePtr )
{
return * NodePtr ;
}
return nullptr ;
}
const INode * FFrontendGraph : : FindInputNode ( int32 InIndex ) const
{
2021-05-20 19:33:21 -04:00
const INode * const * NodePtr = InputNodes . Find ( InIndex ) ;
2020-12-04 11:19:17 -04:00
if ( nullptr ! = NodePtr )
{
return * NodePtr ;
}
return nullptr ;
}
const INode * FFrontendGraph : : FindOutputNode ( int32 InIndex ) const
{
2021-05-20 19:33:21 -04:00
const INode * const * NodePtr = OutputNodes . Find ( InIndex ) ;
2020-12-04 11:19:17 -04:00
if ( nullptr ! = NodePtr )
{
return * NodePtr ;
}
return nullptr ;
}
/** Returns true if all edges, destinations and sources refer to
* nodes stored in this graph . */
bool FFrontendGraph : : OwnsAllReferencedNodes ( ) const
{
const TArray < FDataEdge > & AllEdges = GetDataEdges ( ) ;
for ( const FDataEdge & Edge : AllEdges )
{
if ( ! StoredNodes . Contains ( Edge . From . Node ) )
{
return false ;
}
if ( ! StoredNodes . Contains ( Edge . To . Node ) )
{
return false ;
}
}
const FInputDataDestinationCollection & AllInputDestinations = GetInputDataDestinations ( ) ;
for ( auto & DestTuple : AllInputDestinations )
{
if ( ! StoredNodes . Contains ( DestTuple . Value . Node ) )
{
return false ;
}
}
const FOutputDataSourceCollection & AllOutputSources = GetOutputDataSources ( ) ;
for ( auto & SourceTuple : AllOutputSources )
{
if ( ! StoredNodes . Contains ( SourceTuple . Value . Node ) )
{
return false ;
}
}
return true ;
}
2021-05-20 19:33:21 -04:00
void FFrontendGraph : : StoreNode ( TSharedPtr < const INode > InNode )
2020-12-04 11:19:17 -04:00
{
check ( InNode . IsValid ( ) ) ;
StoredNodes . Add ( InNode . Get ( ) ) ;
2021-05-20 19:33:21 -04:00
NodeStorage . Add ( InNode ) ;
2020-12-04 11:19:17 -04:00
}
2021-05-28 14:09:45 -04:00
TUniquePtr < INode > FFrontendGraphBuilder : : CreateInputNode ( const FMetasoundFrontendNode & InNode , const FMetasoundFrontendClass & InClass , const FMetasoundFrontendClassInput & InOwningGraphClassInput )
2020-12-04 11:19:17 -04:00
{
2021-02-24 18:37:19 -04:00
const FMetasoundFrontendLiteral * FrontendLiteral = FindInputLiteralForInputNode ( InNode , InClass , InOwningGraphClassInput ) ;
2021-01-13 10:48:59 -04:00
2021-02-24 18:37:19 -04:00
if ( nullptr ! = FrontendLiteral )
2020-12-04 11:19:17 -04:00
{
2021-01-13 10:48:59 -04:00
if ( ensure ( InNode . Interface . Inputs . Num ( ) = = 1 ) )
{
const FMetasoundFrontendVertex & InputVertex = InNode . Interface . Inputs [ 0 ] ;
2021-02-24 18:37:19 -04:00
const bool IsLiteralParsableByDataType = Frontend : : DoesDataTypeSupportLiteralType ( InputVertex . TypeName , FrontendLiteral - > GetType ( ) ) ;
2021-01-13 10:48:59 -04:00
if ( IsLiteralParsableByDataType )
{
2021-02-24 18:37:19 -04:00
FLiteral Literal = FrontendLiteral - > ToLiteral ( InputVertex . TypeName ) ;
2021-01-13 10:48:59 -04:00
FInputNodeConstructorParams InitParams =
{
InNode . Name ,
2021-01-28 19:02:51 -04:00
InNode . ID ,
2021-01-13 10:48:59 -04:00
InputVertex . Name ,
MoveTemp ( Literal )
} ;
// TODO: create input node using external class definition
2021-06-14 16:45:51 -04:00
return FMetasoundFrontendRegistryContainer : : Get ( ) - > CreateInputNode ( InputVertex . TypeName , MoveTemp ( InitParams ) ) ;
2021-01-13 10:48:59 -04:00
}
else
{
2021-04-02 03:03:27 -04:00
UE_LOG ( LogMetaSound , Error , TEXT ( " Cannot create input node [NodeID:%s]. [Vertex:%s] cannot be constructed with the provided literal type. " ) , * InNode . ID . ToString ( ) , * InputVertex . Name ) ;
2021-01-13 10:48:59 -04:00
}
}
}
else
{
2021-04-02 03:03:27 -04:00
UE_LOG ( LogMetaSound , Error , TEXT ( " Cannot create input node [NodeID:%s]. No default literal set for input node. " ) , * InNode . ID . ToString ( ) ) ;
2020-12-04 11:19:17 -04:00
}
2021-01-13 10:48:59 -04:00
return TUniquePtr < INode > ( nullptr ) ;
2020-12-04 11:19:17 -04:00
}
2021-05-28 14:09:45 -04:00
TUniquePtr < INode > FFrontendGraphBuilder : : CreateOutputNode ( const FMetasoundFrontendNode & InNode , const FMetasoundFrontendClass & InClass , FBuildGraphContext & InGraphContext )
2020-12-04 11:19:17 -04:00
{
2021-01-13 10:48:59 -04:00
check ( InClass . Metadata . Type = = EMetasoundFrontendClassType : : Output ) ;
check ( InNode . ClassID = = InClass . ID ) ;
2020-12-04 11:19:17 -04:00
2021-01-13 10:48:59 -04:00
if ( ensure ( InNode . Interface . Outputs . Num ( ) = = 1 ) )
{
const FMetasoundFrontendVertex & OutputVertex = InNode . Interface . Outputs [ 0 ] ;
FOutputNodeConstructorParams InitParams =
{
InNode . Name ,
2021-01-28 19:02:51 -04:00
InNode . ID ,
2021-05-28 14:09:45 -04:00
OutputVertex . Name
2021-01-13 10:48:59 -04:00
} ;
2021-05-28 14:09:45 -04:00
{
const FNodeInitData InitData = FrontendGraphPrivate : : CreateNodeInitData ( InNode ) ;
TArray < FDefaultVariableData > DefaultVariableData = GetInputDefaultVariableData ( InNode , InitData ) ;
for ( FDefaultVariableData & Data : DefaultVariableData )
{
2021-06-02 11:30:20 -04:00
InGraphContext . DefaultInputs . Emplace ( FNodeIDVertexID { InNode . ID , Data . DestinationVertexID } , MoveTemp ( Data ) ) ;
2021-05-28 14:09:45 -04:00
}
}
2021-01-13 10:48:59 -04:00
// TODO: create output node using external class definition
2021-06-14 16:45:51 -04:00
return FMetasoundFrontendRegistryContainer : : Get ( ) - > CreateOutputNode ( OutputVertex . TypeName , MoveTemp ( InitParams ) ) ;
2021-01-13 10:48:59 -04:00
}
return TUniquePtr < INode > ( nullptr ) ;
2020-12-04 11:19:17 -04:00
}
2021-05-28 14:09:45 -04:00
TUniquePtr < INode > FFrontendGraphBuilder : : CreateExternalNode ( const FMetasoundFrontendNode & InNode , const FMetasoundFrontendClass & InClass , FBuildGraphContext & InGraphContext )
2020-12-04 11:19:17 -04:00
{
2021-01-13 10:48:59 -04:00
check ( InClass . Metadata . Type = = EMetasoundFrontendClassType : : External ) ;
check ( InNode . ClassID = = InClass . ID ) ;
2020-12-04 11:19:17 -04:00
2021-05-28 14:09:45 -04:00
const FNodeInitData InitData = FrontendGraphPrivate : : CreateNodeInitData ( InNode ) ;
{
TArray < FDefaultVariableData > DefaultVariableData = GetInputDefaultVariableData ( InNode , InitData ) ;
for ( FDefaultVariableData & Data : DefaultVariableData )
{
2021-06-02 11:30:20 -04:00
InGraphContext . DefaultInputs . Emplace ( FNodeIDVertexID { InNode . ID , Data . DestinationVertexID } , MoveTemp ( Data ) ) ;
2021-05-28 14:09:45 -04:00
}
}
2021-01-28 19:02:51 -04:00
2021-01-13 10:48:59 -04:00
// TODO: handle check to see if node interface conforms to class interface here.
// TODO: check to see if external object supports class interface.
2020-12-04 11:19:17 -04:00
2021-01-13 10:48:59 -04:00
Metasound : : Frontend : : FNodeRegistryKey Key = FMetasoundFrontendRegistryContainer : : GetRegistryKey ( InClass . Metadata ) ;
2021-06-14 16:45:51 -04:00
return FMetasoundFrontendRegistryContainer : : Get ( ) - > CreateNode ( Key , InitData ) ;
2021-01-13 10:48:59 -04:00
}
2021-05-28 14:09:45 -04:00
const FMetasoundFrontendClassInput * FFrontendGraphBuilder : : FindClassInputForInputNode ( const FMetasoundFrontendGraphClass & InOwningGraph , const FMetasoundFrontendNode & InInputNode , int32 & OutClassInputIndex )
2021-01-13 10:48:59 -04:00
{
OutClassInputIndex = INDEX_NONE ;
2021-05-20 19:33:21 -04:00
// Input nodes should have exactly one input.
2021-01-13 10:48:59 -04:00
if ( ensure ( InInputNode . Interface . Inputs . Num ( ) = = 1 ) )
{
const FName & TypeName = InInputNode . Interface . Inputs [ 0 ] . TypeName ;
auto IsMatchingInput = [ & ] ( const FMetasoundFrontendClassInput & GraphInput )
{
return ( InInputNode . ID = = GraphInput . NodeID ) ;
} ;
OutClassInputIndex = InOwningGraph . Interface . Inputs . IndexOfByPredicate ( IsMatchingInput ) ;
if ( INDEX_NONE ! = OutClassInputIndex )
{
return & InOwningGraph . Interface . Inputs [ OutClassInputIndex ] ;
}
}
return nullptr ;
}
2021-05-28 14:09:45 -04:00
const FMetasoundFrontendClassOutput * FFrontendGraphBuilder : : FindClassOutputForOutputNode ( const FMetasoundFrontendGraphClass & InOwningGraph , const FMetasoundFrontendNode & InOutputNode , int32 & OutClassOutputIndex )
2021-01-13 10:48:59 -04:00
{
OutClassOutputIndex = INDEX_NONE ;
2021-05-20 19:33:21 -04:00
// Output nodes should have exactly one output
2021-01-13 10:48:59 -04:00
if ( ensure ( InOutputNode . Interface . Outputs . Num ( ) = = 1 ) )
{
const FName & TypeName = InOutputNode . Interface . Outputs [ 0 ] . TypeName ;
auto IsMatchingOutput = [ & ] ( const FMetasoundFrontendClassOutput & GraphOutput )
{
return ( InOutputNode . ID = = GraphOutput . NodeID ) ;
} ;
OutClassOutputIndex = InOwningGraph . Interface . Outputs . IndexOfByPredicate ( IsMatchingOutput ) ;
if ( INDEX_NONE ! = OutClassOutputIndex )
{
return & InOwningGraph . Interface . Outputs [ OutClassOutputIndex ] ;
}
}
return nullptr ;
}
2021-05-28 14:09:45 -04:00
const FMetasoundFrontendLiteral * FFrontendGraphBuilder : : FindInputLiteralForInputNode ( const FMetasoundFrontendNode & InInputNode , const FMetasoundFrontendClass & InInputNodeClass , const FMetasoundFrontendClassInput & InOwningGraphClassInput )
2021-01-13 10:48:59 -04:00
{
// Default value priority is:
// 1. A value set directly on the node
// 2. A default value of the owning graph
// 3. A default value on the input node class.
2021-02-24 18:37:19 -04:00
const FMetasoundFrontendLiteral * Literal = nullptr ;
2021-01-13 10:48:59 -04:00
// Check for default value directly on node.
if ( ensure ( InInputNode . Interface . Inputs . Num ( ) = = 1 ) )
{
const FMetasoundFrontendVertex & InputVertex = InInputNode . Interface . Inputs [ 0 ] ;
2021-02-24 18:37:19 -04:00
// Find input literal matching VerteXID
const FMetasoundFrontendVertexLiteral * VertexLiteral = InInputNode . InputLiterals . FindByPredicate (
[ & ] ( const FMetasoundFrontendVertexLiteral & InVertexLiteral )
{
return InVertexLiteral . VertexID = = InputVertex . VertexID ;
}
) ;
if ( nullptr ! = VertexLiteral )
2021-01-13 10:48:59 -04:00
{
2021-02-24 18:37:19 -04:00
Literal = & VertexLiteral - > Value ;
2021-01-13 10:48:59 -04:00
}
}
// Check for default value on owning graph.
2021-02-24 18:37:19 -04:00
if ( nullptr = = Literal )
2021-01-13 10:48:59 -04:00
{
2021-02-24 18:37:19 -04:00
// Find Class Default that is not invalid
if ( InOwningGraphClassInput . DefaultLiteral . IsValid ( ) )
2021-01-13 10:48:59 -04:00
{
2021-02-24 18:37:19 -04:00
Literal = & InOwningGraphClassInput . DefaultLiteral ;
2021-01-13 10:48:59 -04:00
}
}
// Check for default value on input node class
2021-02-24 18:37:19 -04:00
if ( nullptr = = Literal & & ensure ( InInputNodeClass . Interface . Inputs . Num ( ) = = 1 ) )
2021-01-13 10:48:59 -04:00
{
const FMetasoundFrontendClassInput & InputNodeClassInput = InInputNodeClass . Interface . Inputs [ 0 ] ;
2021-02-24 18:37:19 -04:00
if ( InputNodeClassInput . DefaultLiteral . IsValid ( ) )
2021-01-13 10:48:59 -04:00
{
2021-02-24 18:37:19 -04:00
Literal = & InputNodeClassInput . DefaultLiteral ;
2021-01-13 10:48:59 -04:00
}
}
2021-02-24 18:37:19 -04:00
return Literal ;
2020-12-04 11:19:17 -04:00
}
// TODO: add errors here. Most will be a "PromptIfMissing"...
2021-05-28 14:09:45 -04:00
void FFrontendGraphBuilder : : AddNodesToGraph ( FBuildGraphContext & InGraphContext )
2020-12-04 11:19:17 -04:00
{
2021-05-28 14:09:45 -04:00
for ( const FMetasoundFrontendNode & Node : InGraphContext . GraphClass . Graph . Nodes )
2020-12-04 11:19:17 -04:00
{
2021-05-28 14:09:45 -04:00
const FMetasoundFrontendClass * NodeClass = InGraphContext . BuildContext . FrontendClasses . FindRef ( Node . ClassID ) ;
2020-12-04 11:19:17 -04:00
2021-01-13 10:48:59 -04:00
if ( ensure ( nullptr ! = NodeClass ) )
{
switch ( NodeClass - > Metadata . Type )
2020-12-04 11:19:17 -04:00
{
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendClassType : : Input :
2021-05-28 14:09:45 -04:00
{
int32 InputIndex = INDEX_NONE ;
const FMetasoundFrontendClassInput * ClassInput = FindClassInputForInputNode ( InGraphContext . GraphClass , Node , InputIndex ) ;
if ( ( nullptr ! = ClassInput ) & & ( INDEX_NONE ! = InputIndex ) )
2021-01-13 10:48:59 -04:00
{
2021-05-28 14:09:45 -04:00
TSharedPtr < const INode > InputNode ( CreateInputNode ( Node , * NodeClass , * ClassInput ) . Release ( ) ) ;
InGraphContext . Graph - > AddInputNode ( Node . ID , InputIndex , ClassInput - > Name , InputNode ) ;
2021-01-13 10:48:59 -04:00
}
2021-05-28 14:09:45 -04:00
else
{
const FString GraphClassIDString = InGraphContext . GraphClass . ID . ToString ( ) ;
UE_LOG ( LogMetaSound , Error , TEXT ( " Failed to match input node [NodeID:%s, NodeName:%s] to owning graph [ClassID:%s] output. " ) , * Node . ID . ToString ( ) , * Node . Name , * GraphClassIDString ) ;
}
}
break ;
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendClassType : : Output :
2021-05-28 14:09:45 -04:00
{
int32 OutputIndex = INDEX_NONE ;
const FMetasoundFrontendClassOutput * ClassOutput = FindClassOutputForOutputNode ( InGraphContext . GraphClass , Node , OutputIndex ) ;
if ( ( nullptr ! = ClassOutput ) & & ( INDEX_NONE ! = OutputIndex ) )
2021-01-13 10:48:59 -04:00
{
2021-05-28 14:09:45 -04:00
TSharedPtr < const INode > OutputNode ( CreateOutputNode ( Node , * NodeClass , InGraphContext ) . Release ( ) ) ;
InGraphContext . Graph - > AddOutputNode ( Node . ID , OutputIndex , ClassOutput - > Name , OutputNode ) ;
2021-01-13 10:48:59 -04:00
}
2021-05-28 14:09:45 -04:00
else
{
const FString GraphClassIDString = InGraphContext . GraphClass . ID . ToString ( ) ;
UE_LOG ( LogMetaSound , Error , TEXT ( " Failed to match output node [NodeID:%s, NodeName:%s] to owning graph [ClassID:%s] output. " ) , * Node . ID . ToString ( ) , * Node . Name , * GraphClassIDString ) ;
}
}
break ;
2021-01-13 10:48:59 -04:00
2021-05-28 14:09:45 -04:00
case EMetasoundFrontendClassType : : Variable :
{
ensureAlwaysMsgf ( false , TEXT ( " TODO: Implement ability to create variables directly in Frontend " ) ) ;
}
break ;
2021-01-13 10:48:59 -04:00
2021-05-20 19:33:21 -04:00
case EMetasoundFrontendClassType : : Graph :
2021-05-28 14:09:45 -04:00
{
const TSharedPtr < const INode > SubgraphPtr = InGraphContext . BuildContext . Graphs . FindRef ( Node . ClassID ) ;
2021-05-20 19:33:21 -04:00
2021-05-28 14:09:45 -04:00
if ( SubgraphPtr . IsValid ( ) )
{
InGraphContext . Graph - > AddNode ( Node . ID , SubgraphPtr ) ;
2021-05-20 19:33:21 -04:00
}
2021-05-28 14:09:45 -04:00
else
{
UE_LOG ( LogMetaSound , Error , TEXT ( " Failed to find subgraph for node [NodeID:%s, NodeName:%s, ClassID:%s] " ) , * Node . ID . ToString ( ) , * Node . Name , * Node . ClassID . ToString ( ) ) ;
}
}
break ;
2021-05-20 19:33:21 -04:00
case EMetasoundFrontendClassType : : External :
2021-01-13 10:48:59 -04:00
default :
2021-05-28 14:09:45 -04:00
{
TSharedPtr < const INode > ExternalNode ( CreateExternalNode ( Node , * NodeClass , InGraphContext ) . Release ( ) ) ;
InGraphContext . Graph - > AddNode ( Node . ID , ExternalNode ) ;
}
break ;
2020-12-04 11:19:17 -04:00
}
}
}
}
2021-05-28 14:09:45 -04:00
void FFrontendGraphBuilder : : AddEdgesToGraph ( FBuildGraphContext & InGraphContext )
2020-12-04 11:19:17 -04:00
{
2021-01-13 10:48:59 -04:00
// Pair of frontend node and core node. The frontend node can be one of
// several types.
struct FCoreNodeAndFrontendVertex
2020-12-04 11:19:17 -04:00
{
2021-01-13 10:48:59 -04:00
const INode * Node = nullptr ;
const FMetasoundFrontendVertex * Vertex = nullptr ;
} ;
2020-12-04 11:19:17 -04:00
2021-02-24 18:37:19 -04:00
TMap < FNodeIDVertexID , FCoreNodeAndFrontendVertex > NodeSourcesByID ;
TMap < FNodeIDVertexID , FCoreNodeAndFrontendVertex > NodeDestinationsByID ;
2021-01-13 10:48:59 -04:00
2021-02-24 18:37:19 -04:00
// Add nodes to NodeID/VertexID map
2021-05-28 14:09:45 -04:00
for ( const FMetasoundFrontendNode & Node : InGraphContext . GraphClass . Graph . Nodes )
2021-01-13 10:48:59 -04:00
{
2021-05-28 14:09:45 -04:00
const INode * CoreNode = InGraphContext . Graph - > FindNode ( Node . ID ) ;
2021-01-13 10:48:59 -04:00
if ( nullptr = = CoreNode )
2020-12-04 11:19:17 -04:00
{
2021-05-28 14:09:45 -04:00
UE_LOG ( LogMetaSound , Warning , TEXT ( " Could not find referenced node [Name:%s, NodeID:%s] " ) , * Node . Name , * Node . ID . ToString ( ) ) ;
2020-12-04 11:19:17 -04:00
continue ;
}
2021-01-13 10:48:59 -04:00
for ( const FMetasoundFrontendVertex & Vertex : Node . Interface . Inputs )
2020-12-04 11:19:17 -04:00
{
2021-02-24 18:37:19 -04:00
NodeDestinationsByID . Add ( FNodeIDVertexID ( Node . ID , Vertex . VertexID ) , FCoreNodeAndFrontendVertex ( { CoreNode , & Vertex } ) ) ;
2021-01-13 10:48:59 -04:00
}
2020-12-04 11:19:17 -04:00
2021-01-13 10:48:59 -04:00
for ( const FMetasoundFrontendVertex & Vertex : Node . Interface . Outputs )
{
2021-02-24 18:37:19 -04:00
NodeSourcesByID . Add ( FNodeIDVertexID ( Node . ID , Vertex . VertexID ) , FCoreNodeAndFrontendVertex ( { CoreNode , & Vertex } ) ) ;
2020-12-04 11:19:17 -04:00
}
2021-01-13 10:48:59 -04:00
} ;
2020-12-04 11:19:17 -04:00
2021-05-28 14:09:45 -04:00
for ( const FMetasoundFrontendEdge & Edge : InGraphContext . GraphClass . Graph . Edges )
2020-12-04 11:19:17 -04:00
{
2021-02-24 18:37:19 -04:00
const FNodeIDVertexID DestinationKey ( Edge . ToNodeID , Edge . ToVertexID ) ;
2021-01-13 10:48:59 -04:00
const FCoreNodeAndFrontendVertex * DestinationNodeAndVertex = NodeDestinationsByID . Find ( DestinationKey ) ;
2020-12-04 11:19:17 -04:00
2021-01-13 10:48:59 -04:00
if ( nullptr = = DestinationNodeAndVertex )
2020-12-04 11:19:17 -04:00
{
2021-01-13 10:48:59 -04:00
// TODO: bubble up error
2021-04-02 03:03:27 -04:00
UE_LOG ( LogMetaSound , Error , TEXT ( " Failed to add edge. Could not find destination [NodeID:%s, VertexID:%s] " ) , * Edge . ToNodeID . ToString ( ) , * Edge . ToVertexID . ToString ( ) ) ;
2020-12-04 11:19:17 -04:00
continue ;
}
2021-01-13 10:48:59 -04:00
if ( nullptr = = DestinationNodeAndVertex - > Node )
2020-12-04 11:19:17 -04:00
{
2021-01-13 10:48:59 -04:00
// TODO: bubble up error
2021-04-02 03:03:27 -04:00
UE_LOG ( LogMetaSound , Warning , TEXT ( " Skipping edge. Null destination node [NodeID:%s] " ) , * Edge . ToNodeID . ToString ( ) ) ;
2020-12-04 11:19:17 -04:00
continue ;
}
2021-02-24 18:37:19 -04:00
const FNodeIDVertexID SourceKey ( Edge . FromNodeID , Edge . FromVertexID ) ;
2021-01-13 10:48:59 -04:00
const FCoreNodeAndFrontendVertex * SourceNodeAndVertex = NodeSourcesByID . Find ( SourceKey ) ;
2020-12-04 11:19:17 -04:00
2021-01-13 10:48:59 -04:00
if ( nullptr = = SourceNodeAndVertex )
2020-12-04 11:19:17 -04:00
{
2021-04-02 03:03:27 -04:00
UE_LOG ( LogMetaSound , Error , TEXT ( " Failed to add edge. Could not find source [NodeID:%s, VertexID:%s] " ) , * Edge . FromNodeID . ToString ( ) , * Edge . FromVertexID . ToString ( ) ) ;
2021-01-13 10:48:59 -04:00
continue ;
}
2020-12-04 11:19:17 -04:00
2021-01-13 10:48:59 -04:00
if ( nullptr = = SourceNodeAndVertex - > Node )
{
// TODO: bubble up error
2021-04-02 03:03:27 -04:00
UE_LOG ( LogMetaSound , Warning , TEXT ( " Skipping edge. Null source node [NodeID:%s] " ) , * Edge . FromNodeID . ToString ( ) ) ;
2021-01-13 10:48:59 -04:00
continue ;
}
2020-12-04 11:19:17 -04:00
2021-01-13 10:48:59 -04:00
const INode * FromNode = SourceNodeAndVertex - > Node ;
const FVertexKey FromVertexKey = SourceNodeAndVertex - > Vertex - > Name ;
2020-12-04 11:19:17 -04:00
2021-01-13 10:48:59 -04:00
const INode * ToNode = DestinationNodeAndVertex - > Node ;
const FVertexKey ToVertexKey = DestinationNodeAndVertex - > Vertex - > Name ;
2020-12-04 11:19:17 -04:00
2021-05-28 14:09:45 -04:00
bool bSuccess = InGraphContext . Graph - > AddDataEdge ( * FromNode , FromVertexKey , * ToNode , ToVertexKey ) ;
2020-12-04 11:19:17 -04:00
2021-05-28 14:09:45 -04:00
// If succeeded, remove input as viable vertex to construct default variable, as it has been superceded by a connection.
if ( bSuccess )
{
2021-06-02 11:30:20 -04:00
FNodeIDVertexID DestinationPair { ToNode - > GetInstanceID ( ) , DestinationNodeAndVertex - > Vertex - > VertexID } ;
InGraphContext . DefaultInputs . Remove ( DestinationPair ) ;
2021-05-28 14:09:45 -04:00
}
else
2021-01-13 10:48:59 -04:00
{
2021-04-02 03:03:27 -04:00
UE_LOG ( LogMetaSound , Error , TEXT ( " Failed to connect edge from [NodeID:%s, VertexID:%s] to [NodeID:%s, VertexID:%s] " ) , * Edge . FromNodeID . ToString ( ) , * Edge . FromVertexID . ToString ( ) , * Edge . ToNodeID . ToString ( ) , * Edge . ToVertexID . ToString ( ) ) ;
2020-12-04 11:19:17 -04:00
}
}
}
2021-05-28 14:09:45 -04:00
void FFrontendGraphBuilder : : AddDefaultInputVariables ( FBuildGraphContext & InGraphContext )
{
2021-06-02 11:30:20 -04:00
for ( const TPair < FNodeIDVertexID , FDefaultVariableData > & Pair : InGraphContext . DefaultInputs )
2021-05-28 14:09:45 -04:00
{
const FDefaultVariableData & VariableData = Pair . Value ;
const FGuid VariableNodeID = FGuid : : NewGuid ( ) ;
// 1. Construct and add the default variable to the graph
{
FVariableNodeConstructorParams InitParams = VariableData . InitParams . Clone ( ) ;
2021-06-14 16:45:51 -04:00
TUniquePtr < const INode > DefaultVariable = FMetasoundFrontendRegistryContainer : : Get ( ) - > CreateVariableNode ( VariableData . TypeName , MoveTemp ( InitParams ) ) ;
2021-05-28 14:09:45 -04:00
InGraphContext . Graph - > AddNode ( VariableNodeID , TSharedPtr < const INode > ( DefaultVariable . Release ( ) ) ) ;
}
// 2. Connect the default variable to the expected input
const INode * FromNode = InGraphContext . Graph - > FindNode ( VariableNodeID ) ;
if ( ! ensure ( FromNode ) )
{
continue ;
}
const FVertexKey & FromVertexKey = VariableData . InitParams . VertexName ;
const INode * ToNode = InGraphContext . Graph - > FindNode ( VariableData . DestinationNodeID ) ;
if ( ! ensure ( ToNode ) )
{
continue ;
}
const FVertexKey & ToVertexKey = VariableData . DestinationVertexKey ;
bool bSuccess = InGraphContext . Graph - > AddDataEdge ( * FromNode , FromVertexKey , * ToNode , ToVertexKey ) ;
if ( ! bSuccess )
{
UE_LOG ( LogMetaSound , Error , TEXT ( " Failed to connect default variable edge: from '%s' to '%s' " ) , * FromVertexKey , * ToVertexKey ) ;
}
}
}
TArray < FFrontendGraphBuilder : : FDefaultVariableData > FFrontendGraphBuilder : : GetInputDefaultVariableData ( const FMetasoundFrontendNode & InNode , const FNodeInitData & InInitData )
{
TArray < FDefaultVariableData > DefaultVariableData ;
TArray < FMetasoundFrontendVertex > InputVertices = InNode . Interface . Inputs ;
for ( const FMetasoundFrontendVertexLiteral & Literal : InNode . InputLiterals )
{
FVariableNodeConstructorParams InitParams ;
FName TypeName ;
FGuid VertexID = Literal . VertexID ;
FVertexKey DestinationVertexKey ;
auto RemoveAndBuildParams = [ & ] ( const FMetasoundFrontendVertex & Vertex )
{
if ( Vertex . VertexID = = VertexID )
{
InitParams . InitParam = Literal . Value . ToLiteral ( Vertex . TypeName ) ;
InitParams . InstanceID = FGuid : : NewGuid ( ) ;
InitParams . NodeName = InInitData . InstanceName + Vertex . Name + InitParams . InstanceID . ToString ( ) ;
InitParams . VertexName = InitParams . NodeName ;
TypeName = Vertex . TypeName ;
DestinationVertexKey = Vertex . Name ;
return true ;
}
return false ;
} ;
const bool bRemoved = InputVertices . RemoveAllSwap ( RemoveAndBuildParams , false /* bAllowShrinking */ ) > 0 ;
if ( ensure ( bRemoved ) )
{
DefaultVariableData . Emplace ( FDefaultVariableData
{
InNode . ID ,
VertexID ,
DestinationVertexKey ,
TypeName ,
MoveTemp ( InitParams )
} ) ;
}
}
return DefaultVariableData ;
}
2020-12-04 11:19:17 -04:00
/** Check that all dependencies are C++ class dependencies. */
2021-05-28 14:09:45 -04:00
bool FFrontendGraphBuilder : : IsFlat ( const FMetasoundFrontendDocument & InDocument )
2020-12-04 11:19:17 -04:00
{
2021-01-13 10:48:59 -04:00
if ( InDocument . Subgraphs . Num ( ) > 0 )
{
return false ;
}
return IsFlat ( InDocument . RootGraph , InDocument . Dependencies ) ;
2020-12-04 11:19:17 -04:00
}
2021-05-28 14:09:45 -04:00
bool FFrontendGraphBuilder : : IsFlat ( const FMetasoundFrontendGraphClass & InRoot , const TArray < FMetasoundFrontendClass > & InDependencies )
2020-12-04 11:19:17 -04:00
{
// All dependencies are external dependencies in a flat graph
2021-01-13 10:48:59 -04:00
auto IsClassExternal = [ & ] ( const FMetasoundFrontendClass & InDesc )
{
bool bIsExternal = ( InDesc . Metadata . Type = = EMetasoundFrontendClassType : : External ) | |
( InDesc . Metadata . Type = = EMetasoundFrontendClassType : : Input ) | |
( InDesc . Metadata . Type = = EMetasoundFrontendClassType : : Output ) ;
return bIsExternal ;
} ;
2020-12-04 11:19:17 -04:00
const bool bIsEveryDependencyExternal = Algo : : AllOf ( InDependencies , IsClassExternal ) ;
if ( ! bIsEveryDependencyExternal )
{
return false ;
}
2021-01-13 10:48:59 -04:00
// All the dependencies are met
2021-01-20 17:26:40 -04:00
TSet < FGuid > AvailableDependencies ;
2021-01-13 10:48:59 -04:00
Algo : : Transform ( InDependencies , AvailableDependencies , [ ] ( const FMetasoundFrontendClass & InDesc ) { return InDesc . ID ; } ) ;
2020-12-04 11:19:17 -04:00
2021-01-13 10:48:59 -04:00
auto IsDependencyMet = [ & ] ( const FMetasoundFrontendNode & InNode )
2020-12-04 11:19:17 -04:00
{
2021-01-13 10:48:59 -04:00
return AvailableDependencies . Contains ( InNode . ClassID ) ;
2020-12-04 11:19:17 -04:00
} ;
const bool bIsEveryDependencyMet = Algo : : AllOf ( InRoot . Graph . Nodes , IsDependencyMet ) ;
2021-01-13 10:48:59 -04:00
2020-12-04 11:19:17 -04:00
return bIsEveryDependencyMet ;
}
2021-05-28 14:09:45 -04:00
bool FFrontendGraphBuilder : : SortSubgraphDependencies ( TArray < const FMetasoundFrontendGraphClass * > & Subgraphs )
2020-12-04 11:19:17 -04:00
{
2021-05-20 19:33:21 -04:00
// Helper for caching and querying subgraph dependencies
struct FSubgraphDependencyLookup
2020-12-04 11:19:17 -04:00
{
2021-05-20 19:33:21 -04:00
FSubgraphDependencyLookup ( TArrayView < const FMetasoundFrontendGraphClass * > InGraphs )
{
// Map ClassID to graph pointer.
TMap < FGuid , const FMetasoundFrontendGraphClass * > ClassIDAndGraph ;
for ( const FMetasoundFrontendGraphClass * Graph : InGraphs )
{
ClassIDAndGraph . Add ( Graph - > ID , Graph ) ;
}
// Cache subgraph dependencies.
for ( const FMetasoundFrontendGraphClass * GraphClass : InGraphs )
{
for ( const FMetasoundFrontendNode & Node : GraphClass - > Graph . Nodes )
{
if ( ClassIDAndGraph . Contains ( Node . ClassID ) )
{
DependencyMap . Add ( GraphClass , ClassIDAndGraph [ Node . ClassID ] ) ;
}
}
}
}
TArray < const FMetasoundFrontendGraphClass * > operator ( ) ( const FMetasoundFrontendGraphClass * InParent ) const
{
TArray < const FMetasoundFrontendGraphClass * > Dependencies ;
DependencyMap . MultiFind ( InParent , Dependencies ) ;
return Dependencies ;
}
private :
TMultiMap < const FMetasoundFrontendGraphClass * , const FMetasoundFrontendGraphClass * > DependencyMap ;
} ;
bool bSuccess = Algo : : TopologicalSort ( Subgraphs , FSubgraphDependencyLookup ( Subgraphs ) ) ;
if ( ! bSuccess )
{
UE_LOG ( LogMetaSound , Error , TEXT ( " Failed to topologically sort subgraphs. Possible recursive subgraph dependency " ) ) ;
2020-12-04 11:19:17 -04:00
}
2021-05-20 19:33:21 -04:00
return bSuccess ;
}
2020-12-04 11:19:17 -04:00
2021-05-28 14:09:45 -04:00
TUniquePtr < FFrontendGraph > FFrontendGraphBuilder : : CreateGraph ( FBuildContext & InContext , const FMetasoundFrontendGraphClass & InGraphClass )
2021-05-20 19:33:21 -04:00
{
2021-05-28 14:09:45 -04:00
const FString GraphName = InGraphClass . Metadata . ClassName . GetFullName ( ) . ToString ( ) ;
2020-12-04 11:19:17 -04:00
2021-05-28 14:09:45 -04:00
FBuildGraphContext BuildGraphContext
{
MakeUnique < FFrontendGraph > ( GraphName , FGuid : : NewGuid ( ) ) ,
InGraphClass ,
InContext
} ;
2020-12-04 11:19:17 -04:00
// TODO: will likely want to bubble up errors here for case where
2021-05-28 14:09:45 -04:00
// a datatype or node is not registered.
AddNodesToGraph ( BuildGraphContext ) ;
AddEdgesToGraph ( BuildGraphContext ) ;
AddDefaultInputVariables ( BuildGraphContext ) ;
2020-12-04 11:19:17 -04:00
2021-05-28 14:09:45 -04:00
check ( BuildGraphContext . Graph - > OwnsAllReferencedNodes ( ) ) ;
return MoveTemp ( BuildGraphContext . Graph ) ;
2021-01-13 10:48:59 -04:00
}
2021-05-20 19:33:21 -04:00
2021-05-28 14:09:45 -04:00
TUniquePtr < FFrontendGraph > FFrontendGraphBuilder : : CreateGraph ( const FMetasoundFrontendGraphClass & InGraph , const TArray < FMetasoundFrontendGraphClass > & InSubgraphs , const TArray < FMetasoundFrontendClass > & InDependencies )
2021-05-20 19:33:21 -04:00
{
FBuildContext Context ;
// Gather all references to node classes from external dependencies and subgraphs.
for ( const FMetasoundFrontendClass & ExtClass : InDependencies )
{
Context . FrontendClasses . Add ( ExtClass . ID , & ExtClass ) ;
}
for ( const FMetasoundFrontendClass & ExtClass : InSubgraphs )
{
Context . FrontendClasses . Add ( ExtClass . ID , & ExtClass ) ;
}
// Sort subgraphs so that dependent subgraphs are created in correct order.
TArray < const FMetasoundFrontendGraphClass * > FrontendSubgraphPtrs ;
Algo : : Transform ( InSubgraphs , FrontendSubgraphPtrs , [ ] ( const FMetasoundFrontendGraphClass & InClass ) { return & InClass ; } ) ;
bool bSuccess = SortSubgraphDependencies ( FrontendSubgraphPtrs ) ;
if ( ! bSuccess )
{
UE_LOG ( LogMetaSound , Error , TEXT ( " Failed to create graph due to failed subgraph ordering. " ) ) ;
return TUniquePtr < FFrontendGraph > ( nullptr ) ;
}
// Create each subgraph.
for ( const FMetasoundFrontendGraphClass * FrontendSubgraphPtr : FrontendSubgraphPtrs )
{
TSharedPtr < const INode > Subgraph ( CreateGraph ( Context , * FrontendSubgraphPtr ) . Release ( ) ) ;
if ( ! Subgraph . IsValid ( ) )
{
UE_LOG ( LogMetaSound , Warning , TEXT ( " Failed to create subgraph [SubgraphName: %s] " ) , * FrontendSubgraphPtr - > Metadata . ClassName . ToString ( ) ) ;
}
else
{
// Add subgraphs to context so they are accessible for subsequent graphs.
Context . Graphs . Add ( FrontendSubgraphPtr - > ID , Subgraph ) ;
}
}
// Create parent graph.
2021-05-28 14:09:45 -04:00
return CreateGraph ( Context , InGraph ) ;
2021-05-20 19:33:21 -04:00
}
2021-01-13 10:48:59 -04:00
/* Metasound document should be inflated by now. */
2021-05-28 14:09:45 -04:00
TUniquePtr < FFrontendGraph > FFrontendGraphBuilder : : CreateGraph ( const FMetasoundFrontendDocument & InDocument )
2021-01-13 10:48:59 -04:00
{
return CreateGraph ( InDocument . RootGraph , InDocument . Subgraphs , InDocument . Dependencies ) ;
2020-12-04 11:19:17 -04:00
}
}