// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= EdGraphCompiler.cpp =============================================================================*/ #include "Containers/Array.h" #include "Containers/EnumAsByte.h" #include "Containers/Map.h" #include "Containers/Set.h" #include "Containers/UnrealString.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphCompilerUtilities.h" #include "EdGraphUtilities.h" #include "HAL/PlatformCrt.h" #include "HAL/PlatformMath.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "Kismet2/CompilerResultsLog.h" #include "Templates/SubclassOf.h" #include "UObject/ObjectPtr.h" #include "Misc/CommandLine.h" namespace UE::Private::GraphCompilerContext { // Builds against old assets routinely generate deprecation messages, fixing them would defeat the point of running compat-testing builds. // Since we expect deprecated BP to continue to be functional, provide a way to make the warnings simple log messages. bool IgnoreBlueprintDeprecationWarnings() { const static bool bIgnoreBpWarnings = FParse::Param(FCommandLine::Get(), TEXT("IgnoreBlueprintDeprecationWarnings")); return bIgnoreBpWarnings; } } ////////////////////////////////////////////////////////////////////////// // FGraphCompilerContext /** Validates that the interconnection between two pins is schema compatible */ void FGraphCompilerContext::ValidateLink(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const { const bool bPinDirectionsValid = ((PinA->Direction == EGPD_Input) && (PinB->Direction == EGPD_Output)) || ((PinA->Direction == EGPD_Output) && (PinB->Direction == EGPD_Input)); if (!bPinDirectionsValid) { MessageLog.Error(TEXT("Direction mismatch between pins @@ and @@"), PinA, PinB); } if (PinA->GetOwningNodeUnchecked() == PinB->GetOwningNodeUnchecked()) { MessageLog.Error(TEXT("Pins @@ and @@ on the same node @@ are connected to each other, creating a loop."), PinA, PinB, PinA->GetOwningNode()); } } /** Validate that the wiring for a single pin is schema compatible */ void FGraphCompilerContext::ValidatePin(const UEdGraphPin* Pin) const { if (!Pin || !Pin->GetOwningNodeUnchecked()) { MessageLog.Error( *FText::Format( NSLOCTEXT("EdGraphCompiler", "PinWrongOuterErrorFmt", "Blueprint is corrupted! Pin '{0}' has wrong outer '{1}'."), FText::FromString(Pin ? *Pin->GetName() : TEXT("UNKNOWN")), FText::FromString((Pin && Pin->GetOuter()) ? *Pin->GetOuter()->GetName() : TEXT("NULL")) ).ToString(), Pin ); return; } const int32 ErrorNum = MessageLog.NumErrors; // Validate the links to each connected pin for (int32 LinkIndex = 0; (LinkIndex < Pin->LinkedTo.Num()) && (ErrorNum == MessageLog.NumErrors); ++LinkIndex) { if (const UEdGraphPin* OtherPin = Pin->LinkedTo[LinkIndex]) { ValidateLink(Pin, OtherPin); } else { MessageLog.Error(*NSLOCTEXT("EdGraphCompiler", "PinLinkIsNull Error", "Null or missing pin linked to @@").ToString(), Pin); } } } /** Validates that the node is schema compatible */ void FGraphCompilerContext::ValidateNode(const UEdGraphNode* Node) const { const bool bIsNodeDeprecated = Node->IsDeprecated(); if (bIsNodeDeprecated || Node->HasDeprecatedReference()) { EEdGraphNodeDeprecationType ResponseType = bIsNodeDeprecated ? EEdGraphNodeDeprecationType::NodeTypeIsDeprecated : EEdGraphNodeDeprecationType::NodeHasDeprecatedReference; FEdGraphNodeDeprecationResponse Response = Node->GetDeprecationResponse(ResponseType); if (Response.MessageType != EEdGraphNodeDeprecationMessageType::None && !Response.MessageText.IsEmpty()) { switch (Response.MessageType) { case EEdGraphNodeDeprecationMessageType::Warning: if (LIKELY(!UE::Private::GraphCompilerContext::IgnoreBlueprintDeprecationWarnings())) { MessageLog.Warning(*Response.MessageText.ToString(), Node); break; } // else intentional fall-through to Note case EEdGraphNodeDeprecationMessageType::Note: MessageLog.Note(*Response.MessageText.ToString(), Node); break; } } } for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) { if (const UEdGraphPin* Pin = Node->Pins[PinIndex]) { UEdGraphNode* OwningNode = Pin->GetOwningNodeUnchecked(); if (OwningNode != Node) { MessageLog.Error(*NSLOCTEXT("EdGraphCompiler", "WrongPinsOwner_Error", "The pin @@ has outer @@, but it's used in @@").ToString(), Pin, OwningNode, Node); } else { ValidatePin(Pin); } } } Node->ValidateNodeDuringCompilation(MessageLog); } /** Performs standard validation on the graph (outputs point to inputs, no more than one connection to each input, types match on both ends, etc...) */ bool FGraphCompilerContext::ValidateGraphIsWellFormed(UEdGraph* Graph) const { int32 SavedErrorCount = MessageLog.NumErrors; for (int32 NodeIndex = 0; NodeIndex < Graph->Nodes.Num(); ++NodeIndex) { const UEdGraphNode* Node = Graph->Nodes[NodeIndex]; if( Node ) { ValidateNode(Node); } else { // The graph has a gap in its Nodes array, probably due to a deprecated node class. Remove the element. Graph->Nodes.RemoveAt(NodeIndex); NodeIndex--; } } return SavedErrorCount == MessageLog.NumErrors; } UEdGraphNode* FGraphCompilerContext::FindNodeByClass(const UEdGraph* Graph, TSubclassOf NodeClass, bool bExpectedUnique) const { UEdGraphNode* FirstResultNode = NULL; for (int32 NodeIndex = 0; NodeIndex < Graph->Nodes.Num(); ++NodeIndex) { UEdGraphNode* Node = Graph->Nodes[NodeIndex]; if (Node->IsA(NodeClass)) { if (bExpectedUnique) { if (FirstResultNode == NULL) { FirstResultNode = Node; } else { MessageLog.Error(*FString::Printf(TEXT("Expected only one %s node in graph @@, but found both @@ and @@"), *(NodeClass->GetName())), Graph, FirstResultNode, Node); } } else { return Node; } } } return FirstResultNode; } /** Prunes any nodes that weren't visited from the graph, printing out a warning */ void FGraphCompilerContext::PruneIsolatedNodes(const TArray& RootSet, TArray& GraphNodes) { FEdGraphUtilities::FNodeVisitor Visitor; for (TArray::TConstIterator It(RootSet); It; ++It) { UEdGraphNode* RootNode = *It; Visitor.TraverseNodes(RootNode); } for (int32 NodeIndex = 0; NodeIndex < GraphNodes.Num(); ++NodeIndex) { UEdGraphNode* Node = GraphNodes[NodeIndex]; if (!Visitor.VisitedNodes.Contains(Node)) { if (!CanIgnoreNode(Node)) { // Disabled this warning, because having orphaned chains is standard workflow for LDs //MessageLog.Warning(TEXT("Node @@ will never be executed and is being pruned"), Node); } if (!ShouldForceKeepNode(Node)) { Node->BreakAllNodeLinks(); GraphNodes.RemoveAtSwap(NodeIndex); --NodeIndex; } } } } /** * Performs a topological sort on the graph of nodes passed in (which is expected to form a DAG), scheduling them. * If there are cycles or unconnected nodes present in the graph, an error will be output for each node that failed to be scheduled. */ void FGraphCompilerContext::CreateExecutionSchedule(const TArray& GraphNodes, /*out*/ TArray& LinearExecutionSchedule) const { TArray NodesWithNoEdges; TMap NumIncomingEdges; int32 TotalGraphEdgesLeft = 0; // Build a list of nodes with no antecedents and update the initial incoming edge counts for every node for (int32 NodeIndex = 0; NodeIndex < GraphNodes.Num(); ++NodeIndex) { UEdGraphNode* Node = GraphNodes[NodeIndex]; const int32 NumEdges = CountIncomingEdges(Node); NumIncomingEdges.Add(Node, NumEdges); TotalGraphEdgesLeft += NumEdges; if (NumEdges == 0) { NodesWithNoEdges.Add(Node); } } // While there are nodes with no unscheduled inputs, schedule them and queue up any that are newly scheduleable while (NodesWithNoEdges.Num() > 0) { // Schedule a node UEdGraphNode* Node = NodesWithNoEdges[0]; NodesWithNoEdges.RemoveAtSwap(0); LinearExecutionSchedule.Add(Node); // Decrement edge counts for things that depend on this node, and queue up any that hit 0 incoming edges for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) { UEdGraphPin* OutPin = Node->Pins[PinIndex]; if ((OutPin->Direction == EGPD_Output) && PinIsImportantForDependancies(OutPin)) { for (int32 LinkIndex = 0; LinkIndex < OutPin->LinkedTo.Num(); ++LinkIndex) { UEdGraphPin* LinkedToPin = OutPin->LinkedTo[LinkIndex]; if( !LinkedToPin ) { // If something went wrong in serialization and we have a bad connection, skip this. continue; } UEdGraphNode* WasDependentNode = OutPin->LinkedTo[LinkIndex]->GetOwningNodeUnchecked(); int32* pNumEdgesLeft = WasDependentNode ? NumIncomingEdges.Find(WasDependentNode) : NULL; // Remove the edge between these two nodes, since node is scheduled if (pNumEdgesLeft) { int32& NumEdgesLeft = *pNumEdgesLeft; if (NumEdgesLeft <= 0) { MessageLog.Error(TEXT("Internal compiler error inside CreateExecutionSchedule (site 1); there is an issue with node/pin manipulation that was performed in this graph, please contact the Blueprints team!")); LinearExecutionSchedule.Empty(); return; } NumEdgesLeft--; TotalGraphEdgesLeft--; // Was I the last link on that node? if (NumEdgesLeft == 0) { NodesWithNoEdges.Add(WasDependentNode); } } else { MessageLog.Error(TEXT("Internal compiler error inside CreateExecutionSchedule (site 2); there is an issue with node/pin manipulation that was performed in this graph, please contact the Blueprints team!")); LinearExecutionSchedule.Empty(); return; } } } } } // At this point, any remaining edges are either within an unrelated subgraph (unconnected island) or indicate loops that break the DAG constraint // Before this is called, any unconnected graphs should have been cut free so we can just error if (TotalGraphEdgesLeft > 0) { // Run thru and print out any nodes that couldn't be scheduled due to loops for (TMap::TIterator It(NumIncomingEdges); It; ++It) { //@TODO: Probably want to determine the actual pin that caused the cycle, instead of just printing out the node if (It.Value() > 0) { MessageLog.Error(TEXT("Dependency cycle detected, preventing node @@ from being scheduled"), It.Key()); } } } }