Files
UnrealEngine/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MacroInstance.cpp
2025-05-18 13:04:45 +08:00

914 lines
27 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "K2Node_MacroInstance.h"
#include "BlueprintActionFilter.h"
#include "Containers/EnumAsByte.h"
#include "Delegates/Delegate.h"
#include "EdGraph/EdGraphSchema.h"
#include "EdGraphSchema_K2.h"
#include "EdGraphUtilities.h"
#include "Editor.h"
#include "Editor/EditorEngine.h"
#include "EditorCategoryUtils.h"
#include "Engine/Blueprint.h"
#include "Framework/Commands/UIAction.h"
#include "HAL/PlatformCrt.h"
#include "Internationalization/Internationalization.h"
#include "K2Node_EditablePinBase.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/WildcardNodeUtils.h"
#include "KismetCompiler.h"
#include "Misc/AssertionMacros.h"
#include "Serialization/Archive.h"
#include "Settings/EditorStyleSettings.h"
#include "Styling/AppStyle.h"
#include "Templates/Casts.h"
#include "Templates/SubclassOf.h"
#include "Templates/UnrealTemplate.h"
#include "ToolMenu.h"
#include "ToolMenuSection.h"
#include "UObject/Class.h"
#include "UObject/Object.h"
#include "UObject/ObjectVersion.h"
#include "UObject/UnrealNames.h"
#define LOCTEXT_NAMESPACE "K2Node_MacroInstance"
UK2Node_MacroInstance::UK2Node_MacroInstance(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
bReconstructNode = false;
}
void UK2Node_MacroInstance::Serialize(FArchive& Ar)
{
Super::Serialize(Ar);
if (Ar.UEVer() < VER_UE4_K2NODE_REFERENCEGUIDS)
{
MacroGraphReference.SetGraph(MacroGraph_DEPRECATED);
}
}
bool UK2Node_MacroInstance::IsActionFilteredOut(FBlueprintActionFilter const& Filter)
{
bool bIsFilteredOut = false;
FBlueprintActionContext const& FilterContext = Filter.Context;
for (UEdGraph* Graph : FilterContext.Graphs)
{
if (!CanPasteHere(Graph))
{
bIsFilteredOut = true;
break;
}
}
return bIsFilteredOut;
}
void UK2Node_MacroInstance::PostPasteNode()
{
const UBlueprint* InstanceOwner = GetBlueprint();
// Find the owner of the macro graph
if (const UEdGraph* MacroGraph = MacroGraphReference.GetGraph())
{
UObject* MacroOwner = MacroGraph->GetOuter();
UBlueprint* MacroOwnerBP = nullptr;
while (MacroOwner)
{
MacroOwnerBP = Cast<UBlueprint>(MacroOwner);
if (MacroOwnerBP)
{
break;
}
MacroOwner = MacroOwner->GetOuter();
}
if ((MacroOwnerBP != nullptr)
&& (MacroOwnerBP->BlueprintType != BPTYPE_MacroLibrary)
&& (MacroOwnerBP != InstanceOwner))
{
// If this is a graph from another blueprint that is NOT a library, disallow the connection!
MacroGraphReference.SetGraph(nullptr);
}
}
else
{
// Can't find the referenced macro, fully clear this reference
MacroGraphReference.SetGraph(nullptr);
}
Super::PostPasteNode();
}
void UK2Node_MacroInstance::AllocateDefaultPins()
{
UK2Node::AllocateDefaultPins();
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
PreloadObject(MacroGraphReference.GetBlueprint());
UEdGraph* MacroGraph = MacroGraphReference.GetGraph();
if (MacroGraph != nullptr)
{
PreloadObject(MacroGraph);
// Preload the macro graph, if needed, so that we can get the proper pins
if (MacroGraph->HasAnyFlags(RF_NeedLoad))
{
PreloadObject(MacroGraph);
FBlueprintEditorUtils::PreloadMembers(MacroGraph);
}
for (decltype(MacroGraph->Nodes)::TIterator NodeIt(MacroGraph->Nodes); NodeIt; ++NodeIt)
{
if (UK2Node_Tunnel* TunnelNode = Cast<UK2Node_Tunnel>(*NodeIt))
{
// Only want exact tunnel nodes, more specific nodes like composites or macro instances shouldn't be grabbed.
if (TunnelNode->GetClass() == UK2Node_Tunnel::StaticClass())
{
for (TArray<UEdGraphPin*>::TIterator PinIt(TunnelNode->Pins); PinIt; ++PinIt)
{
UEdGraphPin* PortPin = *PinIt;
// We're not interested in any pins that have been expanded internally on the macro
if (PortPin->ParentPin == nullptr)
{
UEdGraphPin* NewLocalPin = CreatePin(UEdGraphPin::GetComplementaryDirection(PortPin->Direction), PortPin->PinType, PortPin->PinName);
Schema->SetPinAutogeneratedDefaultValue(NewLocalPin, PortPin->GetDefaultAsString());
}
}
}
}
}
}
CacheWildcardPins();
}
void UK2Node_MacroInstance::PreloadRequiredAssets()
{
PreloadObject(MacroGraphReference.GetBlueprint());
UEdGraph* MacroGraph = MacroGraphReference.GetGraph();
PreloadObject(MacroGraph);
Super::PreloadRequiredAssets();
}
FText UK2Node_MacroInstance::GetTooltipText() const
{
UEdGraph* MacroGraph = MacroGraphReference.GetGraph();
if (FKismetUserDeclaredFunctionMetadata* Metadata = GetAssociatedGraphMetadata(MacroGraph))
{
if (!Metadata->ToolTip.IsEmpty())
{
return Metadata->ToolTip;
}
}
if (MacroGraph == nullptr)
{
return NSLOCTEXT("K2Node", "Macro_Tooltip", "Macro");
}
else if (CachedTooltip.IsOutOfDate(this))
{
// FText::Format() is slow, so we cache this to save on performance
CachedTooltip.SetCachedText(FText::Format(NSLOCTEXT("K2Node", "MacroGraphInstance_Tooltip", "{0} instance"), FText::FromName(MacroGraph->GetFName())), this);
}
return CachedTooltip;
}
FText UK2Node_MacroInstance::GetKeywords() const
{
FText Keywords = GetAssociatedGraphMetadata(GetMacroGraph())->Keywords;
// If the Macro has Compact Node Title data, append the compact node title as a Keyword so it can be searched.
if (ShouldDrawCompact())
{
Keywords = FText::Format(FText::FromString(TEXT("{0} {1}")), Keywords, GetCompactNodeTitle());
}
return Keywords;
}
FText UK2Node_MacroInstance::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
UEdGraph* MacroGraph = MacroGraphReference.GetGraph();
FText Result = NSLOCTEXT("K2Node", "MacroInstance", "Macro instance");
if (MacroGraph)
{
Result = FText::FromString(MacroGraph->GetName());
if ((GEditor != NULL) && (GetDefault<UEditorStyleSettings>()->bShowFriendlyNames))
{
Result = FText::FromString(FName::NameToDisplayString(Result.ToString(), false));
}
}
return Result;
}
FLinearColor UK2Node_MacroInstance::GetNodeTitleColor() const
{
UEdGraph* MacroGraph = MacroGraphReference.GetGraph();
if (FKismetUserDeclaredFunctionMetadata* Metadata = GetAssociatedGraphMetadata(MacroGraph))
{
return Metadata->InstanceTitleColor.ToFColor(false);
}
return FLinearColor::White;
}
void UK2Node_MacroInstance::GetNodeContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const
{
if ( Context->Pin == nullptr )
{
{
FToolMenuSection& Section = Menu->AddSection("K2NodeMacroInstance", NSLOCTEXT("K2Node", "MacroInstanceHeader", "Macro Instance"));
Section.AddMenuEntry(
"MacroInstanceFindInContentBrowser",
NSLOCTEXT("K2Node", "MacroInstanceFindInContentBrowser", "Find in Content Browser"),
NSLOCTEXT("K2Node", "MacroInstanceFindInContentBrowserTooltip", "Finds the Blueprint Macro Library that contains this Macro in the Content Browser"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Search"),
FUIAction( FExecuteAction::CreateStatic( &UK2Node_MacroInstance::FindInContentBrowser, MakeWeakObjectPtr(const_cast<UK2Node_MacroInstance*>(this)) ) )
);
}
}
}
FKismetUserDeclaredFunctionMetadata* UK2Node_MacroInstance::GetAssociatedGraphMetadata(const UEdGraph* AssociatedMacroGraph)
{
if (AssociatedMacroGraph)
{
// Look at the graph for the entry node, to get the default title color
TArray<UK2Node_Tunnel*> TunnelNodes;
AssociatedMacroGraph->GetNodesOfClass(TunnelNodes);
for (int32 i = 0; i < TunnelNodes.Num(); i++)
{
UK2Node_Tunnel* Node = TunnelNodes[i];
if (Node->IsEditable())
{
if (Node->bCanHaveOutputs)
{
return &(Node->MetaData);
}
}
}
}
return nullptr;
}
void UK2Node_MacroInstance::FindInContentBrowser(TWeakObjectPtr<UK2Node_MacroInstance> MacroInstance)
{
if ( MacroInstance.IsValid() )
{
UEdGraph* InstanceMacroGraph = MacroInstance.Get()->MacroGraphReference.GetGraph();
if ( InstanceMacroGraph )
{
UBlueprint* BlueprintToSync = FBlueprintEditorUtils::FindBlueprintForGraph(InstanceMacroGraph);
if ( BlueprintToSync )
{
TArray<UObject*> ObjectsToSync;
ObjectsToSync.Add( BlueprintToSync );
GEditor->SyncBrowserToObjects(ObjectsToSync);
}
}
}
}
void UK2Node_MacroInstance::NotifyPinConnectionListChanged(UEdGraphPin* ChangedPin)
{
Super::NotifyPinConnectionListChanged(ChangedPin);
const bool bShouldDoSmartInference = ShouldDoSmartWildcardInference();
if(bShouldDoSmartInference)
{
const bool bIsWildcardPin = FWildcardNodeUtils::HasAnyWildcards(ChangedPin);
if (bIsWildcardPin && ChangedPin->LinkedTo.Num() > 0)
{
// Search the changed pin's links for an inferrable pin:
if(const UEdGraphPin* InferrablePin = FWildcardNodeUtils::FindInferrableLinkedPin(ChangedPin))
{
// we found one, infer from it and then propagate the inference:
FWildcardNodeUtils::InferType(ChangedPin, InferrablePin->PinType);
const UEdGraph* Graph = GetGraph();
const bool bIsMacroGraph = (Graph->GetSchema()->GetGraphType(Graph) == GT_Macro);
if (!bIsMacroGraph)
{
InferWildcards();
}
}
}
}
// added a link?
if (ChangedPin->LinkedTo.Num() > 0)
{
// ... to a wildcard pin?
const bool bIsWildcardPin = ChangedPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard;
if (bIsWildcardPin)
{
// get type of pin we just got linked to
FEdGraphPinType const LinkedPinType = ChangedPin->LinkedTo[0]->PinType;
// change all other wildcard pins to the new type
// note we're assuming only one wildcard type per Macro node, for now
if(!bShouldDoSmartInference)
{
for(int32 PinIdx=0; PinIdx<Pins.Num(); PinIdx++)
{
UEdGraphPin* const TmpPin = Pins[PinIdx];
if (FWildcardNodeUtils::IsWildcardPin(TmpPin))
{
// only copy the category stuff to preserve array and ref status
TmpPin->PinType.PinCategory = LinkedPinType.PinCategory;
TmpPin->PinType.PinSubCategory = LinkedPinType.PinSubCategory;
TmpPin->PinType.PinSubCategoryObject = LinkedPinType.PinSubCategoryObject;
}
}
}
ResolvedWildcardType = LinkedPinType;
bReconstructNode = true;
}
}
else
{
// reconstruct on disconnects so we can revert back to wildcards if necessary
bReconstructNode = true;
}
}
void UK2Node_MacroInstance::NodeConnectionListChanged()
{
Super::NodeConnectionListChanged();
if (bReconstructNode)
{
ReconstructNode();
UBlueprint* const Blueprint = GetBlueprint();
if (Blueprint && !Blueprint->bBeingCompiled)
{
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
}
}
FString UK2Node_MacroInstance::GetDocumentationLink() const
{
return TEXT("Shared/GraphNodes/Blueprint/UK2Node_MacroInstance");
}
FString UK2Node_MacroInstance::GetDocumentationExcerptName() const
{
UEdGraph* MacroGraph = MacroGraphReference.GetGraph();
if (MacroGraph != nullptr)
{
return MacroGraph->GetName();
}
return Super::GetDocumentationExcerptName();
}
void UK2Node_MacroInstance::PostReconstructNode()
{
bReconstructNode = false;
if(ShouldDoSmartWildcardInference())
{
// conform any type mismatches - or just conform
for(UEdGraphPin* Pin : WildcardPins)
{
if (FWildcardNodeUtils::HasAnyWildcards(Pin))
{
const FEdGraphPinType* ConnectedType = nullptr;
for(UEdGraphPin* Link : Pin->LinkedTo)
{
if(!FWildcardNodeUtils::HasAnyWildcards(Link))
{
ConnectedType = &Link->PinType;
}
}
if(ConnectedType)
{
FWildcardNodeUtils::InferType(Pin->PinType, *ConnectedType);
}
}
}
const UEdGraph* Graph = GetGraph();
const bool bIsMacroGraph = (Graph->GetSchema()->GetGraphType(Graph) == GT_Macro);
UBlueprint* Blueprint = GetBlueprint();
const bool bIsCompiling = Blueprint ? Blueprint->bBeingCompiled : false;
if(!bIsMacroGraph || !bIsCompiling)
{
// rerun inference
InferWildcards();
}
}
else
{
// fix up ResolvedWildcardType, which could have been cleared for certain CL ranges
if (ResolvedWildcardType.PinCategory.IsNone() && WildcardPins.Num() > 0)
{
UEdGraphPin* const* NonWildcardPin = Algo::FindByPredicate(WildcardPins,
[](const UEdGraphPin* Pin )
{
return !FWildcardNodeUtils::IsWildcardPin(Pin);
});
if(NonWildcardPin)
{
ResolvedWildcardType = (*NonWildcardPin)->PinType;
}
}
}
Super::PostReconstructNode();
}
FName UK2Node_MacroInstance::GetCornerIcon() const
{
if (UEdGraph* MacroGraph = MacroGraphReference.GetGraph())
{
FBlueprintMacroCosmeticInfo CosmeticInfo = FBlueprintEditorUtils::GetCosmeticInfoForMacro(MacroGraph);
if (CosmeticInfo.bContainsLatentNodes)
{
return TEXT("Graph.Latent.LatentIcon");
}
}
return Super::GetCornerIcon();
}
FSlateIcon UK2Node_MacroInstance::GetIconAndTint(FLinearColor& OutColor) const
{
const char* IconName = "GraphEditor.Macro_16x";
// Special case handling for standard macros
// @TODO Change this to a SlateBurushAsset pointer on the graph or something similar, to allow any macro to have an icon
UEdGraph* MacroGraph = MacroGraphReference.GetGraph();
if(MacroGraph != nullptr && MacroGraph->GetOuter()->GetName() == TEXT("StandardMacros"))
{
FName MacroName = FName(*MacroGraph->GetName());
if( MacroName == TEXT("ForLoop" ) ||
MacroName == TEXT("ForLoopWithBreak") ||
MacroName == TEXT("WhileLoop") )
{
IconName = "GraphEditor.Macro.Loop_16x";
}
else if( MacroName == TEXT("Gate") )
{
IconName = "GraphEditor.Macro.Gate_16x";
}
else if( MacroName == TEXT("Do N") )
{
IconName = "GraphEditor.Macro.DoN_16x";
}
else if (MacroName == TEXT("DoOnce"))
{
IconName = "GraphEditor.Macro.DoOnce_16x";
}
else if (MacroName == TEXT("IsValid"))
{
IconName = "GraphEditor.Macro.IsValid_16x";
}
else if (MacroName == TEXT("FlipFlop"))
{
IconName = "GraphEditor.Macro.FlipFlop_16x";
}
else if ( MacroName == TEXT("ForEachLoop") ||
MacroName == TEXT("ForEachLoopWithBreak") )
{
IconName = "GraphEditor.Macro.ForEach_16x";
}
}
return FSlateIcon(FAppStyle::GetAppStyleSetName(), IconName);
}
FText UK2Node_MacroInstance::GetCompactNodeTitle() const
{
FText ResultText;
if (FKismetUserDeclaredFunctionMetadata* MacroGraphMetadata = GetAssociatedGraphMetadata(GetMacroGraph()))
{
ResultText = MacroGraphMetadata->CompactNodeTitle;
}
return ResultText;
}
bool UK2Node_MacroInstance::ShouldDrawCompact() const
{
return !GetCompactNodeTitle().IsEmpty();
}
bool UK2Node_MacroInstance::CanPasteHere(const UEdGraph* TargetGraph) const
{
bool bCanPaste = false;
UBlueprint* MacroBlueprint = GetSourceBlueprint();
UBlueprint* TargetBlueprint = FBlueprintEditorUtils::FindBlueprintForGraph(TargetGraph);
if ((MacroBlueprint != nullptr) && (TargetBlueprint != nullptr))
{
// Only allow "local" macro instances or instances from a macro library blueprint with the same parent class
check(MacroBlueprint->ParentClass != nullptr && TargetBlueprint->ParentClass != nullptr);
bCanPaste = (MacroBlueprint == TargetBlueprint) || (MacroBlueprint->BlueprintType == BPTYPE_MacroLibrary
&& TargetBlueprint->ParentClass && TargetBlueprint->ParentClass->IsChildOf(MacroBlueprint->ParentClass));
}
// Macro Instances are not allowed in it's own graph
UEdGraph* MacroGraph = GetMacroGraph();
bCanPaste &= (MacroGraph != TargetGraph);
// nor in Function graphs if the macro has latent functions in it
bool const bIsTargetFuncGraph = (TargetGraph->GetSchema()->GetGraphType(TargetGraph) == GT_Function);
bCanPaste &= (!bIsTargetFuncGraph || !FBlueprintEditorUtils::CheckIfGraphHasLatentFunctions(MacroGraph));
return bCanPaste && Super::CanPasteHere(TargetGraph);
}
void UK2Node_MacroInstance::PostFixupAllWildcardPins(bool bInAllWildcardPinsUnlinked)
{
if (bInAllWildcardPinsUnlinked)
{
// Reset the type to a wildcard because there are no longer any wildcard pins linked to determine a type with
ResolvedWildcardType.ResetToDefaults();
// Collapse any wildcard pins that are split and set their type back to wildcard
// doing this would be unsafe when using smart wildcard inference
// because recombining pin in the middle of reconstruction could result in
// pin allocation during reconstruction. Therefore we don't rely upon it
// when doing smart wildcard inference
if (!ShouldDoSmartWildcardInference())
{
for (UEdGraphPin* Pin : WildcardPins)
{
GetSchema()->RecombinePin(Pin);
Pin->PinType.PinCategory = UEdGraphSchema_K2::PC_Wildcard;
Pin->PinType.PinSubCategory = NAME_None;
Pin->PinType.PinSubCategoryObject = nullptr;
}
}
}
}
void UK2Node_MacroInstance::InferWildcards(const TArray<UEdGraphNode*>& InNodes) const
{
if(ShouldDoSmartWildcardInference())
{
SmartInferWildcardsImpl(InNodes);
return;
}
if (!ResolvedWildcardType.PinCategory.IsNone())
{
for (UEdGraphNode* const ClonedNode : InNodes)
{
if (ClonedNode)
{
for (UEdGraphPin* const ClonedPin : ClonedNode->Pins)
{
if (ClonedPin && (ClonedPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard))
{
// copy only type info, so array or ref status is preserved
ClonedPin->PinType.PinCategory = ResolvedWildcardType.PinCategory;
ClonedPin->PinType.PinSubCategory = ResolvedWildcardType.PinSubCategory;
ClonedPin->PinType.PinSubCategoryObject = ResolvedWildcardType.PinSubCategoryObject;
}
}
}
}
}
}
bool UK2Node_MacroInstance::HasExternalDependencies(TArray<class UStruct*>* OptionalOutput) const
{
UBlueprint* OtherBlueprint = MacroGraphReference.GetBlueprint();
const bool bResult = OtherBlueprint && (OtherBlueprint != GetBlueprint());
if (bResult && OptionalOutput)
{
if (UClass* OtherClass = *OtherBlueprint->GeneratedClass)
{
OptionalOutput->AddUnique(OtherClass);
}
for (UEdGraphPin* Pin : Pins)
{
if (Pin->PinType.PinSubCategoryObject.IsValid())
{
if (UStruct* Struct = Cast<UStruct>(Pin->PinType.PinSubCategoryObject.Get()))
{
OptionalOutput->AddUnique(Struct);
}
else
{
OptionalOutput->AddUnique(Pin->PinType.PinSubCategoryObject.Get()->GetClass());
}
}
}
}
const bool bSuperResult = Super::HasExternalDependencies(OptionalOutput);
return bSuperResult || bResult;
}
void UK2Node_MacroInstance::GetNodeAttributes( TArray<TKeyValuePair<FString, FString>>& OutNodeAttributes ) const
{
FString MacroName( TEXT( "InvalidMacro" ));
if( UEdGraph* MacroGraph = GetMacroGraph() )
{
MacroName = MacroGraph->GetName();
}
OutNodeAttributes.Add( TKeyValuePair<FString, FString>( TEXT( "Type" ), TEXT( "Macro" ) ));
OutNodeAttributes.Add( TKeyValuePair<FString, FString>( TEXT( "Class" ), GetClass()->GetName() ));
OutNodeAttributes.Add( TKeyValuePair<FString, FString>( TEXT( "Name" ), MacroName ));
}
FText UK2Node_MacroInstance::GetMenuCategory() const
{
FText MenuCategory = FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Utilities);
if (UEdGraph* MacroGraph = GetMacroGraph())
{
FKismetUserDeclaredFunctionMetadata* MacroGraphMetadata = UK2Node_MacroInstance::GetAssociatedGraphMetadata(MacroGraph);
if ((MacroGraphMetadata != nullptr) && !MacroGraphMetadata->Category.IsEmpty())
{
MenuCategory = MacroGraphMetadata->Category;
}
}
return MenuCategory;
}
FBlueprintNodeSignature UK2Node_MacroInstance::GetSignature() const
{
FBlueprintNodeSignature NodeSignature = Super::GetSignature();
NodeSignature.AddSubObject(GetMacroGraph());
return NodeSignature;
}
void UK2Node_MacroInstance::InferWildcards()
{
// we've got a new user provided pin, expand the macro
UEdGraph* MacroGraph = GetMacroGraph();
if (MacroGraph)
{
// perform macro expansion in a dummy graph, inferring whatever types we can from the provided wildcards:
FCompilerResultsLog MessageLog;
UBlueprint* BP = GetBlueprint();
UEdGraph* ClonedGraph = FEdGraphUtilities::CloneGraph(MacroGraph, BP, &MessageLog, true);
if (ClonedGraph)
{
InferWildcards(ClonedGraph->Nodes);
// Uncomment to record this graph as an intermediate product - useful for debugging
//ClonedGraph->Schema = UEdGraphSchema_K2::StaticClass();
//GetBlueprint()->IntermediateGeneratedGraphs.Add(ClonedGraph);
//ClonedGraph->SetFlags(RF_Transient);
}
}
}
TArray<UEdGraphPin*> UK2Node_MacroInstance::GetAllWildcardPins() const
{
UEdGraph* MacroGraph = GetMacroGraph();
if (MacroGraph == nullptr)
{
return TArray<UEdGraphPin*>();
}
TArray<UEdGraphPin*> Result;
for(UEdGraphNode* Node : MacroGraph->Nodes)
{
if (UK2Node_Tunnel* Tunnel = ExactCast<UK2Node_Tunnel>(Node))
{
for(UEdGraphPin* TunnelPin : Tunnel->Pins)
{
if(FWildcardNodeUtils::IsWildcardPin(TunnelPin))
{
Result.Add(FindPin(TunnelPin->GetName(), UEdGraphPin::GetComplementaryDirection(TunnelPin->Direction)));
}
}
}
}
return Result;
}
namespace UE::Private
{
// Recursively infers a type for a network of wildcard pins - i.e. infers type for your linkedto's linkedto's linketo's....
static void InferLinkedPinsImpl(UEdGraphPin* Pin, const FEdGraphPinType& Type, TArray<TPair<UEdGraphNode*, UEdGraphPin*>>& OutDirtyNodePins, TSet<UEdGraphPin*>& ProcessedPins)
{
FWildcardNodeUtils::InferType(Pin, Type);
OutDirtyNodePins.AddUnique({Pin->GetOwningNode(), Pin});
ProcessedPins.Add(Pin);
for(UEdGraphPin* LinkedPin : Pin->LinkedTo)
{
if(!ProcessedPins.Contains(LinkedPin) && FWildcardNodeUtils::IsWildcardPin(LinkedPin))
{
InferLinkedPinsImpl(LinkedPin, Type, OutDirtyNodePins, ProcessedPins);
}
}
}
}
void UK2Node_MacroInstance::SmartInferWildcardsImpl(const TArray<UEdGraphNode*>& InNodes) const
{
// Gather wild card pins on the tunnel pins:
TArray<UEdGraphPin*> TunnelWildcards;
for (UEdGraphNode* Node : InNodes)
{
if (UK2Node_Tunnel* Tunnel = ExactCast<UK2Node_Tunnel>(Node))
{
for (UEdGraphPin* TunnelPin : Tunnel->Pins)
{
if (FWildcardNodeUtils::HasAnyWildcards(TunnelPin))
{
// the tunnel node with input pins is the output on the macro:
TunnelWildcards.Add(TunnelPin);
}
}
}
}
// no wildcards to infer, bail:
if(TunnelWildcards.Num() == 0)
{
return;
}
// Seed any tunnel wildcard pins that have known values on the macro instance:
TArray<TPair<UEdGraphNode*, UEdGraphPin*>> DirtyNodePins; // when a pin is inferred we want to give the node a chance to propagate
for (UEdGraphPin* TunnelPin : TunnelWildcards)
{
UEdGraphPin* MacroPin = FindPin(TunnelPin->GetName(), UEdGraphPin::GetComplementaryDirection(TunnelPin->Direction));
if (ensure(MacroPin))
{
if (!FWildcardNodeUtils::IsWildcardPin(MacroPin))
{
// tunnel is wildcard, but we are not .. we want to set type on the tunnel and allow inference to run
FEdGraphPinType const& LinkedPinType = MacroPin->PinType;
FWildcardNodeUtils::InferType(TunnelPin, LinkedPinType);
for(UEdGraphPin* LinkedPin : TunnelPin->LinkedTo)
{
if (FWildcardNodeUtils::HasAnyWildcards(LinkedPin))
{
DirtyNodePins.AddUnique({LinkedPin->GetOwningNode(), LinkedPin});
}
}
}
}
}
// Helper to count the number of wildcard pins on a node
// we monitor these counts to detect when notifications need
// to be sent to owning nodes:
const auto CountWildcardPins = [](const UEdGraphNode* Node)
{
int32 WildcardCount = 0;
for(UEdGraphPin* Pin : Node->Pins)
{
if(FWildcardNodeUtils::HasAnyWildcards(Pin))
{
++WildcardCount;
}
}
return WildcardCount;
};
// helper for counting the number of connections a node has,
// used to validate that we aren't modifying graph topology
const auto CountConnections = [](const UEdGraphNode* Node)
{
int32 ConnectionCount = 0;
for(UEdGraphPin* Pin : Node->Pins)
{
ConnectionCount += Pin->LinkedTo.Num();
}
return ConnectionCount;
};
TMap<UEdGraphNode*, int32> WildcardCounts;
TMap<UEdGraphNode*, int32> ConnectionCounts;
for(UEdGraphNode* Node : InNodes)
{
const int32 WildcardCount = CountWildcardPins(Node);
if(WildcardCount > 0)
{
WildcardCounts.Add(Node, WildcardCount);
}
ConnectionCounts.Add(Node, CountConnections(Node));
}
// We've seeded, now iteratively refresh nodes until pins stabilize:
while(DirtyNodePins.Num())
{
TArray<TPair<UEdGraphNode*, UEdGraphPin*>> CurrentDirtyNodePins = MoveTemp(DirtyNodePins);
TArray<UEdGraphNode*> NodesToForceInference;
for(const TPair<UEdGraphNode*, UEdGraphPin*>& DirtyNodePin : CurrentDirtyNodePins)
{
// Any wildcard pins that are connected to this pin need to be
// inferred and marked dirty:
UK2Node* TypedNode = CastChecked<UK2Node>(DirtyNodePin.Key);
TypedNode->NotifyPinConnectionListChanged(DirtyNodePin.Value);
const int32 WildcardCount = CountWildcardPins(TypedNode);
if (WildcardCount != 0 && WildcardCount == WildcardCounts[TypedNode])
{
NodesToForceInference.AddUnique(TypedNode);
}
}
const auto InferLinkedPins = [](UEdGraphPin * Pin, const UEdGraphPin * SourcePin, TArray<TPair<UEdGraphNode*, UEdGraphPin*>>&OutDirtyNodePins)
{
TSet<UEdGraphPin*> ProcessedPins;
UE::Private::InferLinkedPinsImpl(Pin, SourcePin->PinType, OutDirtyNodePins, ProcessedPins);
};
for(UEdGraphNode* Node : NodesToForceInference)
{
// force inference, the node is refusing to propagate based on connections.
// We can directly infer wildcard connections from their connected non wildcard
// pins:
for(UEdGraphPin* Pin : Node->Pins)
{
if(FWildcardNodeUtils::HasAnyWildcards(Pin))
{
for(UEdGraphPin* LinkedPin : Pin->LinkedTo)
{
if (!FWildcardNodeUtils::HasAnyWildcards(LinkedPin) )
{
// infer and mark dirty:
InferLinkedPins(Pin, LinkedPin, DirtyNodePins);
break;
}
}
}
}
}
// look for pins that are now inferable:
for(const TPair<UEdGraphNode*, int32>& NodeWithCount : WildcardCounts)
{
// if count has changed the node is dirty:
UEdGraphNode* WildcardNode = NodeWithCount.Key;
const int32 WildcardCount = CountWildcardPins(WildcardNode);
if(NodeWithCount.Value != WildcardCount)
{
for(UEdGraphPin* Pin : WildcardNode->Pins)
{
for(UEdGraphPin* LinkedPin : Pin->LinkedTo)
{
if(FWildcardNodeUtils::HasAnyWildcards(LinkedPin))
{
// mark dirty:
DirtyNodePins.AddUnique({LinkedPin->GetOwningNode(), LinkedPin});
}
}
}
}
// we must also update the count:
WildcardCounts[NodeWithCount.Key] = WildcardCount;
}
}
for(const TPair<UEdGraphNode*, int32>& NodeWithCount : ConnectionCounts)
{
// This ensure indicates that we have a node that is destroying the graph in its
// NotifyPinConnectionListChanged override - note that this is an imperfect test
// but it seems to be cheap and will catch the most egregious errors
UEdGraph* MacroGraph = GetMacroGraph();
ensureMsgf(NodeWithCount.Value == CountConnections(NodeWithCount.Key),
TEXT("Node connection count changed while inferring %s - consider setting [Blueprints] bUseSimpleWildcardInference as a workaround"),
MacroGraph ? *MacroGraph->GetPathName() : TEXT("Unknown Graph")
);
}
// copy back inferred values to any pins on our macro instance:
for (const UEdGraphPin* TunnelWildcard : TunnelWildcards)
{
UEdGraphPin* SourcePin = FindPin(
*TunnelWildcard->PinName.ToString(),
UEdGraphPin::GetComplementaryDirection(TunnelWildcard->Direction));
if(FWildcardNodeUtils::HasAnyWildcards(SourcePin))
{
FWildcardNodeUtils::InferType(SourcePin, TunnelWildcard->PinType);
}
}
}
#undef LOCTEXT_NAMESPACE