// Copyright Epic Games, Inc. All Rights Reserved. #include "KismetNodes/KismetNodeInfoContext.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphSchema_K2.h" #include "Engine/Blueprint.h" #include "Engine/BlueprintGeneratedClass.h" #include "Engine/Engine.h" #include "Engine/LatentActionManager.h" #include "Engine/World.h" #include "HAL/PlatformCrt.h" #include "K2Node_CallFunction.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetDebugUtilities.h" #include "Misc/AssertionMacros.h" #include "UObject/Class.h" #include "UObject/Field.h" #include "UObject/UnrealType.h" class UEdGraphNode; ////////////////////////////////////////////////////////////////////////// // FKismetNodeInfoContext // Context used to aid debugging displays for nodes FKismetNodeInfoContext::FKismetNodeInfoContext(UEdGraph* SourceGraph) : ActiveObjectBeingDebugged(NULL) { // Only show pending latent actions in PIE/SIE mode SourceBlueprint = FBlueprintEditorUtils::FindBlueprintForGraph(SourceGraph); if (SourceBlueprint != NULL) { ActiveObjectBeingDebugged = SourceBlueprint->GetObjectBeingDebugged(); // Run thru debugged objects to see if any are objects with pending latent actions if (ActiveObjectBeingDebugged != NULL) { UBlueprintGeneratedClass* Class = CastChecked((UObject*)(ActiveObjectBeingDebugged->GetClass())); FBlueprintDebugData const& ClassDebugData = Class->GetDebugData(); TSet LatentContextObjects; TArray FunctionNodes; SourceGraph->GetNodesOfClass(FunctionNodes); // collect all the world context objects for all of the graph's latent nodes for (UK2Node_CallFunction const* FunctionNode : FunctionNodes) { UFunction* Function = FunctionNode->GetTargetFunction(); if ((Function == NULL) || !Function->HasMetaData(FBlueprintMetadata::MD_Latent)) { continue; } UObject* NodeWorldContext = ActiveObjectBeingDebugged; // if the node has a specific "world context" pin, attempt to get the value set for that first if (Function->HasMetaData(FBlueprintMetadata::MD_WorldContext)) { const FString& WorldContextPinName = Function->GetMetaData(FBlueprintMetadata::MD_WorldContext); if (UEdGraphPin* ContextPin = FunctionNode->FindPin(WorldContextPinName)) { if (FObjectPropertyBase* ContextProperty = CastField(ClassDebugData.FindClassPropertyForPin(ContextPin))) { UObject* PropertyValue = ContextProperty->GetObjectPropertyValue_InContainer(ActiveObjectBeingDebugged); if (PropertyValue != NULL) { NodeWorldContext = PropertyValue; } } } } LatentContextObjects.Add(NodeWorldContext); } for (UObject* ContextObject : LatentContextObjects) { if (UWorld* World = GEngine->GetWorldFromContextObject(ContextObject, EGetWorldErrorMode::ReturnNull)) { FLatentActionManager& Manager = World->GetLatentActionManager(); TSet UUIDSet; Manager.GetActiveUUIDs(ActiveObjectBeingDebugged, /*out*/ UUIDSet); for (TSet::TConstIterator IterUUID(UUIDSet); IterUUID; ++IterUUID) { const int32 UUID = *IterUUID; if (UEdGraphNode* ParentNode = ClassDebugData.FindNodeFromUUID(UUID)) { TArray& Pairs = NodesWithActiveLatentActions.FindOrAdd(ParentNode); new (Pairs) FObjectUUIDPair(ContextObject, UUID); } } } } } // Covert the watched pin array into a set FKismetDebugUtilities::ForeachPinWatch( SourceBlueprint, [&WatchedPinSet = WatchedPinSet, &WatchedNodeSet = WatchedNodeSet] (UEdGraphPin* WatchedPin) { if (!ensure(WatchedPin)) { return; // ~continue } UEdGraphNode* OwningNode = WatchedPin->GetOuter(); if (!ensure(OwningNode != NULL)) // shouldn't happen, but just in case a dead pin was added to the WatchedPins array { return; // ~continue } check(OwningNode == WatchedPin->GetOwningNode()); WatchedPinSet.Add(WatchedPin); WatchedNodeSet.Add(OwningNode); } ); } }