Files
UnrealEngineUWP/Engine/Plugins/Runtime/Metasound/Source/MetasoundFrontend/Private/MetasoundFrontendTransform.cpp
hilda cruz 66ba34fea1 [Backout] - CL34112568 due to autotest error.
#rnx
[FYI] Rob.Gay
Original CL Desc
-----------------------------------------------------------------
More protections around accessing the wrong builder via the MetaSound builder registry by supplying the TopLevelPath of the asset if available when the registry has multiple conflicting entries due to bad content.
#rb helen.yang
#jira UE-216533
#rnx
[FYI] sondra.moyls

[CL 34114022 by hilda cruz in ue5-main branch]
2024-06-04 22:37:34 -04:00

978 lines
37 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MetasoundFrontendTransform.h"
#include "Algo/Transform.h"
#include "Interfaces/MetasoundFrontendInterface.h"
#include "Interfaces/MetasoundFrontendInterfaceRegistry.h"
#include "NodeTemplates/MetasoundFrontendNodeTemplateInput.h"
#include "MetasoundAccessPtr.h"
#include "MetasoundAssetBase.h"
#include "MetasoundFrontendDocument.h"
#include "MetasoundFrontendDocumentBuilder.h"
#include "MetasoundFrontendDocumentController.h"
#include "MetasoundFrontendDocumentIdGenerator.h"
#include "MetasoundFrontendRegistries.h"
#include "MetasoundFrontendSearchEngine.h"
#include "MetasoundLog.h"
#include "MetasoundTrace.h"
#include "Misc/App.h"
namespace Metasound
{
namespace Frontend
{
namespace DocumentTransform
{
void LogAutoUpdateWarning(const FString& LogMessage)
{
// These should eventually move back to warning on cook
// but are temporarily downgraded to prevent
// warnings on things like unused test content from
// blocking code checkins
if (IsRunningCookCommandlet())
{
UE_LOG(LogMetaSound, Display, TEXT("%s"), *LogMessage);
}
else
{
UE_LOG(LogMetaSound, Warning, TEXT("%s"), *LogMessage);
}
}
#if WITH_EDITOR
FGetNodeDisplayNameProjection NodeDisplayNameProjection;
void RegisterNodeDisplayNameProjection(FGetNodeDisplayNameProjection&& InNameProjection)
{
NodeDisplayNameProjection = MoveTemp(InNameProjection);
}
FGetNodeDisplayNameProjectionRef GetNodeDisplayNameProjection()
{
return NodeDisplayNameProjection;
}
#endif // WITH_EDITOR
} // namespace DocumentTransform
bool IDocumentTransform::Transform(FMetasoundFrontendDocument& InOutDocument) const
{
FDocumentAccessPtr DocAccessPtr = MakeAccessPtr<FDocumentAccessPtr>(InOutDocument.AccessPoint, InOutDocument);
return Transform(FDocumentController::CreateDocumentHandle(DocAccessPtr));
}
bool INodeTransform::Transform(const FGuid& InNodeID, FMetaSoundFrontendDocumentBuilder& OutBuilder) const
{
return false;
}
bool INodeTransform::Transform(FMetasoundFrontendNode& InOutNode) const
{
return false;
}
FModifyRootGraphInterfaces::FModifyRootGraphInterfaces(const TArray<FMetasoundFrontendInterface>& InInterfacesToRemove, const TArray<FMetasoundFrontendInterface>& InInterfacesToAdd)
: InterfacesToRemove(InInterfacesToRemove)
, InterfacesToAdd(InInterfacesToAdd)
{
Init();
}
FModifyRootGraphInterfaces::FModifyRootGraphInterfaces(const TArray<FMetasoundFrontendVersion>& InInterfaceVersionsToRemove, const TArray<FMetasoundFrontendVersion>& InInterfaceVersionsToAdd)
{
Algo::Transform(InInterfaceVersionsToRemove, InterfacesToRemove, [](const FMetasoundFrontendVersion& Version)
{
FMetasoundFrontendInterface Interface;
const bool bFromInterfaceFound = IInterfaceRegistry::Get().FindInterface(GetInterfaceRegistryKey(Version), Interface);
if (!ensureAlways(bFromInterfaceFound))
{
UE_LOG(LogMetaSound, Error, TEXT("Failed to find interface '%s' to remove"), *Version.ToString());
}
return Interface;
});
Algo::Transform(InInterfaceVersionsToAdd, InterfacesToAdd, [](const FMetasoundFrontendVersion& Version)
{
FMetasoundFrontendInterface Interface;
const bool bToInterfaceFound = IInterfaceRegistry::Get().FindInterface(GetInterfaceRegistryKey(Version), Interface);
if (!ensureAlways(bToInterfaceFound))
{
UE_LOG(LogMetaSound, Error, TEXT("Failed to find interface '%s' to add"), *Version.ToString());
}
return Interface;
});
Init();
}
#if WITH_EDITOR
void FModifyRootGraphInterfaces::SetDefaultNodeLocations(bool bInSetDefaultNodeLocations)
{
bSetDefaultNodeLocations = bInSetDefaultNodeLocations;
}
#endif // WITH_EDITOR
void FModifyRootGraphInterfaces::SetNamePairingFunction(const TFunction<bool(FName, FName)>& InNamePairingFunction)
{
// Reinit required to rebuild list of pairs
Init(&InNamePairingFunction);
}
bool FModifyRootGraphInterfaces::AddMissingVertices(FGraphHandle GraphHandle) const
{
for (const FInputData& InputData : InputsToAdd)
{
const FMetasoundFrontendClassInput& InputToAdd = InputData.Input;
GraphHandle->AddInputVertex(InputToAdd);
}
for (const FOutputData& OutputData : OutputsToAdd)
{
const FMetasoundFrontendClassOutput& OutputToAdd = OutputData.Output;
GraphHandle->AddOutputVertex(OutputToAdd);
}
return !InputsToAdd.IsEmpty() || !OutputsToAdd.IsEmpty();
}
void FModifyRootGraphInterfaces::Init(const TFunction<bool(FName, FName)>* InNamePairingFunction)
{
InputsToRemove.Reset();
InputsToAdd.Reset();
OutputsToRemove.Reset();
OutputsToAdd.Reset();
PairedInputs.Reset();
PairedOutputs.Reset();
for (const FMetasoundFrontendInterface& FromInterface : InterfacesToRemove)
{
InputsToRemove.Append(FromInterface.Inputs);
OutputsToRemove.Append(FromInterface.Outputs);
}
// This function combines all the inputs of all interfaces into one input list and ptrs to their originating interfaces.
// The interface ptr will be used to query the interface for required validations on inputs. Interfaces define required inputs (and possibly other validation requirements).
for (const FMetasoundFrontendInterface& ToInterface : InterfacesToAdd)
{
TArray<FInputData> NewInputDataArray;
for (const FMetasoundFrontendClassInput& Input : ToInterface.Inputs)
{
FInputData NewData;
NewData.Input = Input;
NewData.InputInterface = &ToInterface;
NewInputDataArray.Add(NewData);
}
InputsToAdd.Append(NewInputDataArray);
TArray<FOutputData> NewOutputDataArray;
for (const FMetasoundFrontendClassOutput& Output : ToInterface.Outputs)
{
FOutputData NewData;
NewData.Output = Output;
NewData.OutputInterface = &ToInterface;
NewOutputDataArray.Add(NewData);
}
OutputsToAdd.Append(NewOutputDataArray);
}
// Iterate in reverse to allow removal from `InputsToAdd`
for (int32 AddIndex = InputsToAdd.Num() - 1; AddIndex >= 0; AddIndex--)
{
const FMetasoundFrontendClassVertex& VertexToAdd = InputsToAdd[AddIndex].Input;
const int32 RemoveIndex = InputsToRemove.IndexOfByPredicate([&](const FMetasoundFrontendClassVertex& VertexToRemove)
{
if (VertexToAdd.TypeName != VertexToRemove.TypeName)
{
return false;
}
if (InNamePairingFunction && *InNamePairingFunction)
{
return (*InNamePairingFunction)(VertexToAdd.Name, VertexToRemove.Name);
}
FName ParamA;
FName ParamB;
FName Namespace;
VertexToAdd.SplitName(Namespace, ParamA);
VertexToRemove.SplitName(Namespace, ParamB);
return ParamA == ParamB;
});
if (INDEX_NONE != RemoveIndex)
{
PairedInputs.Add(FVertexPair{InputsToRemove[RemoveIndex], InputsToAdd[AddIndex].Input});
InputsToRemove.RemoveAtSwap(RemoveIndex);
InputsToAdd.RemoveAtSwap(AddIndex);
}
}
// Iterate in reverse to allow removal from `OutputsToAdd`
for (int32 AddIndex = OutputsToAdd.Num() - 1; AddIndex >= 0; AddIndex--)
{
const FMetasoundFrontendClassVertex& VertexToAdd = OutputsToAdd[AddIndex].Output;
const int32 RemoveIndex = OutputsToRemove.IndexOfByPredicate([&](const FMetasoundFrontendClassVertex& VertexToRemove)
{
if (VertexToAdd.TypeName != VertexToRemove.TypeName)
{
return false;
}
if (InNamePairingFunction && *InNamePairingFunction)
{
return (*InNamePairingFunction)(VertexToAdd.Name, VertexToRemove.Name);
}
FName ParamA;
FName ParamB;
FName Namespace;
VertexToAdd.SplitName(Namespace, ParamA);
VertexToRemove.SplitName(Namespace, ParamB);
return ParamA == ParamB;
});
if (INDEX_NONE != RemoveIndex)
{
PairedOutputs.Add(FVertexPair { OutputsToRemove[RemoveIndex], OutputsToAdd[AddIndex].Output });
OutputsToRemove.RemoveAtSwap(RemoveIndex);
OutputsToAdd.RemoveAtSwap(AddIndex);
}
}
}
bool FModifyRootGraphInterfaces::RemoveUnsupportedVertices(FGraphHandle GraphHandle) const
{
// Remove unsupported inputs
for (const FMetasoundFrontendClassVertex& InputToRemove : InputsToRemove)
{
if (const FMetasoundFrontendClassInput* ClassInput = GraphHandle->FindClassInputWithName(InputToRemove.Name).Get())
{
if (FMetasoundFrontendClassInput::IsFunctionalEquivalent(*ClassInput, InputToRemove))
{
GraphHandle->RemoveInputVertex(InputToRemove.Name);
}
}
}
// Remove unsupported outputs
for (const FMetasoundFrontendClassVertex& OutputToRemove : OutputsToRemove)
{
if (const FMetasoundFrontendClassOutput* ClassOutput = GraphHandle->FindClassOutputWithName(OutputToRemove.Name).Get())
{
if (FMetasoundFrontendClassOutput::IsFunctionalEquivalent(*ClassOutput, OutputToRemove))
{
GraphHandle->RemoveOutputVertex(OutputToRemove.Name);
}
}
}
return !InputsToRemove.IsEmpty() || !OutputsToRemove.IsEmpty();
}
bool FModifyRootGraphInterfaces::SwapPairedVertices(FGraphHandle GraphHandle) const
{
for (const FVertexPair& InputPair : PairedInputs)
{
const FMetasoundFrontendClassVertex& OriginalVertex = InputPair.Get<0>();
FMetasoundFrontendClassInput NewVertex = InputPair.Get<1>();
// Cache off node locations and connections to push to new node
TMap<FGuid, FVector2D> Locations;
TArray<FInputHandle> ConnectedInputs;
if (const FMetasoundFrontendClassInput* ClassInput = GraphHandle->FindClassInputWithName(OriginalVertex.Name).Get())
{
if (FMetasoundFrontendVertex::IsFunctionalEquivalent(*ClassInput, OriginalVertex))
{
NewVertex.DefaultLiteral = ClassInput->DefaultLiteral;
NewVertex.NodeID = ClassInput->NodeID;
FNodeHandle OriginalInputNode = GraphHandle->GetInputNodeWithName(OriginalVertex.Name);
#if WITH_EDITOR
Locations = OriginalInputNode->GetNodeStyle().Display.Locations;
#endif // WITH_EDITOR
FOutputHandle OriginalInputNodeOutput = OriginalInputNode->GetOutputWithVertexName(OriginalVertex.Name);
ConnectedInputs = OriginalInputNodeOutput->GetConnectedInputs();
GraphHandle->RemoveInputVertex(OriginalVertex.Name);
}
}
FNodeHandle NewInputNode = GraphHandle->AddInputVertex(NewVertex);
#if WITH_EDITOR
// Copy prior node locations
if (!Locations.IsEmpty())
{
FMetasoundFrontendNodeStyle Style = NewInputNode->GetNodeStyle();
Style.Display.Locations = Locations;
NewInputNode->SetNodeStyle(Style);
}
#endif // WITH_EDITOR
// Copy prior node connections
FOutputHandle OutputHandle = NewInputNode->GetOutputWithVertexName(NewVertex.Name);
for (FInputHandle& ConnectedInput : ConnectedInputs)
{
OutputHandle->Connect(*ConnectedInput);
}
}
// Swap paired outputs.
for (const FVertexPair& OutputPair : PairedOutputs)
{
const FMetasoundFrontendClassVertex& OriginalVertex = OutputPair.Get<0>();
FMetasoundFrontendClassVertex NewVertex = OutputPair.Get<1>();
#if WITH_EDITOR
// Cache off node locations to push to new node
// Default add output node to origin.
TMap<FGuid, FVector2D> Locations;
Locations.Add(FGuid(), FVector2D { 0.f, 0.f });
#endif // WITH_EDITOR
FOutputHandle ConnectedOutput = IOutputController::GetInvalidHandle();
if (const FMetasoundFrontendClassOutput* ClassOutput = GraphHandle->FindClassOutputWithName(OriginalVertex.Name).Get())
{
if (FMetasoundFrontendVertex::IsFunctionalEquivalent(*ClassOutput, OriginalVertex))
{
NewVertex.NodeID = ClassOutput->NodeID;
#if WITH_EDITOR
// Interface members do not serialize text to avoid localization
// mismatches between assets and interfaces defined in code.
NewVertex.Metadata.SetSerializeText(false);
#endif // WITH_EDITOR
FNodeHandle OriginalOutputNode = GraphHandle->GetOutputNodeWithName(OriginalVertex.Name);
#if WITH_EDITOR
Locations = OriginalOutputNode->GetNodeStyle().Display.Locations;
#endif // WITH_EDITOR
FInputHandle Input = OriginalOutputNode->GetInputWithVertexName(OriginalVertex.Name);
ConnectedOutput = Input->GetConnectedOutput();
GraphHandle->RemoveOutputVertex(OriginalVertex.Name);
}
}
FNodeHandle NewOutputNode = GraphHandle->AddOutputVertex(NewVertex);
#if WITH_EDITOR
if (Locations.Num() > 0)
{
FMetasoundFrontendNodeStyle Style = NewOutputNode->GetNodeStyle();
Style.Display.Locations = Locations;
NewOutputNode->SetNodeStyle(Style);
}
#endif // WITH_EDITOR
// Copy prior node connections
FInputHandle InputHandle = NewOutputNode->GetInputWithVertexName(NewVertex.Name);
ConnectedOutput->Connect(*InputHandle);
}
return !PairedInputs.IsEmpty() || !PairedOutputs.IsEmpty();
}
bool FModifyRootGraphInterfaces::Transform(FDocumentHandle InDocument) const
{
bool bDidEdit = false;
FGraphHandle GraphHandle = InDocument->GetRootGraph();
if (ensure(GraphHandle->IsValid()))
{
bDidEdit |= UpdateInterfacesInternal(InDocument);
const bool bAddedVertices = AddMissingVertices(GraphHandle);
bDidEdit |= bAddedVertices;
bDidEdit |= SwapPairedVertices(GraphHandle);
bDidEdit |= RemoveUnsupportedVertices(GraphHandle);
#if WITH_EDITORONLY_DATA
if (bAddedVertices && bSetDefaultNodeLocations)
{
UpdateAddedVertexNodePositions(GraphHandle);
}
#endif // WITH_EDITORONLY_DATA
}
return bDidEdit;
}
bool FModifyRootGraphInterfaces::Transform(FMetasoundFrontendDocument& InOutDocument) const
{
{
// Mutation of a document via the soft deprecated access ptr/controller system is not tracked by
// the builder registry, so the document cache is invalidated here. It is discouraged to mutate
// documents using controllers at this point as it disconnects delegates applied at the object level
// (i.e. disconnecting changes to any MetaSound instances being auditioned).
const FMetasoundFrontendClassName& ClassName = InOutDocument.RootGraph.Metadata.GetClassName();
if (FMetaSoundFrontendDocumentBuilder* Builder = IDocumentBuilderRegistry::GetChecked().FindBuilder(ClassName))
{
Builder->Reload();
}
}
FDocumentAccessPtr DocAccessPtr = MakeAccessPtr<FDocumentAccessPtr>(InOutDocument.AccessPoint, InOutDocument);
return Transform(FDocumentController::CreateDocumentHandle(DocAccessPtr));
}
bool FModifyRootGraphInterfaces::UpdateInterfacesInternal(FDocumentHandle DocumentHandle) const
{
for (const FMetasoundFrontendInterface& Interface : InterfacesToRemove)
{
DocumentHandle->RemoveInterfaceVersion(Interface.Version);
}
for (const FMetasoundFrontendInterface& Interface : InterfacesToAdd)
{
DocumentHandle->AddInterfaceVersion(Interface.Version);
}
return !InterfacesToRemove.IsEmpty() || !InterfacesToAdd.IsEmpty();
}
#if WITH_EDITORONLY_DATA
void FModifyRootGraphInterfaces::UpdateAddedVertexNodePositions(FGraphHandle GraphHandle) const
{
auto SortAndPlaceMemberNodes = [&GraphHandle](EMetasoundFrontendClassType ClassType, TSet<FName>& AddedNames, TFunctionRef<int32(const FVertexName&)> InGetSortOrder)
{
// Add graph member nodes by sort order
TSortedMap<int32, FNodeHandle> SortOrderToName;
GraphHandle->IterateNodes([&GraphHandle, &SortOrderToName, &InGetSortOrder](FNodeHandle NodeHandle)
{
const int32 Index = InGetSortOrder(NodeHandle->GetNodeName());
SortOrderToName.Add(Index, NodeHandle);
}, ClassType);
// Prime the first location as an offset prior to an existing location (as provided by a swapped member)
// to avoid placing away from user's active area if possible.
FVector2D NextLocation = { 0.0f, 0.0f };
{
int32 NumBeforeDefined = 1;
for (const TPair<int32, FNodeHandle>& Pair : SortOrderToName) //-V1078
{
const FConstNodeHandle& NodeHandle = Pair.Value;
const FName NodeName = NodeHandle->GetNodeName();
if (AddedNames.Contains(NodeName))
{
NumBeforeDefined++;
}
else
{
const TMap<FGuid, FVector2D>& Locations = NodeHandle->GetNodeStyle().Display.Locations;
if (!Locations.IsEmpty())
{
for (const TPair<FGuid, FVector2D>& Location : Locations)
{
NextLocation = Location.Value - (NumBeforeDefined * DisplayStyle::NodeLayout::DefaultOffsetY);
break;
}
break;
}
}
}
}
// Iterate through sorted map in sequence, slotting in new locations after existing swapped nodes with predefined locations.
for (TPair<int32, FNodeHandle>& Pair : SortOrderToName) //-V1078
{
FNodeHandle& NodeHandle = Pair.Value;
const FName NodeName = NodeHandle->GetNodeName();
if (AddedNames.Contains(NodeName))
{
FMetasoundFrontendNodeStyle NewStyle = NodeHandle->GetNodeStyle();
NewStyle.Display.Locations.Add(FGuid(), NextLocation);
NextLocation += DisplayStyle::NodeLayout::DefaultOffsetY;
NodeHandle->SetNodeStyle(NewStyle);
}
else
{
for (const TPair<FGuid, FVector2D>& Location : NodeHandle->GetNodeStyle().Display.Locations)
{
NextLocation = Location.Value + DisplayStyle::NodeLayout::DefaultOffsetY;
}
}
}
};
// Sort/Place Inputs
{
TSet<FName> AddedNames;
Algo::Transform(InputsToAdd, AddedNames, [](const FInputData& InputData) { return InputData.Input.Name; });
auto GetInputSortOrder = [&GraphHandle](const FVertexName& InVertexName) { return GraphHandle->GetSortOrderIndexForInput(InVertexName); };
SortAndPlaceMemberNodes(EMetasoundFrontendClassType::Input, AddedNames, GetInputSortOrder);
}
// Sort/Place Outputs
{
TSet<FName> AddedNames;
Algo::Transform(OutputsToAdd, AddedNames, [](const FOutputData& OutputData) { return OutputData.Output.Name; });
auto GetOutputSortOrder = [&GraphHandle](const FVertexName& InVertexName) { return GraphHandle->GetSortOrderIndexForOutput(InVertexName); };
SortAndPlaceMemberNodes(EMetasoundFrontendClassType::Output, AddedNames, GetOutputSortOrder);
}
}
#endif // WITH_EDITOR
bool FAutoUpdateRootGraph::Transform(FDocumentHandle InDocument) const
{
METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(FAutoUpdateRootGraph::Transform);
bool bDidEdit = false;
FMetasoundAssetBase* PresetReferencedMetaSoundAsset = nullptr;
TArray<TPair<FNodeHandle, FMetasoundFrontendVersionNumber>> NodesToUpdate;
FGraphHandle RootGraph = InDocument->GetRootGraph();
const bool bIsPreset = RootGraph->GetGraphPresetOptions().bIsPreset;
RootGraph->IterateNodes([&](FNodeHandle NodeHandle)
{
using namespace Metasound::Frontend;
const FMetasoundFrontendClassMetadata& ClassMetadata = NodeHandle->GetClassMetadata();
const FNodeRegistryKey RegistryKey(ClassMetadata);
if (FMetasoundAssetBase* ReferencedMetaSoundAsset = IMetaSoundAssetManager::GetChecked().FindAsset(RegistryKey))
{
if (bIsPreset)
{
PresetReferencedMetaSoundAsset = ReferencedMetaSoundAsset;
}
}
else
{
if (bIsPreset)
{
UE_LOG(LogMetaSound, Error, TEXT("Auto-Updating preset '%s' failed: Referenced class '%s' missing."), *DebugAssetPath, *ClassMetadata.GetClassName().ToString());
return;
}
}
FClassInterfaceUpdates InterfaceUpdates;
if (!NodeHandle->CanAutoUpdate(InterfaceUpdates))
{
return;
}
// Check if a updated minor version exists.
FMetasoundFrontendClass ClassWithHighestMinorVersion;
const bool bFoundClassInSearchEngine = Frontend::ISearchEngine::Get().FindClassWithHighestMinorVersion(ClassMetadata.GetClassName(), ClassMetadata.GetVersion().Major, ClassWithHighestMinorVersion);
if (bFoundClassInSearchEngine && (ClassWithHighestMinorVersion.Metadata.GetVersion() > ClassMetadata.GetVersion()))
{
const FMetasoundFrontendVersionNumber UpdateVersion = ClassWithHighestMinorVersion.Metadata.GetVersion();
UE_LOG(LogMetaSound, Display, TEXT("Auto-Updating '%s' node class '%s': Newer version '%s' found."), *DebugAssetPath, *ClassMetadata.GetClassName().ToString(), *UpdateVersion.ToString());
NodesToUpdate.Add(TPair<FNodeHandle, FMetasoundFrontendVersionNumber>(NodeHandle, UpdateVersion));
}
else if (InterfaceUpdates.ContainsChanges())
{
const FMetasoundFrontendVersionNumber UpdateVersion = ClassMetadata.GetVersion();
UE_LOG(LogMetaSound, Display, TEXT("Auto-Updating '%s' node class '%s (%s)': Interface change detected."), *DebugAssetPath, *ClassMetadata.GetClassName().ToString(), *UpdateVersion.ToString());
NodesToUpdate.Add(TPair<FNodeHandle, FMetasoundFrontendVersionNumber>(NodeHandle, UpdateVersion));
}
// Only update the node at this point if editor data is loaded. If it isn't and their are no interface
// changes but auto-update returned it was eligible, then the auto-update only contains cosmetic changes.
#if WITH_EDITORONLY_DATA
else
{
NodesToUpdate.Add(TPair<FNodeHandle, FMetasoundFrontendVersionNumber>(NodeHandle, ClassMetadata.GetVersion()));
}
#endif // WITH_EDITORONLY_DATA
}, EMetasoundFrontendClassType::External);
if (PresetReferencedMetaSoundAsset)
{
if (bIsPreset)
{
bDidEdit |= FRebuildPresetRootGraph(PresetReferencedMetaSoundAsset->GetDocumentHandle()).Transform(InDocument);
if (bDidEdit)
{
FMetasoundFrontendClassMetadata PresetMetadata = InDocument->GetRootGraphClass().Metadata;
PresetMetadata.SetType(EMetasoundFrontendClassType::External);
const FNodeRegistryKey RegistryKey(PresetMetadata);
FMetasoundAssetBase* PresetMetaSoundAsset = IMetaSoundAssetManager::GetChecked().TryLoadAssetFromKey(RegistryKey);
if (ensure(PresetMetaSoundAsset))
{
TScriptInterface<IMetaSoundDocumentInterface> PresetInterface = PresetMetaSoundAsset->GetOwningAsset();
check(PresetInterface);
PresetInterface->ConformObjectToDocument();
}
InDocument->RemoveUnreferencedDependencies();
InDocument->SynchronizeDependencyMetadata();
}
}
}
else
{
using FVertexNameAndType = INodeController::FVertexNameAndType;
bDidEdit |= !NodesToUpdate.IsEmpty();
for (const TPair<FNodeHandle, FMetasoundFrontendVersionNumber>& Pair : NodesToUpdate)
{
FNodeHandle ExistingNode = Pair.Key;
FMetasoundFrontendVersionNumber InitialVersion = ExistingNode->GetClassMetadata().GetVersion();
TArray<FVertexNameAndType> DisconnectedInputs;
TArray<FVertexNameAndType> DisconnectedOutputs;
FNodeHandle NewNode = ExistingNode->ReplaceWithVersion(Pair.Value, &DisconnectedInputs, &DisconnectedOutputs);
// Log warnings for any disconnections
if (bLogWarningOnDroppedConnection)
{
if ((DisconnectedInputs.Num() > 0) || (DisconnectedOutputs.Num() > 0))
{
const FString NodeClassName = NewNode->GetClassMetadata().GetClassName().ToString();
const FString NewClassVersion = Pair.Value.ToString();
for (const FVertexNameAndType& InputPin : DisconnectedInputs)
{
DocumentTransform::LogAutoUpdateWarning(FString::Printf(TEXT("Auto-Updating '%s' node class '%s (%s)': Previously connected input '%s' with data type '%s' no longer exists."), *DebugAssetPath, *NodeClassName, *NewClassVersion, *InputPin.Get<0>().ToString(), *InputPin.Get<1>().ToString()));
}
for (const FVertexNameAndType& OutputPin : DisconnectedOutputs)
{
DocumentTransform::LogAutoUpdateWarning(FString::Printf(TEXT("Auto-Updating '%s' node class '%s (%s)': Previously connected output '%s' with data type '%s' no longer exists."), *DebugAssetPath, *NodeClassName, *NewClassVersion, *OutputPin.Get<0>().ToString(), *OutputPin.Get<1>().ToString()));
}
}
}
}
InDocument->RemoveUnreferencedDependencies();
InDocument->SynchronizeDependencyMetadata();
}
return bDidEdit;
}
bool FRebuildPresetRootGraph::Transform(FDocumentHandle InDocument) const
{
METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(Metasound::Frontend::FRebuildPresetRootGraph::Transform);
FGraphHandle PresetGraphHandle = InDocument->GetRootGraph();
if (!ensure(PresetGraphHandle->IsValid()))
{
return false;
}
// Callers of this transform should check that the graph is supposed to
// be managed externally before calling this transform. If a scenario
// arises where this transform is used outside of AutoUpdate, then this
// early exist should be removed as it's mostly here to protect against
// accidental manipulation of metasound graphs.
if (!ensure(PresetGraphHandle->GetGraphPresetOptions().bIsPreset))
{
return false;
}
FConstGraphHandle ReferencedGraphHandle = ReferencedDocument->GetRootGraph();
if (!ensure(ReferencedGraphHandle->IsValid()))
{
return false;
}
// Determine the inputs and outputs needed in the wrapping graph. Also
// cache any exiting literals that have been set on the wrapping graph.
TSet<FName> InputsInheritingDefault;
TArray<FMetasoundFrontendClassInput> ClassInputs = GenerateRequiredClassInputs(InDocument, PresetGraphHandle, InputsInheritingDefault);
TArray<FMetasoundFrontendClassOutput> ClassOutputs = GenerateRequiredClassOutputs(InDocument, PresetGraphHandle);
FGuid PresetNodeID;
PresetGraphHandle->IterateConstNodes([InPresetNodeID = &PresetNodeID](FConstNodeHandle PresetNodeHandle)
{
*InPresetNodeID = PresetNodeHandle->GetID();
}, EMetasoundFrontendClassType::External);
if (!PresetNodeID.IsValid())
{
// This ID was originally being set to FGuid::NewGuid.
// If you were reliant on that ID, please resave the asset so it is serialized with a valid ID
PresetNodeID = InDocument->GetRootGraph()->GetClassID();
}
// Clear the root graph so it can be rebuilt.
PresetGraphHandle->ClearGraph();
// Ensure preset interfaces match those found in referenced graph. Referenced graph is assumed to be
// well-formed (i.e. all inputs/outputs/environment variables declared by interfaces are present, and
// of proper name & data type).
const TSet<FMetasoundFrontendVersion>& RefInterfaceVersions = ReferencedDocument->GetInterfaceVersions();
for (const FMetasoundFrontendVersion& Version : RefInterfaceVersions)
{
InDocument->AddInterfaceVersion(Version);
}
// Add referenced node
FMetasoundFrontendClassMetadata ReferencedClassMetadata = ReferencedGraphHandle->GetGraphMetadata();
// Swap type on look-up as it will be referenced as an externally defined class relative to the new Preset asset
ReferencedClassMetadata.SetType(EMetasoundFrontendClassType::External);
FNodeHandle ReferencedNodeHandle = PresetGraphHandle->AddNode(ReferencedClassMetadata, PresetNodeID);
#if WITH_EDITOR
// Set node location.
FMetasoundFrontendNodeStyle RefNodeStyle;
// Offset to be to the right of input nodes
const FGuid EdNodeGuid = FGuid::NewGuid(); // EdNodes are now never serialized and are transient, so just assign here
RefNodeStyle.Display.Locations.Add(EdNodeGuid, DisplayStyle::NodeLayout::DefaultOffsetX);
ReferencedNodeHandle->SetNodeStyle(RefNodeStyle);
#endif // WITH_EDITOR
// Connect parent graph to referenced graph
PresetGraphHandle->SetInputsInheritingDefault(MoveTemp(InputsInheritingDefault));
AddAndConnectInputs(ClassInputs, PresetGraphHandle, ReferencedNodeHandle);
AddAndConnectOutputs(ClassOutputs, PresetGraphHandle, ReferencedNodeHandle);
return true;
}
FRebuildPresetRootGraph::FRebuildPresetRootGraph(const FMetasoundFrontendDocument& InReferencedDocument)
{
// TODO: Swap implementation to not use access pointers/controllers
ReferencedDocument = IDocumentController::CreateDocumentHandle(InReferencedDocument);
}
bool FRebuildPresetRootGraph::Transform(FMetasoundFrontendDocument& InDocument) const
{
// TODO: Swap implementation to not use access pointers/controllers
return Transform(IDocumentController::CreateDocumentHandle(InDocument));
}
void FRebuildPresetRootGraph::AddAndConnectInputs(const TArray<FMetasoundFrontendClassInput>& InClassInputs, FGraphHandle& InPresetGraphHandle, FNodeHandle& InReferencedNode) const
{
// Add inputs and space appropriately
FVector2D InputNodeLocation = FVector2D::ZeroVector;
FConstGraphHandle ReferencedGraphHandle = ReferencedDocument->GetRootGraph();
const INodeTemplate* InputTemplate = INodeTemplateRegistry::Get().FindTemplate(FInputNodeTemplate::ClassName);
check(InputTemplate);
TArray<FNodeHandle> NodeHandles;
for (const FMetasoundFrontendClassInput& ClassInput : InClassInputs)
{
FNodeHandle InputNode = InPresetGraphHandle->AddInputVertex(ClassInput);
if (ensure(InputNode->IsValid()))
{
// Connect input node to corresponding referencing node.
FOutputHandle OutputToConnect = InputNode->GetOutputWithVertexName(ClassInput.Name);
FInputHandle InputToConnect = InReferencedNode->GetInputWithVertexName(ClassInput.Name);
ensure(OutputToConnect->Connect(*InputToConnect));
NodeHandles.Add(MoveTemp(InputNode));
// template node takes on data type of concrete input node's output type
const FName DataType = InputNode->GetOutputs().Last()->GetDataType();
FNodeTemplateGenerateInterfaceParams Params { { }, { DataType }};
FNodeHandle TemplateNodeHandle = InPresetGraphHandle->AddTemplateNode(*InputTemplate, MoveTemp(Params));
TemplateNodeHandle->GetInputs().Last()->Connect(*OutputToConnect);
TemplateNodeHandle->GetOutputs().Last()->Connect(*InputToConnect);
}
}
#if WITH_EDITOR
// Sort before adding nodes to graph layout & copy to preset (must be done after all
// inputs/outputs are added but before setting locations to propagate effectively)
FMetasoundFrontendInterfaceStyle Style = ReferencedGraphHandle->GetInputStyle();
InPresetGraphHandle->SetInputStyle(Style);
Style.SortDefaults(NodeHandles, DocumentTransform::GetNodeDisplayNameProjection());
InputNodeLocation = FVector2D::ZeroVector;
for (const FNodeHandle& NodeHandle : NodeHandles)
{
// Create input template node and set location
FMetasoundFrontendNodeStyle NodeStyle;
const FGuid EdNodeGuid = FGuid::NewGuid(); // EdNodes are now never serialized and are transient, so just assign here
NodeStyle.Display.Locations.Add(EdNodeGuid, InputNodeLocation);
FOutputHandle InputNodeOutputHandle = NodeHandle->GetOutputs().Last();
FInputHandle InputTemplateNodeInputHandle = InputNodeOutputHandle->GetConnectedInputs().Last();
FNodeHandle TemplateNodeHandle = InputTemplateNodeInputHandle->GetOwningNode();
TemplateNodeHandle->SetNodeStyle(NodeStyle);
InputNodeLocation += DisplayStyle::NodeLayout::DefaultOffsetY;
}
#endif // WITH_EDITOR
}
void FRebuildPresetRootGraph::AddAndConnectOutputs(const TArray<FMetasoundFrontendClassOutput>& InClassOutputs, FGraphHandle& InPresetGraphHandle, FNodeHandle& InReferencedNode) const
{
// Add outputs and space appropriately
FVector2D OutputNodeLocation = (2 * DisplayStyle::NodeLayout::DefaultOffsetX);
FConstGraphHandle ReferencedGraphHandle = ReferencedDocument->GetRootGraph();
TArray<FNodeHandle> NodeHandles;
for (const FMetasoundFrontendClassOutput& ClassOutput : InClassOutputs)
{
FNodeHandle OutputNode = InPresetGraphHandle->AddOutputVertex(ClassOutput);
if (ensure(OutputNode->IsValid()))
{
// Connect input node to corresponding referenced node.
FInputHandle InputToConnect = OutputNode->GetInputWithVertexName(ClassOutput.Name);
FOutputHandle OutputToConnect = InReferencedNode->GetOutputWithVertexName(ClassOutput.Name);
ensure(InputToConnect->Connect(*OutputToConnect));
NodeHandles.Add(MoveTemp(OutputNode));
}
}
#if WITH_EDITOR
// Sort before adding nodes to graph layout & copy to preset (must be done after all
// inputs/outputs are added but before setting locations to propagate effectively)
FMetasoundFrontendInterfaceStyle Style = ReferencedGraphHandle->GetOutputStyle();
InPresetGraphHandle->SetOutputStyle(Style);
Style.SortDefaults(NodeHandles, DocumentTransform::GetNodeDisplayNameProjection());
// Set output node location
for (const FNodeHandle& OutputNode : NodeHandles)
{
FMetasoundFrontendNodeStyle NodeStyle;
const FGuid EdNodeGuid = FGuid::NewGuid(); // EdNodes are now never serialized and are transient, so just assign here
NodeStyle.Display.Locations.Add(EdNodeGuid, OutputNodeLocation);
OutputNode->SetNodeStyle(NodeStyle);
OutputNodeLocation += DisplayStyle::NodeLayout::DefaultOffsetY;
}
#endif // WITH_EDITOR
}
TArray<FMetasoundFrontendClassInput> FRebuildPresetRootGraph::GenerateRequiredClassInputs(FDocumentHandle& InDocumentHandle, const FConstGraphHandle& InPresetGraph, TSet<FName>& OutInputsInheritingDefault) const
{
TArray<FMetasoundFrontendClassInput> ClassInputs;
FConstGraphHandle ReferencedGraph = ReferencedDocument->GetRootGraph();
// Iterate through all input nodes of referenced graph
ReferencedGraph->IterateConstNodes([&](FConstNodeHandle InputNode)
{
const FName NodeName = InputNode->GetNodeName();
FConstInputHandle Input = InputNode->GetConstInputWithVertexName(NodeName);
if (ensure(Input->IsValid()))
{
FMetasoundFrontendClassInput ClassInput;
ClassInput.Name = NodeName;
ClassInput.TypeName = Input->GetDataType();
ClassInput.AccessType = Input->GetVertexAccessType();
#if WITH_EDITOR
ClassInput.Metadata.SetDescription(InputNode->GetDescription());
ClassInput.Metadata.SetDisplayName(Input->GetMetadata().GetDisplayName());
#endif // WITH_EDITOR
FDocumentAccessPtr DocumentPtr = InDocumentHandle->GetDocumentPtr();
const FMetasoundFrontendDocument* Document = DocumentPtr.Get();
check(Document);
ClassInput.VertexID = FDocumentIDGenerator::Get().CreateVertexID(*Document);;
if (const FMetasoundFrontendClassInput* ExistingClassInput = InPresetGraph->FindClassInputWithName(NodeName).Get())
{
ClassInput.NodeID = ExistingClassInput->NodeID;
}
if (InPresetGraph->ContainsInputVertex(NodeName, ClassInput.TypeName))
{
// If the input vertex already exists in the parent graph,
// check if parent should be used or not from set of managed
// input names.
if (InPresetGraph->GetInputsInheritingDefault().Contains(NodeName))
{
const FGuid ReferencedVertexID = ReferencedGraph->GetVertexIDForInputVertex(NodeName);
ClassInput.DefaultLiteral = ReferencedGraph->GetDefaultInput(ReferencedVertexID);
}
else
{
FGuid VertexID = InPresetGraph->GetVertexIDForInputVertex(NodeName);
ClassInput.DefaultLiteral = InPresetGraph->GetDefaultInput(VertexID);
}
}
else
{
// If the input vertex does not exist on the parent graph,
// then it is a new vertex and should use the default value
// of the referenced graph.
const FGuid ReferencedVertexID = ReferencedGraph->GetVertexIDForInputVertex(NodeName);
ClassInput.DefaultLiteral = ReferencedGraph->GetDefaultInput(ReferencedVertexID);
}
ClassInputs.Add(MoveTemp(ClassInput));
}
}, EMetasoundFrontendClassType::Input);
// Fill new managed inputs set with names of all class inputs & if the old input was explicitly not
// marked as a managed input, then remove it from the new managed inputs if found.
OutInputsInheritingDefault.Reset();
Algo::Transform(ClassInputs, OutInputsInheritingDefault, [](const FMetasoundFrontendClassInput& Input) { return Input.Name; });
const TSet<FName>& InputsInheritingDefault = InPresetGraph->GetInputsInheritingDefault();
InPresetGraph->IterateConstNodes([&InputsInheritingDefault, &OutInputsInheritingDefault](FConstNodeHandle Input)
{
if (!InputsInheritingDefault.Contains(Input->GetNodeName()))
{
OutInputsInheritingDefault.Remove(Input->GetNodeName());
}
}, EMetasoundFrontendClassType::Input);
return ClassInputs;
}
TArray<FMetasoundFrontendClassOutput> FRebuildPresetRootGraph::GenerateRequiredClassOutputs(FDocumentHandle& InDocumentHandle, const FConstGraphHandle& InPresetGraph) const
{
TArray<FMetasoundFrontendClassOutput> ClassOutputs;
FConstGraphHandle ReferencedGraph = ReferencedDocument->GetRootGraph();
// Iterate over the referenced graph's output nodes.
ReferencedGraph->IterateConstNodes([&](FConstNodeHandle OutputNode)
{
const FName NodeName = OutputNode->GetNodeName();
FConstOutputHandle Output = OutputNode->GetConstOutputWithVertexName(NodeName);
if (ensure(Output->IsValid()))
{
FMetasoundFrontendClassOutput ClassOutput;
ClassOutput.Name = NodeName;
ClassOutput.TypeName = Output->GetDataType();
ClassOutput.AccessType = Output->GetVertexAccessType();
#if WITH_EDITOR
ClassOutput.Metadata.SetDescription(OutputNode->GetDescription());
ClassOutput.Metadata.SetDisplayName(Output->GetMetadata().GetDisplayName());
#endif // WITH_EDITOR
FDocumentAccessPtr DocumentPtr = InDocumentHandle->GetDocumentPtr();
const FMetasoundFrontendDocument* Document = DocumentPtr.Get();
check(Document);
ClassOutput.VertexID = FDocumentIDGenerator::Get().CreateVertexID(*Document);
if (const FMetasoundFrontendClassOutput* ExistingClassOutput = InPresetGraph->FindClassOutputWithName(NodeName).Get())
{
ClassOutput.NodeID = ExistingClassOutput->NodeID;
}
ClassOutputs.Add(MoveTemp(ClassOutput));
}
}, EMetasoundFrontendClassType::Output);
return ClassOutputs;
}
bool FRenameRootGraphClass::Transform(FDocumentHandle InDocument) const
{
return false;
}
bool FRenameRootGraphClass::Transform(FMetasoundFrontendDocument& InOutDocument) const
{
return false;
}
} // namespace Frontend
} // namespace Metasound