// 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(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(); 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(*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::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()->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(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 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 MacroInstance) { if ( MacroInstance.IsValid() ) { UEdGraph* InstanceMacroGraph = MacroInstance.Get()->MacroGraphReference.GetGraph(); if ( InstanceMacroGraph ) { UBlueprint* BlueprintToSync = FBlueprintEditorUtils::FindBlueprintForGraph(InstanceMacroGraph); if ( BlueprintToSync ) { TArray 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; PinIdxPinType.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& 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* 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(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>& OutNodeAttributes ) const { FString MacroName( TEXT( "InvalidMacro" )); if( UEdGraph* MacroGraph = GetMacroGraph() ) { MacroName = MacroGraph->GetName(); } OutNodeAttributes.Add( TKeyValuePair( TEXT( "Type" ), TEXT( "Macro" ) )); OutNodeAttributes.Add( TKeyValuePair( TEXT( "Class" ), GetClass()->GetName() )); OutNodeAttributes.Add( TKeyValuePair( 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 UK2Node_MacroInstance::GetAllWildcardPins() const { UEdGraph* MacroGraph = GetMacroGraph(); if (MacroGraph == nullptr) { return TArray(); } TArray Result; for(UEdGraphNode* Node : MacroGraph->Nodes) { if (UK2Node_Tunnel* Tunnel = ExactCast(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>& OutDirtyNodePins, TSet& 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& InNodes) const { // Gather wild card pins on the tunnel pins: TArray TunnelWildcards; for (UEdGraphNode* Node : InNodes) { if (UK2Node_Tunnel* Tunnel = ExactCast(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> 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 WildcardCounts; TMap 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> CurrentDirtyNodePins = MoveTemp(DirtyNodePins); TArray NodesToForceInference; for(const TPair& DirtyNodePin : CurrentDirtyNodePins) { // Any wildcard pins that are connected to this pin need to be // inferred and marked dirty: UK2Node* TypedNode = CastChecked(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>&OutDirtyNodePins) { TSet 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& 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& 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