// Give specific editor modes a chance to highlight this connection or darken non-interesting connections void FKismetConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ float& Thickness, /*inout*/ FLinearColor& WireColor, /*inout*/bool& bDrawBubbles, /*inout*/bool& bBidirectional) { // Get the schema and grab the default color from it check(OutputPin); check(GraphObj); const UEdGraphSchema* Schema = GraphObj->GetSchema(); WireColor = Schema->GetPinTypeColor(OutputPin->PinType); const bool bDeemphasizeUnhoveredPins = HoveredPins.Num() > 0; // If this is a K2 graph, try to be a little more specific const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(Schema); if (K2Schema != NULL) { if (TreatWireAsExecutionPin(InputPin, OutputPin)) { if (CanBuildRoadmap()) { bool bExecuted = false; // Run thru the predecessors, and on if (FExecPairingMap* PredecessorMap = PredecessorNodes.Find(InputPin->GetOwningNode())) { if (FTimePair* Times = PredecessorMap->Find(OutputPin->GetOwningNode())) { bExecuted = true; DetermineStyleOfExecWire(/*inout*/ Thickness, /*inout*/ WireColor, /*inout*/ bDrawBubbles, *Times); } } if (!bExecuted) { // It's not followed, fade it and keep it thin WireColor = ReleaseColor; Thickness = ReleaseWireThickness; } } else { // Make exec wires slightly thicker even outside of debug Thickness = 3.0f; } } else { // Array types should draw thicker if( (InputPin && InputPin->PinType.bIsArray) || (OutputPin && OutputPin->PinType.bIsArray) ) { Thickness = 3.0f; } } } if (bDeemphasizeUnhoveredPins) { ApplyHoverDeemphasis(OutputPin, InputPin, /*inout*/ Thickness, /*inout*/ WireColor); } }
void FBlueprintProfilerConnectionDrawingPolicy::BuildExecutionRoadmap() { LatestTimeDiscovered = 0.0; // Only do highlighting in PIE or SIE if (!CanBuildRoadmap()) { return; } // Grab relevant profiler interfaces const FName InstanceName = BlueprintContext->GetActiveInstanceName(); TSharedPtr<FBlueprintFunctionContext> FunctionContext = BlueprintContext->GetFunctionContextFromGraph(GraphObj); if (!FunctionContext.IsValid()) { // No point in proceeding without a valid function context. return; } TArray<double> SequentialNodeTimes; TArray<UEdGraphPin*> SequentialExecPinsInGraph; TArray<FTracePath> SequentialTracePathsInGraph; TArray<TWeakPtr<FScriptExecutionNode>> SequentialProfilerNodesInGraph; const FName ScopedFunctionName = FunctionContext->GetFunctionName(); const FName GraphName = FunctionContext->GetGraphName(); const TSimpleRingBuffer<FBlueprintExecutionTrace>& TraceHistory = BlueprintContext->GetTraceHistory(); for (int32 i = 0; i < TraceHistory.Num(); ++i) { const FBlueprintExecutionTrace& Sample = TraceHistory(i); const bool bInstanceMatch = InstanceName == SPDN_Blueprint ? true : (InstanceName == Sample.InstanceName); if (bInstanceMatch && GraphName == Sample.GraphName) { if (Sample.ProfilerNode.IsValid()) { const bool bSamplePinValid = Sample.PinReference.Get() != nullptr; // Patch in any missing pins using the script offset. UEdGraphPin* Pin = bSamplePinValid ? Sample.PinReference.Get() : const_cast<UEdGraphPin*>(FunctionContext->GetPinFromCodeLocation(Sample.Offset)); if (Pin && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) { SequentialProfilerNodesInGraph.Add(Sample.ProfilerNode); SequentialNodeTimes.Add(Sample.ObservationTime); SequentialExecPinsInGraph.Add(Pin); SequentialTracePathsInGraph.Add(Sample.TracePath); } } } } // Run thru and apply bonus time const float InvNumNodes = 1.0f / (float)SequentialNodeTimes.Num(); for (int32 i = 0; i < SequentialProfilerNodesInGraph.Num(); ++i) { double& ObservationTime = SequentialNodeTimes[i]; const float PositionRatio = (SequentialNodeTimes.Num() - i) * InvNumNodes; const float PositionBonus = FMath::Pow(PositionRatio, TracePositionExponent) * TracePositionBonusPeriod; ObservationTime += PositionBonus; LatestTimeDiscovered = FMath::Max<double>(LatestTimeDiscovered, ObservationTime); } UEdGraphPin* OutputPin = nullptr; int32 OutputPinIndex = INDEX_NONE; for (int32 i = SequentialExecPinsInGraph.Num() - 1; i > 0; --i) { UEdGraphPin* InputPin = SequentialExecPinsInGraph[i]; if (InputPin->Direction == EGPD_Output) { OutputPin = InputPin; OutputPinIndex = i; } else if (OutputPin) { bool bLinked = false; for (auto LinkedPin : OutputPin->LinkedTo) { if (LinkedPin == InputPin) { bLinked = true; break; } } if (bLinked) { TSharedPtr<FScriptExecutionNode> CurExecNode = SequentialProfilerNodesInGraph[OutputPinIndex].Pin(); TSharedPtr<FScriptExecutionNode> NextExecNode = SequentialProfilerNodesInGraph[i].Pin(); if (CurExecNode.IsValid() && NextExecNode.IsValid() && CurExecNode != NextExecNode) { double NextNodeTime = SequentialNodeTimes[i]; UEdGraphNode* NextNode = const_cast<UEdGraphNode*>(NextExecNode->GetTypedObservedObject<UEdGraphNode>()); FExecPairingMap& ExecPaths = PredecessorPins.FindOrAdd(NextNode); FTimePair& ExecTiming = ExecPaths.FindOrAdd(OutputPin); FProfilerPairingMap& ProfilerPaths = ProfilerPins.FindOrAdd(NextNode); FProfilerPair& ProfilerData = ProfilerPaths.FindOrAdd(OutputPin); // make sure that if we've already visited this exec-pin (like // in a for-loop or something), that we're replacing it with a // more recent execution time // // @TODO I don't see when this wouldn't be the case if (ExecTiming.ThisExecTime < NextNodeTime) { double CurNodeTime = SequentialNodeTimes[OutputPinIndex]; ExecTiming.ThisExecTime = NextNodeTime; ExecTiming.PredExecTime = CurNodeTime; const bool bExecPin = CurExecNode->HasFlags(EScriptExecutionNodeFlags::ExecPin); const FTracePath& CurTracePath = bExecPin ? SequentialTracePathsInGraph[i] : SequentialTracePathsInGraph[OutputPinIndex]; const FTracePath& NextTracePath = SequentialTracePathsInGraph[i]; TSharedPtr<FScriptPerfData> CurPerfData = CurExecNode->GetPerfDataByInstanceAndTracePath(InstanceName, CurTracePath); TSharedPtr<FScriptPerfData> NextPerfData = NextExecNode->GetPerfDataByInstanceAndTracePath(InstanceName, NextTracePath); ProfilerData.bPureNode = CurExecNode->IsPureNode(); switch(WireHeatMode) { case EBlueprintProfilerHeatMapDisplayMode::Average: case EBlueprintProfilerHeatMapDisplayMode::PinToPin: { ProfilerData.PredPerfData = CurPerfData->GetAverageHeatLevel(); ProfilerData.ThisPerfData = NextPerfData->GetAverageHeatLevel(); break; } case EBlueprintProfilerHeatMapDisplayMode::Inclusive: { ProfilerData.PredPerfData = CurPerfData->GetInclusiveHeatLevel(); ProfilerData.ThisPerfData = NextPerfData->GetInclusiveHeatLevel(); break; } case EBlueprintProfilerHeatMapDisplayMode::MaxTiming: { ProfilerData.PredPerfData = CurPerfData->GetMaxTimeHeatLevel(); ProfilerData.ThisPerfData = NextPerfData->GetMaxTimeHeatLevel(); break; } case EBlueprintProfilerHeatMapDisplayMode::Total: { ProfilerData.PredPerfData = CurPerfData->GetTotalHeatLevel(); ProfilerData.ThisPerfData = NextPerfData->GetTotalHeatLevel(); break; } case EBlueprintProfilerHeatMapDisplayMode::HottestPath: { ProfilerData.PredPerfData = CurPerfData->GetHottestPathHeatLevel(); ProfilerData.ThisPerfData = NextPerfData->GetHottestPathHeatLevel(); break; } } } } } } } // Fade only when free-running (since we're using FApp::GetCurrentTime(), instead of FPlatformTime::Seconds) const double MaxTimeAhead = FMath::Min(FApp::GetCurrentTime() + 2*TracePositionBonusPeriod, LatestTimeDiscovered); //@TODO: Rough clamping; should be exposed as a parameter CurrentTime = FMath::Max(FApp::GetCurrentTime(), MaxTimeAhead); }
void FKismetConnectionDrawingPolicy::BuildExecutionRoadmap() { LatestTimeDiscovered = 0.0; // Only do highlighting in PIE or SIE if (!CanBuildRoadmap()) { return; } UBlueprint* TargetBP = FBlueprintEditorUtils::FindBlueprintForGraphChecked(GraphObj); UObject* ActiveObject = TargetBP->GetObjectBeingDebugged(); check(ActiveObject); // Due to CanBuildRoadmap // Redirect the target Blueprint when debugging with a macro graph visible if (TargetBP->BlueprintType == BPTYPE_MacroLibrary) { TargetBP = Cast<UBlueprint>(ActiveObject->GetClass()->ClassGeneratedBy); } TArray<UEdGraphNode*> SequentialNodesInGraph; TArray<double> SequentialNodeTimes; TArray<UEdGraphPin*> SequentialExecPinsInGraph; { const TSimpleRingBuffer<FKismetTraceSample>& TraceStack = FKismetDebugUtilities::GetTraceStack(); UBlueprintGeneratedClass* TargetClass = Cast<UBlueprintGeneratedClass>(TargetBP->GeneratedClass); FBlueprintDebugData& DebugData = TargetClass->GetDebugData(); for (int32 i = 0; i < TraceStack.Num(); ++i) { const FKismetTraceSample& Sample = TraceStack(i); if (UObject* TestObject = Sample.Context.Get()) { if (TestObject == ActiveObject) { UEdGraphPin* AssociatedPin = DebugData.FindExecPinFromCodeLocation(Sample.Function.Get(), Sample.Offset); if (UEdGraphNode* Node = DebugData.FindSourceNodeFromCodeLocation(Sample.Function.Get(), Sample.Offset, /*bAllowImpreciseHit=*/ false)) { if (GraphObj == Node->GetGraph()) { SequentialNodesInGraph.Add(Node); SequentialNodeTimes.Add(Sample.ObservationTime); SequentialExecPinsInGraph.Add(AssociatedPin); } else { // If the top-level source node is a macro instance node UK2Node_MacroInstance* MacroInstanceNode = Cast<UK2Node_MacroInstance>(Node); if (MacroInstanceNode) { // Attempt to locate the macro source node through the code mapping UEdGraphNode* MacroSourceNode = DebugData.FindMacroSourceNodeFromCodeLocation(Sample.Function.Get(), Sample.Offset); if (MacroSourceNode) { // If the macro source node is located in the current graph context if (GraphObj == MacroSourceNode->GetGraph()) { // Add it to the sequential node list SequentialNodesInGraph.Add(MacroSourceNode); SequentialNodeTimes.Add(Sample.ObservationTime); SequentialExecPinsInGraph.Add(AssociatedPin); } else { // The macro source node isn't in the current graph context, but we might have a macro instance node that is // in the current graph context, so obtain the set of macro instance nodes that are mapped to the code here. TArray<UEdGraphNode*> MacroInstanceNodes; DebugData.FindMacroInstanceNodesFromCodeLocation(Sample.Function.Get(), Sample.Offset, MacroInstanceNodes); // For each macro instance node in the set for (auto MacroInstanceNodeIt = MacroInstanceNodes.CreateConstIterator(); MacroInstanceNodeIt; ++MacroInstanceNodeIt) { // If the macro instance node is located in the current graph context MacroInstanceNode = Cast<UK2Node_MacroInstance>(*MacroInstanceNodeIt); if (MacroInstanceNode && GraphObj == MacroInstanceNode->GetGraph()) { // Add it to the sequential node list SequentialNodesInGraph.Add(MacroInstanceNode); SequentialNodeTimes.Add(Sample.ObservationTime); SequentialExecPinsInGraph.Add(AssociatedPin); // Exit the loop; we're done break; } } } } } } } } } } } // Run thru and apply bonus time const float InvNumNodes = 1.0f / (float)SequentialNodeTimes.Num(); for (int32 i = 0; i < SequentialNodesInGraph.Num(); ++i) { double& ObservationTime = SequentialNodeTimes[i]; const float PositionRatio = (SequentialNodeTimes.Num() - i) * InvNumNodes; const float PositionBonus = FMath::Pow(PositionRatio, TracePositionExponent) * TracePositionBonusPeriod; ObservationTime += PositionBonus; LatestTimeDiscovered = FMath::Max<double>(LatestTimeDiscovered, ObservationTime); } UEdGraphPin* LastExecPin = NULL; // Record the unique exec-pin to time pairings, keeping only the most recent // times for each pairing... reverse the "SequentialNodes" because right now // it is in stack order (with the last executed node first) for (int32 i = SequentialNodesInGraph.Num() - 1; i >= 1; --i) { UEdGraphNode* CurNode = SequentialNodesInGraph[i]; UEdGraphNode* NextNode = SequentialNodesInGraph[i-1]; // keep track of the last exec-pin executed by CurNode (these tracked // pins coincide with "WireTraceSite" op-codes that have been injected // prior to every "goto" statement... this way we have context for which // pin executed the jump) if (UEdGraphPin* AssociatedPin = SequentialExecPinsInGraph[i]) { LastExecPin = AssociatedPin; } // if this statement is a jump (from one node to another) if (CurNode != NextNode) { // if there was a wire-trace op-code inserted before this jump if (LastExecPin != NULL) { //ensure(LastExecPin->GetOwningNode() == CurNode); double NextNodeTime = SequentialNodeTimes[i-1]; FExecPairingMap& ExecPaths = PredecessorPins.FindOrAdd(NextNode); FTimePair& ExecTiming = ExecPaths.FindOrAdd(LastExecPin); // make sure that if we've already visited this exec-pin (like // in a for-loop or something), that we're replacing it with a // more recent execution time // // @TODO I don't see when this wouldn't be the case if (ExecTiming.ThisExecTime < NextNodeTime) { double CurNodeTime = SequentialNodeTimes[i]; ExecTiming.ThisExecTime = NextNodeTime; ExecTiming.PredExecTime = CurNodeTime; } } // if the nodes aren't graphically connected how could they be // executed back-to-back? well, this could be a pop back to a // sequence node from the end of one thread of execution, etc. else if (AreNodesGraphicallySequential(CurNode, NextNode)) { // only warn when the nodes are directly connected (this is all // for execution flow visualization after all) UE_LOG(LogConnectionDrawingPolicy, Warning, TEXT("Looks like a wire-trace was not injected before the jump from '%s' to '%s'."), *CurNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString(), *NextNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString()); } // clear the exec-pin (we're moving to a new node and want to find // it's executed out pin) LastExecPin = NULL; } // else, we're only collecting this data for tracing node-to-node // executions (so we don't care about this sequence of statements) } // Fade only when free-running (since we're using FApp::GetCurrentTime(), instead of FPlatformTime::Seconds) const double MaxTimeAhead = FMath::Min(FApp::GetCurrentTime() + 2*TracePositionBonusPeriod, LatestTimeDiscovered); //@TODO: Rough clamping; should be exposed as a parameter CurrentTime = FMath::Max(FApp::GetCurrentTime(), MaxTimeAhead); }
void FKismetConnectionDrawingPolicy::BuildExecutionRoadmap() { LatestTimeDiscovered = 0.0; // Only do highlighting in PIE or SIE if (!CanBuildRoadmap()) { return; } UBlueprint* TargetBP = FBlueprintEditorUtils::FindBlueprintForGraphChecked(GraphObj); UObject* ActiveObject = TargetBP->GetObjectBeingDebugged(); check(ActiveObject); // Due to CanBuildRoadmap // Redirect the target Blueprint when debugging with a macro graph visible if (TargetBP->BlueprintType == BPTYPE_MacroLibrary) { TargetBP = Cast<UBlueprint>(ActiveObject->GetClass()->ClassGeneratedBy); } TArray<UEdGraphNode*> SequentialNodesInGraph; TArray<double> SequentialNodeTimes; { const TSimpleRingBuffer<FKismetTraceSample>& TraceStack = FKismetDebugUtilities::GetTraceStack(); UBlueprintGeneratedClass* TargetClass = Cast<UBlueprintGeneratedClass>(TargetBP->GeneratedClass); FBlueprintDebugData& DebugData = TargetClass->GetDebugData(); for (int32 i = 0; i < TraceStack.Num(); ++i) { const FKismetTraceSample& Sample = TraceStack(i); if (UObject* TestObject = Sample.Context.Get()) { if (TestObject == ActiveObject) { if (UEdGraphNode* Node = DebugData.FindSourceNodeFromCodeLocation(Sample.Function.Get(), Sample.Offset, /*bAllowImpreciseHit=*/ false)) { if (GraphObj == Node->GetGraph()) { SequentialNodesInGraph.Add(Node); SequentialNodeTimes.Add(Sample.ObservationTime); } else { // If the top-level source node is a macro instance node UK2Node_MacroInstance* MacroInstanceNode = Cast<UK2Node_MacroInstance>(Node); if (MacroInstanceNode) { // Attempt to locate the macro source node through the code mapping UEdGraphNode* MacroSourceNode = DebugData.FindMacroSourceNodeFromCodeLocation(Sample.Function.Get(), Sample.Offset); if (MacroSourceNode) { // If the macro source node is located in the current graph context if (GraphObj == MacroSourceNode->GetGraph()) { // Add it to the sequential node list SequentialNodesInGraph.Add(MacroSourceNode); SequentialNodeTimes.Add(Sample.ObservationTime); } else { // The macro source node isn't in the current graph context, but we might have a macro instance node that is // in the current graph context, so obtain the set of macro instance nodes that are mapped to the code here. TArray<UEdGraphNode*> MacroInstanceNodes; DebugData.FindMacroInstanceNodesFromCodeLocation(Sample.Function.Get(), Sample.Offset, MacroInstanceNodes); // For each macro instance node in the set for (auto MacroInstanceNodeIt = MacroInstanceNodes.CreateConstIterator(); MacroInstanceNodeIt; ++MacroInstanceNodeIt) { // If the macro instance node is located in the current graph context MacroInstanceNode = Cast<UK2Node_MacroInstance>(*MacroInstanceNodeIt); if (MacroInstanceNode && GraphObj == MacroInstanceNode->GetGraph()) { // Add it to the sequential node list SequentialNodesInGraph.Add(MacroInstanceNode); SequentialNodeTimes.Add(Sample.ObservationTime); // Exit the loop; we're done break; } } } } } } } } } } } // Run thru and apply bonus time const float InvNumNodes = 1.0f / (float)SequentialNodeTimes.Num(); for (int32 i = 0; i < SequentialNodesInGraph.Num(); ++i) { double& ObservationTime = SequentialNodeTimes[i]; const float PositionRatio = (SequentialNodeTimes.Num() - i) * InvNumNodes; const float PositionBonus = FMath::Pow(PositionRatio, TracePositionExponent) * TracePositionBonusPeriod; ObservationTime += PositionBonus; LatestTimeDiscovered = FMath::Max<double>(LatestTimeDiscovered, ObservationTime); } // Record the unique node->node pairings, keeping only the most recent times for each pairing for (int32 i = SequentialNodesInGraph.Num() - 1; i >= 1; --i) { UEdGraphNode* CurNode = SequentialNodesInGraph[i]; double CurNodeTime = SequentialNodeTimes[i]; UEdGraphNode* NextNode = SequentialNodesInGraph[i-1]; double NextNodeTime = SequentialNodeTimes[i-1]; FExecPairingMap& Predecessors = PredecessorNodes.FindOrAdd(NextNode); // Update the timings if this is a more recent pairing FTimePair& Timings = Predecessors.FindOrAdd(CurNode); if (Timings.ThisExecTime < NextNodeTime) { Timings.PredExecTime = CurNodeTime; Timings.ThisExecTime = NextNodeTime; } } // Fade only when free-running (since we're using GCurrentTime, instead of FPlatformTime::Seconds) const double MaxTimeAhead = FMath::Min(GCurrentTime + 2*TracePositionBonusPeriod, LatestTimeDiscovered); //@TODO: Rough clamping; should be exposed as a parameter CurrentTime = FMath::Max(GCurrentTime, MaxTimeAhead); }
// Give specific editor modes a chance to highlight this connection or darken non-interesting connections void FKismetConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ FConnectionParams& Params) { Params.AssociatedPin1 = OutputPin; Params.AssociatedPin2 = InputPin; // Get the schema and grab the default color from it check(OutputPin); check(GraphObj); const UEdGraphSchema* Schema = GraphObj->GetSchema(); Params.WireColor = Schema->GetPinTypeColor(OutputPin->PinType); UEdGraphNode* OutputNode = OutputPin->GetOwningNode(); UEdGraphNode* InputNode = (InputPin != nullptr) ? InputPin->GetOwningNode() : nullptr; const bool bDeemphasizeUnhoveredPins = HoveredPins.Num() > 0; // If this is a K2 graph, try to be a little more specific const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(Schema); if (K2Schema != NULL) { // If the output or input connect to a knot that is going backwards, we will flip the direction on values going into them { if (UK2Node_Knot* OutputKnotNode = Cast<UK2Node_Knot>(OutputNode)) { if (ShouldChangeTangentForKnot(OutputKnotNode)) { Params.StartDirection = EGPD_Input; } } if (UK2Node_Knot* InputKnotNode = Cast<UK2Node_Knot>(InputNode)) { if (ShouldChangeTangentForKnot(InputKnotNode)) { Params.EndDirection = EGPD_Output; } } } if (TreatWireAsExecutionPin(InputPin, OutputPin)) { if (CanBuildRoadmap()) { // knot nodes are removed from the graph at compile time, so we // have to follow them until we find something that would have // actually executed while (UK2Node_Knot* InputKnotNode = Cast<UK2Node_Knot>(InputNode)) { InputNode = nullptr; UEdGraphPin* OutPin = InputKnotNode->GetOutputPin(); if (OutPin->LinkedTo.Num() > 0) { check(OutPin->LinkedTo.Num() == 1); InputNode = OutPin->LinkedTo[0]->GetOwningNode(); } } // track if this node connection was ran or not bool bExecuted = false; // if the node belonging to InputPin was actually executed if (FExecPairingMap* ExecPaths = PredecessorPins.Find(InputNode)) { // if the output pin is one of the pins that lead to InputNode being ran if (FTimePair const* ExecTiming = BackTraceExecPath(OutputPin, ExecPaths)) { bExecuted = true; DetermineStyleOfExecWire(/*inout*/ Params.WireThickness, /*inout*/ Params.WireColor, /*inout*/ Params.bDrawBubbles, *ExecTiming); } } if (!bExecuted) { // It's not followed, fade it and keep it thin Params.WireColor = ReleaseColor; Params.WireThickness = ReleaseWireThickness; } } else { // Make exec wires slightly thicker even outside of debug Params.WireThickness = 3.0f; } } else { // Array types should draw thicker if ((InputPin && InputPin->PinType.bIsArray) || (OutputPin && OutputPin->PinType.bIsArray)) { Params.WireThickness = 3.0f; } } } if (bDeemphasizeUnhoveredPins) { ApplyHoverDeemphasis(OutputPin, InputPin, /*inout*/ Params.WireThickness, /*inout*/ Params.WireColor); } }