#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "MetasoundFrontendBaseClasses.h"
2020-07-20 00:05:22 -04:00
# include "MetasoundDataReference.h"
# include "MetasoundFrontendRegistries.h"
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
namespace Metasound
{
namespace Frontend
{
2020-07-20 00:05:22 -04:00
TMap < FNodeRegistryKey , FNodeRegistryElement > & GetExternalNodeRegistry ( )
{
return FMetasoundFrontendRegistryContainer : : Get ( ) - > GetExternalNodeRegistry ( ) ;
}
2020-08-13 19:07:41 -04:00
TUniquePtr < INode > ConstructInputNode ( const FName & InInputType , FInputNodeConstructorParams & & InParams )
2020-07-20 00:05:22 -04:00
{
2020-08-13 19:07:41 -04:00
return FMetasoundFrontendRegistryContainer : : Get ( ) - > ConstructInputNode ( InInputType , MoveTemp ( InParams ) ) ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
TUniquePtr < INode > ConstructOutputNode ( const FName & InOutputType , const FOutputNodeConstructorParams & InParams )
2020-07-20 00:05:22 -04:00
{
return FMetasoundFrontendRegistryContainer : : Get ( ) - > ConstructOutputNode ( InOutputType , InParams ) ;
}
TUniquePtr < INode > ConstructExternalNode ( const FName & InNodeType , uint32 InNodeHash , const FNodeInitData & InInitData )
{
return FMetasoundFrontendRegistryContainer : : Get ( ) - > ConstructExternalNode ( InNodeType , InNodeHash , InInitData ) ;
}
2021-01-13 10:48:59 -04:00
#if 0
void SetLiteralDescription ( FMetasoundFrontendLiteral & OutDescription , bool InValue )
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
OutDescription . LiteralType = EMetasoundFrontendLiteralType : : Bool ;
2020-07-20 00:05:22 -04:00
OutDescription . AsBool = InValue ;
OutDescription . AsInteger = 0 ;
OutDescription . AsFloat = 0.0f ;
OutDescription . AsString . Empty ( ) ;
2020-08-13 19:07:41 -04:00
OutDescription . AsUObject = nullptr ;
OutDescription . AsUObjectArray . Empty ( ) ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
void SetLiteralDescription ( FMetasoundFrontendLiteral & OutDescription , int32 InValue )
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
OutDescription . LiteralType = EMetasoundFrontendLiteralType : : Integer ;
2020-07-20 00:05:22 -04:00
OutDescription . AsBool = false ;
OutDescription . AsInteger = InValue ;
OutDescription . AsFloat = 0.0f ;
OutDescription . AsString . Empty ( ) ;
2020-08-13 19:07:41 -04:00
OutDescription . AsUObject = nullptr ;
OutDescription . AsUObjectArray . Empty ( ) ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
void SetLiteralDescription ( FMetasoundFrontendLiteral & OutDescription , float InValue )
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
OutDescription . LiteralType = EMetasoundFrontendLiteralType : : Float ;
2020-07-20 00:05:22 -04:00
OutDescription . AsBool = false ;
OutDescription . AsInteger = 0 ;
OutDescription . AsFloat = InValue ;
OutDescription . AsString . Empty ( ) ;
2020-08-13 19:07:41 -04:00
OutDescription . AsUObject = nullptr ;
OutDescription . AsUObjectArray . Empty ( ) ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
void SetLiteralDescription ( FMetasoundFrontendLiteral & OutDescription , const FString & InValue )
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
OutDescription . LiteralType = EMetasoundFrontendLiteralType : : String ;
2020-07-20 00:05:22 -04:00
OutDescription . AsBool = false ;
OutDescription . AsInteger = 0 ;
OutDescription . AsFloat = 0.0f ;
OutDescription . AsString = InValue ;
2020-08-13 19:07:41 -04:00
OutDescription . AsUObject = nullptr ;
OutDescription . AsUObjectArray . Empty ( ) ;
}
2021-01-13 10:48:59 -04:00
void SetLiteralDescription ( FMetasoundFrontendLiteral & OutDescription , UObject * InValue )
2020-08-13 19:07:41 -04:00
{
2021-01-13 10:48:59 -04:00
OutDescription . LiteralType = EMetasoundFrontendLiteralType : : UObject ;
2020-08-13 19:07:41 -04:00
OutDescription . AsBool = false ;
OutDescription . AsInteger = 0 ;
OutDescription . AsFloat = 0.0f ;
OutDescription . AsString . Empty ( ) ;
OutDescription . AsUObject = InValue ;
OutDescription . AsUObjectArray . Empty ( ) ;
}
2021-01-13 10:48:59 -04:00
void SetLiteralDescription ( FMetasoundFrontendLiteral & OutDescription , const TArray < UObject * > & InValue )
2020-08-13 19:07:41 -04:00
{
2021-01-13 10:48:59 -04:00
OutDescription . LiteralType = EMetasoundFrontendLiteralType : : UObjectArray ;
2020-08-13 19:07:41 -04:00
OutDescription . AsBool = false ;
OutDescription . AsInteger = 0 ;
OutDescription . AsFloat = 0.0f ;
OutDescription . AsString . Empty ( ) ;
OutDescription . AsUObject = nullptr ;
OutDescription . AsUObjectArray = InValue ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
void ClearLiteralDescription ( FMetasoundFrontendLiteral & OutDescription )
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
OutDescription . LiteralType = EMetasoundFrontendLiteralType : : None ;
2020-07-20 00:05:22 -04:00
OutDescription . AsBool = false ;
OutDescription . AsInteger = 0 ;
OutDescription . AsFloat = 0.0f ;
OutDescription . AsString . Empty ( ) ;
2020-08-13 19:07:41 -04:00
OutDescription . AsUObject = nullptr ;
OutDescription . AsUObjectArray . Empty ( ) ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
# endif
2020-07-20 00:05:22 -04:00
2021-01-13 10:48:59 -04:00
FLiteral GetLiteralParamForDataType ( FName InDataType , const FMetasoundFrontendLiteral & InDescription )
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
EMetasoundFrontendLiteralType LiteralType = InDescription . Type ;
2020-07-20 00:05:22 -04:00
FMetasoundFrontendRegistryContainer * Registry = FMetasoundFrontendRegistryContainer : : Get ( ) ;
switch ( LiteralType )
{
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendLiteralType : : None :
2020-07-20 00:05:22 -04:00
{
return GetDefaultParamForDataType ( InDataType ) ;
}
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendLiteralType : : Bool :
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
if ( ! Registry - > DoesDataTypeSupportLiteralType ( InDataType , ELiteralType : : Boolean ) )
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral : : CreateInvalid ( ) ;
2020-07-20 00:05:22 -04:00
}
else
{
2021-01-13 10:48:59 -04:00
return FLiteral ( InDescription . AsBool ) ;
2020-07-20 00:05:22 -04:00
}
}
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendLiteralType : : Float :
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
if ( ! Registry - > DoesDataTypeSupportLiteralType ( InDataType , ELiteralType : : Float ) )
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral : : CreateInvalid ( ) ;
2020-07-20 00:05:22 -04:00
}
else
{
2021-01-13 10:48:59 -04:00
return FLiteral ( InDescription . AsFloat ) ;
2020-07-20 00:05:22 -04:00
}
}
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendLiteralType : : Integer :
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
if ( ! Registry - > DoesDataTypeSupportLiteralType ( InDataType , ELiteralType : : Integer ) )
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral : : CreateInvalid ( ) ;
2020-07-20 00:05:22 -04:00
}
else
{
2021-01-13 10:48:59 -04:00
return FLiteral ( InDescription . AsInteger ) ;
2020-07-20 00:05:22 -04:00
}
}
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendLiteralType : : String :
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
if ( ! Registry - > DoesDataTypeSupportLiteralType ( InDataType , ELiteralType : : String ) )
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral : : CreateInvalid ( ) ;
2020-07-20 00:05:22 -04:00
}
else
{
2021-01-13 10:48:59 -04:00
return FLiteral ( InDescription . AsString ) ;
2020-07-20 00:05:22 -04:00
}
}
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendLiteralType : : UObject :
2020-08-13 19:07:41 -04:00
{
2021-01-13 10:48:59 -04:00
if ( ! Registry - > DoesDataTypeSupportLiteralType ( InDataType , ELiteralType : : UObjectProxy ) )
2020-08-13 19:07:41 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral : : CreateInvalid ( ) ;
2020-08-13 19:07:41 -04:00
}
else
{
return Registry - > GenerateLiteralForUObject ( InDataType , InDescription . AsUObject ) ;
}
}
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendLiteralType : : UObjectArray :
2020-08-13 19:07:41 -04:00
{
2021-01-13 10:48:59 -04:00
if ( ! Registry - > DoesDataTypeSupportLiteralType ( InDataType , ELiteralType : : UObjectProxyArray ) )
2020-08-13 19:07:41 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral : : CreateInvalid ( ) ;
2020-08-13 19:07:41 -04:00
}
else
{
return Registry - > GenerateLiteralForUObjectArray ( InDataType , InDescription . AsUObjectArray ) ;
}
}
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendLiteralType : : Invalid :
2020-07-20 00:05:22 -04:00
default :
{
2021-01-13 10:48:59 -04:00
return FLiteral : : CreateInvalid ( ) ;
2020-07-20 00:05:22 -04:00
}
}
}
2021-01-13 10:48:59 -04:00
bool DoesDataTypeSupportLiteralType ( FName InDataType , EMetasoundFrontendLiteralType InLiteralType )
2020-07-20 00:05:22 -04:00
{
switch ( InLiteralType )
{
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendLiteralType : : None :
return DoesDataTypeSupportLiteralType ( InDataType , ELiteralType : : None ) ;
case EMetasoundFrontendLiteralType : : Bool :
return DoesDataTypeSupportLiteralType ( InDataType , ELiteralType : : Boolean ) ;
case EMetasoundFrontendLiteralType : : Float :
return DoesDataTypeSupportLiteralType ( InDataType , ELiteralType : : Float ) ;
case EMetasoundFrontendLiteralType : : Integer :
return DoesDataTypeSupportLiteralType ( InDataType , ELiteralType : : Integer ) ;
case EMetasoundFrontendLiteralType : : String :
return DoesDataTypeSupportLiteralType ( InDataType , ELiteralType : : String ) ;
case EMetasoundFrontendLiteralType : : UObject :
return DoesDataTypeSupportLiteralType ( InDataType , ELiteralType : : UObjectProxy ) ;
case EMetasoundFrontendLiteralType : : UObjectArray :
return DoesDataTypeSupportLiteralType ( InDataType , ELiteralType : : UObjectProxyArray ) ;
case EMetasoundFrontendLiteralType : : Invalid :
2020-07-20 00:05:22 -04:00
default :
return false ;
}
}
2021-01-13 10:48:59 -04:00
bool DoesDataTypeSupportLiteralType ( FName InDataType , ELiteralType InLiteralType )
2020-07-20 00:05:22 -04:00
{
FMetasoundFrontendRegistryContainer * Registry = FMetasoundFrontendRegistryContainer : : Get ( ) ;
return Registry - > DoesDataTypeSupportLiteralType ( InDataType , InLiteralType ) ;
}
2021-01-13 10:48:59 -04:00
Metasound : : FLiteral GetLiteralParam ( const FMetasoundFrontendLiteral & InDescription )
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
EMetasoundFrontendLiteralType LiteralType = InDescription . Type ;
2020-07-20 00:05:22 -04:00
switch ( LiteralType )
{
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendLiteralType : : Bool :
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral ( InDescription . AsBool ) ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendLiteralType : : Float :
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral ( InDescription . AsFloat ) ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendLiteralType : : Integer :
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral ( InDescription . AsInteger ) ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendLiteralType : : String :
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral ( InDescription . AsString ) ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
case EMetasoundFrontendLiteralType : : UObject :
case EMetasoundFrontendLiteralType : : UObjectArray :
case EMetasoundFrontendLiteralType : : None :
case EMetasoundFrontendLiteralType : : Invalid :
2020-07-20 00:05:22 -04:00
default :
{
2021-01-13 10:48:59 -04:00
return FLiteral : : CreateInvalid ( ) ;
2020-07-20 00:05:22 -04:00
}
}
}
2021-01-13 10:48:59 -04:00
Metasound : : FLiteral GetDefaultParamForDataType ( FName InDataType )
2020-07-20 00:05:22 -04:00
{
FMetasoundFrontendRegistryContainer * Registry = FMetasoundFrontendRegistryContainer : : Get ( ) ;
2021-01-13 10:48:59 -04:00
ELiteralType DesiredArgType = Registry - > GetDesiredLiteralTypeForDataType ( InDataType ) ;
2020-07-20 00:05:22 -04:00
switch ( DesiredArgType )
{
2021-01-13 10:48:59 -04:00
case Metasound : : ELiteralType : : Boolean :
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral ( false ) ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
case Metasound : : ELiteralType : : Integer :
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral ( 0 ) ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
case Metasound : : ELiteralType : : Float :
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral ( 0.0f ) ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
case Metasound : : ELiteralType : : String :
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral ( FString ( ) ) ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
case Metasound : : ELiteralType : : UObjectProxy :
case Metasound : : ELiteralType : : UObjectProxyArray :
case Metasound : : ELiteralType : : None :
2020-07-20 00:05:22 -04:00
{
2021-01-13 10:48:59 -04:00
return FLiteral ( ) ;
2020-07-20 00:05:22 -04:00
}
2021-01-13 10:48:59 -04:00
case Metasound : : ELiteralType : : Invalid :
2020-07-20 00:05:22 -04:00
default :
{
2021-01-13 10:48:59 -04:00
return FLiteral : : CreateInvalid ( ) ;
2020-07-20 00:05:22 -04:00
}
}
}
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
ITransactable : : ITransactable ( uint32 InUndoLimit , TWeakObjectPtr < UObject > InOwningAsset /* = nullptr */ )
: UndoLimit ( InUndoLimit )
, OwningAsset ( InOwningAsset )
{
}
bool ITransactable : : Undo ( )
{
if ( UndoTransactableStack . Num ( ) = = 0 )
{
return false ;
}
if ( TSharedPtr < ITransactable > Owner = OwningTransactable . Pin ( ) )
{
Owner - > DiscardUndoFromOwnedTransactable ( AsShared ( ) ) ;
}
TSharedPtr < ITransactable > Transactable = UndoTransactableStack . Pop ( ) . Pin ( ) ;
TSharedPtr < ITransactable > PreviousTransactable = AsShared ( ) ;
RedoTransactableStack . Push ( Transactable ) ;
while ( Transactable )
{
if ( Transactable = = PreviousTransactable )
{
bool bUndoSucceeded = Transactable - > PerformLocalUndo ( ) ;
if ( bUndoSucceeded & & OwningAsset . IsValid ( ) )
{
OwningAsset - > MarkPackageDirty ( ) ;
}
return bUndoSucceeded ;
}
PreviousTransactable = Transactable ;
Transactable = Transactable - > UndoTransactableStack . Pop ( ) . Pin ( ) ;
PreviousTransactable - > RedoTransactableStack . Push ( Transactable ) ;
}
return false ;
}
bool ITransactable : : Redo ( )
{
if ( RedoTransactableStack . Num ( ) = = 0 )
{
return false ;
}
if ( TSharedPtr < ITransactable > Owner = OwningTransactable . Pin ( ) )
{
Owner - > DiscardRedoFromOwnedTransactable ( AsShared ( ) ) ;
}
TSharedPtr < ITransactable > Transactable = RedoTransactableStack . Pop ( ) . Pin ( ) ;
TSharedPtr < ITransactable > PreviousTransactable = AsShared ( ) ;
UndoTransactableStack . Push ( Transactable ) ;
while ( Transactable )
{
if ( Transactable = = PreviousTransactable )
{
const bool bRedoSucceeded = Transactable - > PerformLocalRedo ( ) ;
if ( bRedoSucceeded & & OwningAsset . IsValid ( ) )
{
OwningAsset - > MarkPackageDirty ( ) ;
}
return bRedoSucceeded ;
}
PreviousTransactable = Transactable ;
Transactable = Transactable - > RedoTransactableStack . Pop ( ) . Pin ( ) ;
PreviousTransactable - > UndoTransactableStack . Push ( Transactable ) ;
}
return false ;
}
void ITransactable : : CommitTransaction ( FReversibleTransaction & & InTransactionDescription )
{
if ( ( ( uint32 ) LocalUndoTransactionStack . Num ( ) ) > = UndoLimit )
{
// Discard the oldest undo action.
LocalUndoTransactionStack . RemoveAt ( 0 ) ;
}
LocalUndoTransactionStack . Push ( MoveTemp ( InTransactionDescription ) ) ;
RedoTransactableStack . Reset ( ) ;
TWeakPtr < ITransactable > WeakThisPtr = AsShared ( ) ;
UndoTransactableStack . Push ( WeakThisPtr ) ;
if ( TSharedPtr < ITransactable > Owner = OwningTransactable . Pin ( ) )
{
Owner - > PushUndoFromOwnedTransactable ( WeakThisPtr ) ;
}
if ( OwningAsset . IsValid ( ) )
{
OwningAsset - > MarkPackageDirty ( ) ;
}
}
bool ITransactable : : RegisterOwningTransactable ( ITransactable & InOwningTransactable )
{
// Sanity check that this will not create a cycle.
while ( TSharedPtr < ITransactable > Owner = InOwningTransactable . OwningTransactable . Pin ( ) )
{
if ( Owner . Get ( ) = = this )
{
return false ;
}
}
OwningTransactable = InOwningTransactable . AsShared ( ) ;
return true ;
}
bool ITransactable : : PerformLocalUndo ( )
{
if ( LocalUndoTransactionStack . Num ( ) = = 0 )
{
return false ;
}
FReversibleTransaction Transaction = LocalUndoTransactionStack . Pop ( ) ;
bool bResult = Transaction . UndoTransaction ( ) ;
if ( bResult )
{
LocalRedoTransactionStack . Push ( MoveTemp ( Transaction ) ) ;
}
return bResult ;
}
bool ITransactable : : PerformLocalRedo ( )
{
if ( LocalRedoTransactionStack . Num ( ) = = 0 )
{
return false ;
}
FReversibleTransaction Transaction = LocalRedoTransactionStack . Pop ( ) ;
bool bResult = Transaction . RedoTransaction ( ) ;
if ( bResult )
{
LocalUndoTransactionStack . Push ( MoveTemp ( Transaction ) ) ;
}
return bResult ;
}
bool ITransactable : : DiscardUndoFromOwnedTransactable ( TWeakPtr < ITransactable > InOwnedTransactable )
{
// NOTE: This relies on TArray.Push adding an element to the end of the array.
// Start at the end of the array and work backwards until we find the most recent undo operation for this transactable.
for ( int32 Index = UndoTransactableStack . Num ( ) - 1 ; Index > = 0 ; Index - - )
{
if ( UndoTransactableStack [ Index ] = = InOwnedTransactable )
{
// Discard, but preserve the stack order.
UndoTransactableStack . RemoveAt ( Index ) ;
return true ;
}
}
return false ;
}
bool ITransactable : : DiscardRedoFromOwnedTransactable ( TWeakPtr < ITransactable > InOwnedTransactable )
{
// NOTE: This relies on TArray.Push adding an element to the end of the array.
// Start at the end of the array and work backwards until we find the most recent undo operation for this transactable.
for ( int32 Index = RedoTransactableStack . Num ( ) - 1 ; Index > = 0 ; Index - - )
{
if ( RedoTransactableStack [ Index ] = = InOwnedTransactable )
{
// Discard, but preserve the stack order.
RedoTransactableStack . RemoveAt ( Index ) ;
return true ;
}
}
return false ;
}
bool ITransactable : : PushUndoFromOwnedTransactable ( TWeakPtr < ITransactable > InOwnedTransactable )
{
UndoTransactableStack . Push ( MoveTemp ( InOwnedTransactable ) ) ;
RedoTransactableStack . Reset ( ) ;
return true ;
}
bool ITransactable : : PushRedoFromOwnedTransactable ( TWeakPtr < ITransactable > InOwnedTransactable )
{
RedoTransactableStack . Push ( MoveTemp ( InOwnedTransactable ) ) ;
DiscardUndoFromOwnedTransactable ( InOwnedTransactable ) ;
return true ;
}
2021-01-13 10:48:59 -04:00
#if 0
FDescriptionAccessPoint : : FDescriptionAccessPoint ( TAccessPtr < FMetasoundFrontendDocument > InRootDocumentPtr )
2020-12-14 15:48:27 -04:00
: RootDocumentPtr ( InRootDocumentPtr )
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
{
}
2021-01-13 10:48:59 -04:00
FMetasoundFrontendDocument & FDescriptionAccessPoint : : GetRootChecked ( ) const
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
{
2020-12-14 15:48:27 -04:00
check ( RootDocumentPtr . IsValid ( ) ) ;
return * RootDocumentPtr ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
}
2020-12-14 15:48:27 -04:00
bool FDescriptionAccessPoint : : GetDescriptionPtrFromPath ( const FDescPath & InPathFromRoot , FMetasoundDescriptionPtr & OutPtr ) const
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
{
2020-12-14 15:48:27 -04:00
if ( ! RootDocumentPtr . IsValid ( ) )
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
{
2020-12-14 15:48:27 -04:00
return false ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
}
FDescPath CurrentPath = InPathFromRoot ;
2021-01-13 10:48:59 -04:00
OutPtr . Set < FMetasoundFrontendDocument * > ( RootDocumentPtr . Get ( ) ) ;
2020-12-14 15:48:27 -04:00
FDescriptionUnwindStep CurrentStep = { OutPtr , Path : : EDescType : : Document } ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
while ( CurrentPath . Num ( ) ! = 0 & & CurrentStep . Type ! = Path : : EDescType : : Invalid )
{
CurrentStep = GoToNext ( CurrentPath , CurrentStep ) ;
}
2020-12-14 15:48:27 -04:00
OutPtr = CurrentStep . DescriptionStructPtr ;
return CurrentStep . Type ! = Path : : EDescType : : Invalid ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
}
2020-12-14 15:48:27 -04:00
2021-01-13 10:48:59 -04:00
FDescriptionAccessPoint : : FDescriptionUnwindStep FDescriptionAccessPoint : : GoToNextFromDocument ( FMetasoundFrontendDocument & InDocument , FDescPath & InPath , const Path : : FElement & InNext ) const
2020-12-04 11:19:17 -04:00
{
2020-12-14 15:48:27 -04:00
check ( RootDocumentPtr . IsValid ( ) ) ;
2020-12-04 11:19:17 -04:00
switch ( InNext . CurrentDescType )
{
case Path : : EDescType : : Document :
{
FDescriptionUnwindStep UnwindStep ;
2021-01-13 10:48:59 -04:00
UnwindStep . DescriptionStructPtr . Set < FMetasoundFrontendDocument * > ( & InDocument ) ;
2020-12-04 11:19:17 -04:00
UnwindStep . Type = Path : : EDescType : : Document ;
return UnwindStep ;
}
case Path : : EDescType : : Class :
{
FDescriptionUnwindStep UnwindStep ;
2021-01-13 10:48:59 -04:00
UnwindStep . DescriptionStructPtr . Set < FMetasoundFrontendClass * > ( & ( InDocument . RootGraph ) ) ;
2020-12-04 11:19:17 -04:00
UnwindStep . Type = Path : : EDescType : : Class ;
return UnwindStep ;
}
case Path : : EDescType : : DocDependencies :
{
// The next element in a path after Dependencies will always be the name of the dependency.
Path : : FElement DependencyElement = InPath . Path [ 0 ] ;
InPath . Path . RemoveAt ( 0 ) ;
if ( ! ensureAlwaysMsgf ( DependencyElement . CurrentDescType = = Path : : EDescType : : Class , TEXT ( " Invalid path set up. " ) ) )
{
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
}
FString & DependencyName = DependencyElement . LookupName ;
int32 DependencyID = DependencyElement . LookupID ;
if ( ! ensureAlwaysMsgf ( DependencyID ! = INDEX_NONE | | DependencyName . Len ( ) > 0 , TEXT ( " Path to a dependency did not include a valid ID or dependency name. " ) ) )
{
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
}
2021-01-13 10:48:59 -04:00
if ( DependencyID = = FMetasoundFrontendClass : : RootClassID )
2020-12-04 11:19:17 -04:00
{
FDescriptionUnwindStep UnwindStep ;
2021-01-13 10:48:59 -04:00
UnwindStep . DescriptionStructPtr . Set < FMetasoundFrontendClass * > ( & ( InDocument . RootGraph ) ) ;
2020-12-04 11:19:17 -04:00
UnwindStep . Type = Path : : EDescType : : Class ;
return UnwindStep ;
}
2021-01-13 10:48:59 -04:00
TArray < FMetasoundFrontendClass > & DependenciesList = InDocument . Dependencies ;
2020-12-04 11:19:17 -04:00
// Dependencies can be looked up by ID or by name.
if ( DependencyID ! = INDEX_NONE )
{
// Scan the dependencies list for the matching lookup ID.
2021-01-13 10:48:59 -04:00
for ( FMetasoundFrontendClass & Dependency : DependenciesList )
2020-12-04 11:19:17 -04:00
{
if ( Dependency . UniqueID = = DependencyID )
{
FDescriptionUnwindStep UnwindStep ;
2021-01-13 10:48:59 -04:00
UnwindStep . DescriptionStructPtr . Set < FMetasoundFrontendClass * > ( & Dependency ) ;
2020-12-04 11:19:17 -04:00
UnwindStep . Type = Path : : EDescType : : Class ;
return UnwindStep ;
}
}
}
else
{
// TODO: remove this chunk of code in the "else{}" block. Not sure if it has to be here.
checkNoEntry ( ) ;
// fall back to scanning the dependencies list for the matching lookup name.
2021-01-13 10:48:59 -04:00
for ( FMetasoundFrontendClass & Dependency : DependenciesList )
2020-12-04 11:19:17 -04:00
{
if ( Dependency . Metadata . NodeName = = DependencyName )
{
FDescriptionUnwindStep UnwindStep ;
2021-01-13 10:48:59 -04:00
UnwindStep . DescriptionStructPtr . Set < FMetasoundFrontendClass * > ( & Dependency ) ;
2020-12-04 11:19:17 -04:00
UnwindStep . Type = Path : : EDescType : : Class ;
return UnwindStep ;
}
}
}
// If we reached the end of the Dependencies list and didn't find a match, ensure.
ensureAlwaysMsgf ( false , TEXT ( " Couldn't find dependency %s in path. " ) , * DependencyName ) ;
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
}
}
checkNoEntry ( ) ;
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
}
2021-01-13 10:48:59 -04:00
FDescriptionAccessPoint : : FDescriptionUnwindStep FDescriptionAccessPoint : : GoToNextFromClass ( FMetasoundFrontendClass & InClassDescription , FDescPath & InPath , const Path : : FElement & InNext ) const
2020-12-04 11:19:17 -04:00
{
switch ( InNext . CurrentDescType )
{
case Path : : EDescType : : Graph :
{
FDescriptionUnwindStep UnwindStep ;
2021-01-13 10:48:59 -04:00
UnwindStep . DescriptionStructPtr . Set < FMetasoundFrontendGraph * > ( & ( InClassDescription . Graph ) ) ;
2020-12-04 11:19:17 -04:00
UnwindStep . Type = Path : : EDescType : : Graph ;
return UnwindStep ;
}
case Path : : EDescType : : ClassDependencies :
{
FDescriptionUnwindStep UnwindStep ;
UnwindStep . DescriptionStructPtr . Set < FClassDependencyIDs * > ( & ( InClassDescription . DependencyIDs ) ) ;
UnwindStep . Type = Path : : EDescType : : ClassDependencies ;
return UnwindStep ;
}
case Path : : EDescType : : Inputs :
{
// The next element after an Inputs element should always be the name of an input.
Path : : FElement InputElement = InPath . Path [ 0 ] ;
InPath . Path . RemoveAt ( 0 ) ;
if ( ! ensureAlwaysMsgf ( InputElement . CurrentDescType = = Path : : EDescType : : Input , TEXT ( " Invalid path set up. " ) ) )
{
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
}
FString & InputName = InputElement . LookupName ;
// Scan the inputs list for the lookup name.
2021-01-13 10:48:59 -04:00
TArray < FMetasoundFrontendClassInput > & InputsList = InClassDescription . Inputs ;
for ( FMetasoundFrontendClassInput & Input : InputsList )
2020-12-04 11:19:17 -04:00
{
if ( Input . Name = = InputName )
{
FDescriptionUnwindStep UnwindStep ;
2021-01-13 10:48:59 -04:00
UnwindStep . DescriptionStructPtr . Set < FMetasoundFrontendClassInput * > ( & Input ) ;
2020-12-04 11:19:17 -04:00
UnwindStep . Type = Path : : EDescType : : Input ;
return UnwindStep ;
}
}
// If we reached the end of the Inputs list and didn't find a match, ensure.
ensureAlwaysMsgf ( false , TEXT ( " Couldn't find input %s in path. " ) , * InputName ) ;
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
}
case Path : : EDescType : : Outputs :
{
// The next element after an Outputs element should always be the name of an output.
Path : : FElement OutputElement = InPath . Path [ 0 ] ;
InPath . Path . RemoveAt ( 0 ) ;
if ( ! ensureAlwaysMsgf ( OutputElement . CurrentDescType = = Path : : EDescType : : Output , TEXT ( " Invalid path set up. " ) ) )
{
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
}
FString & OutputName = OutputElement . LookupName ;
// Scan the outputs list for the lookup name.
2021-01-13 10:48:59 -04:00
TArray < FMetasoundFrontendClassOutput > & OutputsList = InClassDescription . Outputs ;
for ( FMetasoundFrontendClassOutput & Output : OutputsList )
2020-12-04 11:19:17 -04:00
{
if ( Output . Name = = OutputElement . LookupName )
{
FDescriptionUnwindStep UnwindStep ;
2021-01-13 10:48:59 -04:00
UnwindStep . DescriptionStructPtr . Set < FMetasoundFrontendClassOutput * > ( & Output ) ;
2020-12-04 11:19:17 -04:00
UnwindStep . Type = Path : : EDescType : : Output ;
return UnwindStep ;
}
}
// If we reached the end of the Inputs list and didn't find a match, ensure.
ensureAlwaysMsgf ( false , TEXT ( " Couldn't find output %s in path. " ) , * InNext . LookupName ) ;
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
}
case Path : : EDescType : : Metadata :
{
FDescriptionUnwindStep UnwindStep ;
2021-01-13 10:48:59 -04:00
UnwindStep . DescriptionStructPtr . Set < FMetasoundFrontendClassMetadata * > ( & ( InClassDescription . Metadata ) ) ;
2020-12-04 11:19:17 -04:00
UnwindStep . Type = Path : : EDescType : : Metadata ;
return UnwindStep ;
}
default :
{
ensureAlwaysMsgf ( false , TEXT ( " Invalid path- Tried to path directly from a Class Description to a type that wasn't a direct memember of the Class " ) ) ;
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
}
}
}
2020-12-14 15:48:27 -04:00
FDescriptionAccessPoint : : FDescriptionUnwindStep FDescriptionAccessPoint : : GoToNext ( FDescPath & InPath , FDescriptionUnwindStep InElement ) const
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
{
if ( ! ensureMsgf ( InPath . Path . Num ( ) ! = 0 , TEXT ( " Attempted to unwind an empty path. " ) ) )
{
2020-12-04 11:19:17 -04:00
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
}
Path : : FElement NextStep = InPath . Path [ 0 ] ;
InPath . Path . RemoveAt ( 0 ) ;
switch ( InElement . Type )
{
case Path : : EDescType : : Document :
{
2021-01-13 10:48:59 -04:00
FMetasoundFrontendDocument * Document = InElement . DescriptionStructPtr . Get < FMetasoundFrontendDocument * > ( ) ;
2020-12-04 11:19:17 -04:00
return GoToNextFromDocument ( * Document , InPath , NextStep ) ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
break ;
}
case Path : : EDescType : : Class :
{
2021-01-13 10:48:59 -04:00
FMetasoundFrontendClass * ClassDescription = InElement . DescriptionStructPtr . Get < FMetasoundFrontendClass * > ( ) ;
2020-12-04 11:19:17 -04:00
return GoToNextFromClass ( * ClassDescription , InPath , NextStep ) ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
break ;
}
case Path : : EDescType : : Graph :
{
if ( ! ensureAlwaysMsgf ( NextStep . CurrentDescType = = Path : : EDescType : : Nodes , TEXT ( " Invalid path. the Graph description only contains the Nodes list. " ) ) )
{
2020-12-04 11:19:17 -04:00
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
}
2021-01-13 10:48:59 -04:00
FMetasoundFrontendGraph * GraphDescription = InElement . DescriptionStructPtr . Get < FMetasoundFrontendGraph * > ( ) ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
if ( ! ensureAlwaysMsgf ( InPath . Path . Num ( ) ! = 0 , TEXT ( " Incomplete path! path stopped at Nodes list without specifying a node ID. " ) ) )
{
2020-12-04 11:19:17 -04:00
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
}
Path : : FElement NodeElement = InPath . Path [ 0 ] ;
InPath . Path . RemoveAt ( 0 ) ;
if ( ! ensureAlwaysMsgf ( NodeElement . CurrentDescType = = Path : : EDescType : : Node , TEXT ( " Invalid path! a Nodes element must always be followed by a Node ID. " ) ) )
{
2020-12-04 11:19:17 -04:00
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
}
2020-12-04 11:19:17 -04:00
int32 NodeID = NodeElement . LookupID ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
2021-01-13 10:48:59 -04:00
TArray < FMetasoundFrontendNode > & NodeList = GraphDescription - > Nodes ;
for ( FMetasoundFrontendNode & Node : NodeList )
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
{
if ( Node . UniqueID = = NodeID )
{
FDescriptionUnwindStep UnwindStep ;
2021-01-13 10:48:59 -04:00
UnwindStep . DescriptionStructPtr . Set < FMetasoundFrontendNode * > ( & Node ) ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
UnwindStep . Type = Path : : EDescType : : Node ;
return UnwindStep ;
}
}
break ;
}
case Path : : EDescType : : Inputs :
{
ensureAlwaysMsgf ( false , TEXT ( " Invalid path. Inputs will always be pathed directly after a Class. " ) ) ;
2020-12-04 11:19:17 -04:00
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
}
case Path : : EDescType : : DocDependencies :
{
ensureAlwaysMsgf ( false , TEXT ( " Invalid path. Document dependencies should always follow after a Document and should always list a specific dependency by ID or name. " ) ) ;
2020-12-04 11:19:17 -04:00
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
}
case Path : : EDescType : : Input :
case Path : : EDescType : : Output :
case Path : : EDescType : : Node :
case Path : : EDescType : : Metadata :
default :
{
ensureAlwaysMsgf ( false , TEXT ( " Invalid path. Input, Output, Node, and Metadata don't have any child elements. " ) ) ;
2020-12-04 11:19:17 -04:00
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
}
}
// if we ever hit this, we likely missed a return on one of these branches.
checkNoEntry ( ) ;
2020-12-04 11:19:17 -04:00
return FDescriptionUnwindStep : : CreateInvalid ( ) ;
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
}
FDescPath Path : : GetPathToClassForNode ( FDescPath InPathForNode , FString & InNodeName )
{
return FDescPath ( ) [ EFromDocument : : ToDependencies ] [ * InNodeName ] ;
}
FDescPath Path : : GetOwningClassDescription ( FDescPath InPathForGraph )
{
// Backtrack from the end of the path until we find a Class element.
for ( int32 Level = InPathForGraph . Path . Num ( ) - 1 ; Level > = 0 ; Level - - )
{
if ( InPathForGraph . Path [ Level ] . CurrentDescType = = Path : : EDescType : : Class )
{
return InPathForGraph ;
}
else
{
InPathForGraph . Path . RemoveAt ( InPathForGraph . Path . Num ( ) - 1 ) ;
}
}
return InPathForGraph ;
}
2020-12-04 11:19:17 -04:00
FDescPath Path : : GetDependencyPath ( int32 InDependencyID )
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
{
return FDescPath ( ) [ Path : : EFromDocument : : ToDependencies ] [ InDependencyID ] ;
}
2020-07-20 00:05:22 -04:00
FDescPath Path : : GetInputDescriptionPath ( FDescPath InPathForInputNode , const FString & InputName )
{
return ( InPathForInputNode < < 3 ) [ Path : : EFromClass : : ToInputs ] [ * InputName ] ;
}
FDescPath Path : : GetOutputDescriptionPath ( FDescPath InPathForOutputNode , const FString & OutputName )
{
return ( InPathForOutputNode < < 3 ) [ Path : : EFromClass : : ToOutputs ] [ * OutputName ] ;
}
2020-08-26 14:16:05 -04:00
FDescPath Path : : GetOuterGraphPath ( FDescPath InPath )
{
// Unwind element by element until we hit a graph.
while ( InPath . Path . Num ( ) > 1 & & InPath . Path . Last ( ) . CurrentDescType ! = Path : : EDescType : : Graph )
{
InPath . Path . Pop ( ) ;
}
return InPath ;
}
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
FString Path : : GetPrintableString ( FDescPath InPath )
{
FString OutString = FString ( TEXT ( " // " ) ) ;
for ( FElement & PathElement : InPath . Path )
{
switch ( PathElement . CurrentDescType )
{
case EDescType : : Document :
{
OutString + = TEXT ( " Document/ " ) ;
break ;
}
case EDescType : : Class :
{
OutString + = TEXT ( " Class/ " ) ;
break ;
}
case EDescType : : DocDependencies :
{
OutString + = TEXT ( " Dependencies( " ) ;
if ( PathElement . LookupID ! = INDEX_NONE )
{
OutString . AppendInt ( PathElement . LookupID ) ;
}
else
{
OutString + = PathElement . LookupName ;
}
OutString + = TEXT ( " )/ " ) ;
break ;
}
case EDescType : : ClassDependencies :
{
OutString + = TEXT ( " Dependencies( " ) ;
OutString . AppendInt ( PathElement . LookupID ) ;
OutString + = TEXT ( " )/ " ) ;
break ;
}
case EDescType : : Graph :
{
OutString + = TEXT ( " Graph/ " ) ;
break ;
}
case EDescType : : Inputs :
{
OutString + = TEXT ( " Inputs/ " ) ;
break ;
}
case EDescType : : Input :
{
OutString + = TEXT ( " Input( " ) ;
OutString + = PathElement . LookupName ;
OutString + = TEXT ( " )/ " ) ;
break ;
}
case EDescType : : Metadata :
{
OutString + = TEXT ( " Metadata/ " ) ;
break ;
}
case EDescType : : Nodes :
{
OutString + = TEXT ( " Nodes/ " ) ;
break ;
}
case EDescType : : Node :
{
OutString + = TEXT ( " Node( " ) ;
OutString . AppendInt ( PathElement . LookupID ) ;
OutString + = TEXT ( " )/ " ) ;
break ;
}
case EDescType : : Outputs :
{
OutString + = TEXT ( " Outputs/ " ) ;
break ;
}
case EDescType : : Output :
{
OutString + = TEXT ( " Output( " ) ;
OutString + = PathElement . LookupName ;
OutString + = TEXT ( " )/ " ) ;
break ;
}
default :
{
OutString + = TEXT ( " Unknown/ " ) ;
break ;
}
}
}
return OutString ;
}
2021-01-13 10:48:59 -04:00
# endif
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
2020-07-20 00:05:22 -04:00
void InitializeFrontend ( )
{
FMetasoundFrontendRegistryContainer : : Get ( ) - > InitializeFrontend ( ) ;
}
#jira UEAU-467
Initial implementation of Metasound::Frontend. Includes all classes and abstractions for dealing with the metasound document tree and graph editing.
Overall super happy with the way this turned out, since it lets us support exporting metasound graphs, creating local subgraphs in a metasound graph, and other really cool things.
High Level:
1) a Metasound with any sorts of inputs and outputs can be created using a UMetasoundNodeAsset, which contains a struct called the FMetasoundDocument.
2) since it would be very easy to create a malformed FMetasoundDocument by just editing the struct directly, all editing of the FMetasoundDocument is done through a set of APIs: FGraphHandle, FNodeHandle, FInputHandle, and FOutputHandle
2) You can access the main graph for a UMetasoundNodeAsset with UMetasoundNodeAsset::GetRootGraphHandle(), which returns a Metasound::Frontend::FGraphHandle.
3) The FGraphHandle lets you do all sorts of things, including:
i) Adding and removing nodes from the graph (AddNewNode/RemoveNode)
ii) adding and removing inputs/outputs from a graph (AddNewInput/RemoveInput, AddNewOutput/RemoveOutput)
iii) Get a handle to manipulate individual nodes in the graph (GetAllNodes/GetOutputNodes/GetInputNodes/GetOutputNodeWithName/GetInputNodeWithName)
iv) Get any metadata associated with that graph (GetGraphMetadata)
v) Create a subgraph that lives in the same document (CreateEmptySubgraphNode)
vi) Export a graph to a new Metasound asset or JSON
vii) compile that graph to an executable, playable, Metasound::IOperator (currently not all the way implemented in this CL but I'm getting to that tomorrow)
4) The FNodeHandle lets you manipulate any indivdual node on any given graph, primarily:
i) Getting input pins (GetAllInputs/GetAllOutputs/GetInputWithName/GetOutputWithName)
ii) get an FGraphHandle for the subgraph for this node if there is one (GetContainedGraph)
5) FInputHandle/FOutputHandle lets you manipulate individual input/output pins, primarily:
i) Seeing if two pins can be connected (CanConnectTo)
ii) Connecting two pins (Connect/ConnectWithConverterNode, the latter of which isn't implemented yet)
Mid Level (the metasound document model):
1) FMetasoundDocument is an aggregate and totally serializable to binary or text using the uproperty serializer.
2) A FMetasoundDocument owns a root class, which is it's primary graph, and a list of dependencies, which could be c++ implemented nodes, metasound graphs living in other assets, or subgraphs living in the current metasound document. the Dependencies list owns a list of any dependencies for the root class, as well as any nested dependencies (for example, if the root class has a subgraph that requires a C++ implemented sine osc, the subgraph and the sine osc will both be in the Dependencies array.
3) Each FMetasoundClassDescription contains that metasounds metadata, as well as a list of inputs and outputs for that metasound, and optionally a fully implemented graph.
4) If the metasound class description has a fully implemented graph, that graph is a list of FMetasoundNodeDescriptions, which themselves describe which nodes they are connected to.
5) Each FMetasoundClassDescription contains a list of uint32s corresponding to a dependency in the document's Dependency list.
Low level (how individual elements of the FMetasoundDocument are accessed and edited)
1) We can't hand out weak pointers to the FMetasoundDocument itself unfortunately, but We are able to enforce a strict visitor pattern using the class FMetasound::Frontend:: FDescriptionAccessPoint. Currently, uobjects like UMetasoundNodeAsset can hand out weak pointers to the FDescriptionAccessPoint, and the FDescriptionAccessPoint can return raw pointers to specific elements in the FMetasoundDocument using FDescriptionAccessPoint::GetElementAtPath. Since individual elements in the document can get moved around for a number of reasons (for example, we added or removed a dependency to the document, or a node to a specific subgraph), we enforce that specific elements get accessed via a path that doesn't cache specific indices or references to specific nested structs.
2) Paths to specific elements are saved as FDescPaths, which is basically an array of enums/uint32s/strings. I added some syntactical sugar using bracket overloads to more easily build out paths to specific elements. At first this looks whacky, but after a few uses it ends up being pretty easy to write paths using chained bracket operators. All paths start at a document type, and autocomplete does most of the hard work using a set of enums.
For example, to access a specific node on a specific subgraph (which is the most complicated use case):
FDescPath()[Path::EFromDocument::ToDepenencies][TEXT("MyCoolSubgraph")][Path::EFromClass::ToGraph][Path::EFromGraph::ToNodes][12 /* or whatever node id I want */];
3) Finally, Metasound::Frontend:::TDescriptionPtr wraps a weak ptr to a FDescriptionAccessPoint and a FDescPath and ends up getting used like a weak pointer. Under the hood though, it's enforcing a visitor pattern.
4) All of the big branching statements for following a FDescPath to a specific element in the metasound document is hidden in FDescriptionAccessPoint::GoToNext, which uses TVariants to cache the current step and go to the next step.
5) Most of the complicated parts of implementing FGraphHandle is dynamically adding and removing dependencies to documents and classes behind the scenes when we add/remove new nodes.
6) I also added support for basic undo/redo callbacks, but haven't explicitly added em yet to any specific handles.
#rb rob.gay
[CL 13829763 by Ethan Geller in ue5-main branch]
2020-07-02 23:05:41 -04:00
}
}
2020-07-20 00:05:22 -04:00