// Copyright Epic Games, Inc. All Rights Reserved. #include "MetasoundFrontendBaseClasses.h" #include "MetasoundDataReference.h" #include "MetasoundFrontendRegistries.h" namespace Metasound { namespace Frontend { TMap& GetExternalNodeRegistry() { return FMetasoundFrontendRegistryContainer::Get()->GetExternalNodeRegistry(); } TUniquePtr ConstructInputNode(const FName& InInputType, FInputNodeConstructorParams&& InParams) { return FMetasoundFrontendRegistryContainer::Get()->ConstructInputNode(InInputType, MoveTemp(InParams)); } TUniquePtr ConstructOutputNode(const FName& InOutputType, const FOutputNodeConstructorParams& InParams) { return FMetasoundFrontendRegistryContainer::Get()->ConstructOutputNode(InOutputType, InParams); } TUniquePtr ConstructExternalNode(const FName& InNodeType, uint32 InNodeHash, const FNodeInitData& InInitData) { return FMetasoundFrontendRegistryContainer::Get()->ConstructExternalNode(InNodeType, InNodeHash, InInitData); } #if 0 void SetLiteralDescription(FMetasoundFrontendLiteral& OutDescription, bool InValue) { OutDescription.LiteralType = EMetasoundFrontendLiteralType::Bool; OutDescription.AsBool = InValue; OutDescription.AsInteger = 0; OutDescription.AsFloat = 0.0f; OutDescription.AsString.Empty(); OutDescription.AsUObject = nullptr; OutDescription.AsUObjectArray.Empty(); } void SetLiteralDescription(FMetasoundFrontendLiteral& OutDescription, int32 InValue) { OutDescription.LiteralType = EMetasoundFrontendLiteralType::Integer; OutDescription.AsBool = false; OutDescription.AsInteger = InValue; OutDescription.AsFloat = 0.0f; OutDescription.AsString.Empty(); OutDescription.AsUObject = nullptr; OutDescription.AsUObjectArray.Empty(); } void SetLiteralDescription(FMetasoundFrontendLiteral& OutDescription, float InValue) { OutDescription.LiteralType = EMetasoundFrontendLiteralType::Float; OutDescription.AsBool = false; OutDescription.AsInteger = 0; OutDescription.AsFloat = InValue; OutDescription.AsString.Empty(); OutDescription.AsUObject = nullptr; OutDescription.AsUObjectArray.Empty(); } void SetLiteralDescription(FMetasoundFrontendLiteral& OutDescription, const FString& InValue) { OutDescription.LiteralType = EMetasoundFrontendLiteralType::String; OutDescription.AsBool = false; OutDescription.AsInteger = 0; OutDescription.AsFloat = 0.0f; OutDescription.AsString = InValue; OutDescription.AsUObject = nullptr; OutDescription.AsUObjectArray.Empty(); } void SetLiteralDescription(FMetasoundFrontendLiteral& OutDescription, UObject* InValue) { OutDescription.LiteralType = EMetasoundFrontendLiteralType::UObject; OutDescription.AsBool = false; OutDescription.AsInteger = 0; OutDescription.AsFloat = 0.0f; OutDescription.AsString.Empty(); OutDescription.AsUObject = InValue; OutDescription.AsUObjectArray.Empty(); } void SetLiteralDescription(FMetasoundFrontendLiteral& OutDescription, const TArray& InValue) { OutDescription.LiteralType = EMetasoundFrontendLiteralType::UObjectArray; OutDescription.AsBool = false; OutDescription.AsInteger = 0; OutDescription.AsFloat = 0.0f; OutDescription.AsString.Empty(); OutDescription.AsUObject = nullptr; OutDescription.AsUObjectArray = InValue; } void ClearLiteralDescription(FMetasoundFrontendLiteral& OutDescription) { OutDescription.LiteralType = EMetasoundFrontendLiteralType::None; OutDescription.AsBool = false; OutDescription.AsInteger = 0; OutDescription.AsFloat = 0.0f; OutDescription.AsString.Empty(); OutDescription.AsUObject = nullptr; OutDescription.AsUObjectArray.Empty(); } #endif FLiteral GetLiteralParamForDataType(FName InDataType, const FMetasoundFrontendLiteral& InDescription) { EMetasoundFrontendLiteralType LiteralType = InDescription.Type; FMetasoundFrontendRegistryContainer* Registry = FMetasoundFrontendRegistryContainer::Get(); switch (LiteralType) { case EMetasoundFrontendLiteralType::None: { return GetDefaultParamForDataType(InDataType); } case EMetasoundFrontendLiteralType::Bool: { if (!Registry->DoesDataTypeSupportLiteralType(InDataType, ELiteralType::Boolean)) { return FLiteral::CreateInvalid(); } else { return FLiteral(InDescription.AsBool); } } case EMetasoundFrontendLiteralType::Float: { if (!Registry->DoesDataTypeSupportLiteralType(InDataType, ELiteralType::Float)) { return FLiteral::CreateInvalid(); } else { return FLiteral(InDescription.AsFloat); } } case EMetasoundFrontendLiteralType::Integer: { if (!Registry->DoesDataTypeSupportLiteralType(InDataType, ELiteralType::Integer)) { return FLiteral::CreateInvalid(); } else { return FLiteral(InDescription.AsInteger); } } case EMetasoundFrontendLiteralType::String: { if (!Registry->DoesDataTypeSupportLiteralType(InDataType, ELiteralType::String)) { return FLiteral::CreateInvalid(); } else { return FLiteral(InDescription.AsString); } } case EMetasoundFrontendLiteralType::UObject: { if (!Registry->DoesDataTypeSupportLiteralType(InDataType, ELiteralType::UObjectProxy)) { return FLiteral::CreateInvalid(); } else { return Registry->GenerateLiteralForUObject(InDataType, InDescription.AsUObject); } } case EMetasoundFrontendLiteralType::UObjectArray: { if (!Registry->DoesDataTypeSupportLiteralType(InDataType, ELiteralType::UObjectProxyArray)) { return FLiteral::CreateInvalid(); } else { return Registry->GenerateLiteralForUObjectArray(InDataType, InDescription.AsUObjectArray); } } case EMetasoundFrontendLiteralType::Invalid: default: { return FLiteral::CreateInvalid(); } } } bool DoesDataTypeSupportLiteralType(FName InDataType, EMetasoundFrontendLiteralType InLiteralType) { switch (InLiteralType) { 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: default: return false; } } bool DoesDataTypeSupportLiteralType(FName InDataType, ELiteralType InLiteralType) { FMetasoundFrontendRegistryContainer* Registry = FMetasoundFrontendRegistryContainer::Get(); return Registry->DoesDataTypeSupportLiteralType(InDataType, InLiteralType); } Metasound::FLiteral GetLiteralParam(const FMetasoundFrontendLiteral& InDescription) { EMetasoundFrontendLiteralType LiteralType = InDescription.Type; switch (LiteralType) { case EMetasoundFrontendLiteralType::Bool: { return FLiteral(InDescription.AsBool); } case EMetasoundFrontendLiteralType::Float: { return FLiteral(InDescription.AsFloat); } case EMetasoundFrontendLiteralType::Integer: { return FLiteral(InDescription.AsInteger); } case EMetasoundFrontendLiteralType::String: { return FLiteral(InDescription.AsString); } case EMetasoundFrontendLiteralType::UObject: case EMetasoundFrontendLiteralType::UObjectArray: case EMetasoundFrontendLiteralType::None: case EMetasoundFrontendLiteralType::Invalid: default: { return FLiteral::CreateInvalid(); } } } Metasound::FLiteral GetDefaultParamForDataType(FName InDataType) { FMetasoundFrontendRegistryContainer* Registry = FMetasoundFrontendRegistryContainer::Get(); ELiteralType DesiredArgType = Registry->GetDesiredLiteralTypeForDataType(InDataType); switch (DesiredArgType) { case Metasound::ELiteralType::Boolean: { return FLiteral(false); } case Metasound::ELiteralType::Integer: { return FLiteral(0); } case Metasound::ELiteralType::Float: { return FLiteral(0.0f); } case Metasound::ELiteralType::String: { return FLiteral(FString()); } case Metasound::ELiteralType::UObjectProxy: case Metasound::ELiteralType::UObjectProxyArray: case Metasound::ELiteralType::None: { return FLiteral(); } case Metasound::ELiteralType::Invalid: default: { return FLiteral::CreateInvalid(); } } } ITransactable::ITransactable(uint32 InUndoLimit, TWeakObjectPtr InOwningAsset /* = nullptr */) : UndoLimit(InUndoLimit) , OwningAsset(InOwningAsset) { } bool ITransactable::Undo() { if (UndoTransactableStack.Num() == 0) { return false; } if (TSharedPtr Owner = OwningTransactable.Pin()) { Owner->DiscardUndoFromOwnedTransactable(AsShared()); } TSharedPtr Transactable = UndoTransactableStack.Pop().Pin(); TSharedPtr 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 Owner = OwningTransactable.Pin()) { Owner->DiscardRedoFromOwnedTransactable(AsShared()); } TSharedPtr Transactable = RedoTransactableStack.Pop().Pin(); TSharedPtr 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 WeakThisPtr = AsShared(); UndoTransactableStack.Push(WeakThisPtr); if (TSharedPtr 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 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 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 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 InOwnedTransactable) { UndoTransactableStack.Push(MoveTemp(InOwnedTransactable)); RedoTransactableStack.Reset(); return true; } bool ITransactable::PushRedoFromOwnedTransactable(TWeakPtr InOwnedTransactable) { RedoTransactableStack.Push(MoveTemp(InOwnedTransactable)); DiscardUndoFromOwnedTransactable(InOwnedTransactable); return true; } #if 0 FDescriptionAccessPoint::FDescriptionAccessPoint(TAccessPtr InRootDocumentPtr) : RootDocumentPtr(InRootDocumentPtr) { } FMetasoundFrontendDocument& FDescriptionAccessPoint::GetRootChecked() const { check(RootDocumentPtr.IsValid()); return *RootDocumentPtr; } bool FDescriptionAccessPoint::GetDescriptionPtrFromPath(const FDescPath& InPathFromRoot, FMetasoundDescriptionPtr& OutPtr) const { if (!RootDocumentPtr.IsValid()) { return false; } FDescPath CurrentPath = InPathFromRoot; OutPtr.Set(RootDocumentPtr.Get()); FDescriptionUnwindStep CurrentStep = { OutPtr, Path::EDescType::Document }; while (CurrentPath.Num() != 0 && CurrentStep.Type != Path::EDescType::Invalid) { CurrentStep = GoToNext(CurrentPath, CurrentStep); } OutPtr = CurrentStep.DescriptionStructPtr; return CurrentStep.Type != Path::EDescType::Invalid; } FDescriptionAccessPoint::FDescriptionUnwindStep FDescriptionAccessPoint::GoToNextFromDocument(FMetasoundFrontendDocument& InDocument, FDescPath& InPath, const Path::FElement& InNext) const { check(RootDocumentPtr.IsValid()); switch (InNext.CurrentDescType) { case Path::EDescType::Document: { FDescriptionUnwindStep UnwindStep; UnwindStep.DescriptionStructPtr.Set(&InDocument); UnwindStep.Type = Path::EDescType::Document; return UnwindStep; } case Path::EDescType::Class: { FDescriptionUnwindStep UnwindStep; UnwindStep.DescriptionStructPtr.Set(&(InDocument.RootGraph)); 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(); } if (DependencyID == FMetasoundFrontendClass::RootClassID) { FDescriptionUnwindStep UnwindStep; UnwindStep.DescriptionStructPtr.Set(&(InDocument.RootGraph)); UnwindStep.Type = Path::EDescType::Class; return UnwindStep; } TArray& DependenciesList = InDocument.Dependencies; // Dependencies can be looked up by ID or by name. if (DependencyID != INDEX_NONE) { // Scan the dependencies list for the matching lookup ID. for (FMetasoundFrontendClass& Dependency : DependenciesList) { if (Dependency.UniqueID == DependencyID) { FDescriptionUnwindStep UnwindStep; UnwindStep.DescriptionStructPtr.Set(&Dependency); 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. for (FMetasoundFrontendClass& Dependency : DependenciesList) { if (Dependency.Metadata.NodeName == DependencyName) { FDescriptionUnwindStep UnwindStep; UnwindStep.DescriptionStructPtr.Set(&Dependency); 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(); } FDescriptionAccessPoint::FDescriptionUnwindStep FDescriptionAccessPoint::GoToNextFromClass(FMetasoundFrontendClass& InClassDescription, FDescPath& InPath, const Path::FElement& InNext) const { switch (InNext.CurrentDescType) { case Path::EDescType::Graph: { FDescriptionUnwindStep UnwindStep; UnwindStep.DescriptionStructPtr.Set(&(InClassDescription.Graph)); UnwindStep.Type = Path::EDescType::Graph; return UnwindStep; } case Path::EDescType::ClassDependencies: { FDescriptionUnwindStep UnwindStep; UnwindStep.DescriptionStructPtr.Set(&(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. TArray& InputsList = InClassDescription.Inputs; for (FMetasoundFrontendClassInput& Input : InputsList) { if (Input.Name == InputName) { FDescriptionUnwindStep UnwindStep; UnwindStep.DescriptionStructPtr.Set(&Input); 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. TArray& OutputsList = InClassDescription.Outputs; for (FMetasoundFrontendClassOutput& Output : OutputsList) { if (Output.Name == OutputElement.LookupName) { FDescriptionUnwindStep UnwindStep; UnwindStep.DescriptionStructPtr.Set(&Output); 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; UnwindStep.DescriptionStructPtr.Set(&(InClassDescription.Metadata)); 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(); } } } FDescriptionAccessPoint::FDescriptionUnwindStep FDescriptionAccessPoint::GoToNext(FDescPath& InPath, FDescriptionUnwindStep InElement) const { if (!ensureMsgf(InPath.Path.Num() != 0, TEXT("Attempted to unwind an empty path."))) { return FDescriptionUnwindStep::CreateInvalid(); } Path::FElement NextStep = InPath.Path[0]; InPath.Path.RemoveAt(0); switch (InElement.Type) { case Path::EDescType::Document: { FMetasoundFrontendDocument* Document = InElement.DescriptionStructPtr.Get(); return GoToNextFromDocument(*Document, InPath, NextStep); break; } case Path::EDescType::Class: { FMetasoundFrontendClass* ClassDescription = InElement.DescriptionStructPtr.Get(); return GoToNextFromClass(*ClassDescription, InPath, NextStep); break; } case Path::EDescType::Graph: { if (!ensureAlwaysMsgf(NextStep.CurrentDescType == Path::EDescType::Nodes, TEXT("Invalid path. the Graph description only contains the Nodes list."))) { return FDescriptionUnwindStep::CreateInvalid(); } FMetasoundFrontendGraph* GraphDescription = InElement.DescriptionStructPtr.Get(); if (!ensureAlwaysMsgf(InPath.Path.Num() != 0, TEXT("Incomplete path! path stopped at Nodes list without specifying a node ID."))) { return FDescriptionUnwindStep::CreateInvalid(); } 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."))) { return FDescriptionUnwindStep::CreateInvalid(); } int32 NodeID = NodeElement.LookupID; TArray& NodeList = GraphDescription->Nodes; for (FMetasoundFrontendNode& Node : NodeList) { if (Node.UniqueID == NodeID) { FDescriptionUnwindStep UnwindStep; UnwindStep.DescriptionStructPtr.Set(&Node); 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.")); return FDescriptionUnwindStep::CreateInvalid(); } 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.")); return FDescriptionUnwindStep::CreateInvalid(); } 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.")); return FDescriptionUnwindStep::CreateInvalid(); } } // if we ever hit this, we likely missed a return on one of these branches. checkNoEntry(); return FDescriptionUnwindStep::CreateInvalid(); } 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; } FDescPath Path::GetDependencyPath(int32 InDependencyID) { return FDescPath()[Path::EFromDocument::ToDependencies][InDependencyID]; } 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]; } 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; } 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; } #endif void InitializeFrontend() { FMetasoundFrontendRegistryContainer::Get()->InitializeFrontend(); } } }