// Reconcile other pin links: // - Links between nodes within the copied set are fine // - Links to nodes that were not copied need to be fixed up if the copy-paste was in the same graph or broken completely // Call PostPasteNode on each node void FEdGraphUtilities::PostProcessPastedNodes(TSet<UEdGraphNode*>& SpawnedNodes) { // Run thru and fix up the node's pin links; they may point to invalid pins if the paste was to another graph for (TSet<UEdGraphNode*>::TIterator It(SpawnedNodes); It; ++It) { UEdGraphNode* Node = *It; UEdGraph* CurrentGraph = Node->GetGraph(); for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) { UEdGraphPin* ThisPin = Node->Pins[PinIndex]; for (int32 LinkIndex = 0; LinkIndex < ThisPin->LinkedTo.Num(); ) { UEdGraphPin* OtherPin = ThisPin->LinkedTo[LinkIndex]; if (OtherPin == NULL) { // Totally bogus link ThisPin->LinkedTo.RemoveAtSwap(LinkIndex); } else if (!SpawnedNodes.Contains(OtherPin->GetOwningNode())) { // It's a link across the selection set, so it should be broken OtherPin->LinkedTo.RemoveSwap(ThisPin); ThisPin->LinkedTo.RemoveAtSwap(LinkIndex); } else if (!OtherPin->LinkedTo.Contains(ThisPin)) { // The link needs to be reciprocal check(OtherPin->GetOwningNode()->GetGraph() == CurrentGraph); OtherPin->LinkedTo.Add(ThisPin); ++LinkIndex; } else { // Everything seems fine but sanity check the graph check(OtherPin->GetOwningNode()->GetGraph() == CurrentGraph); ++LinkIndex; } } } } // Give every node a chance to deep copy associated resources, etc... for (TSet<UEdGraphNode*>::TIterator It(SpawnedNodes); It; ++It) { UEdGraphNode* Node = *It; Node->PostPasteNode(); Node->ReconstructNode(); // Ensure we have RF_Transactional set on all pasted nodes, as its not copied in the T3D format Node->SetFlags(RF_Transactional); } }
void UMaterialGraphSchema::BreakNodeLinks(UEdGraphNode& TargetNode) const { bool bHasLinksToBreak = false; for (auto PinIt = TargetNode.Pins.CreateConstIterator(); PinIt; ++PinIt) { UEdGraphPin* Pin = *PinIt; for (auto LinkIt = Pin->LinkedTo.CreateConstIterator(); LinkIt; ++LinkIt) { if (*LinkIt) { bHasLinksToBreak = true; } } } Super::BreakNodeLinks(TargetNode); if (bHasLinksToBreak) { FMaterialEditorUtilities::UpdateMaterialAfterGraphChange(TargetNode.GetGraph()); } }
void FKismetVariableDragDropAction::HoverTargetChanged() { UProperty* VariableProperty = GetVariableProperty(); if (VariableProperty == nullptr) { return; } FString VariableString = VariableName.ToString(); // Icon/text to draw on tooltip FSlateColor IconColor = FLinearColor::White; const FSlateBrush* StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); FText Message = LOCTEXT("InvalidDropTarget", "Invalid drop target!"); UEdGraphPin* PinUnderCursor = GetHoveredPin(); bool bCanMakeSetter = true; bool bBadSchema = false; bool bBadGraph = false; UEdGraph* HoveredGraph = GetHoveredGraph(); if (HoveredGraph) { if (Cast<const UEdGraphSchema_K2>(HoveredGraph->GetSchema()) == NULL) { bBadSchema = true; } else if(!CanVariableBeDropped(VariableProperty, *HoveredGraph)) { bBadGraph = true; } UStruct* Outer = CastChecked<UStruct>(VariableProperty->GetOuter()); FNodeConstructionParams NewNodeParams; NewNodeParams.VariableName = VariableName; const UBlueprint* DropOnBlueprint = FBlueprintEditorUtils::FindBlueprintForGraph(HoveredGraph); NewNodeParams.Graph = HoveredGraph; NewNodeParams.VariableSource = Outer; bCanMakeSetter = CanExecuteMakeSetter(NewNodeParams, VariableProperty); } UEdGraphNode* VarNodeUnderCursor = Cast<UK2Node_Variable>(GetHoveredNode()); if (bBadSchema) { StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); Message = LOCTEXT("CannotCreateInThisSchema", "Cannot access variables in this type of graph"); } else if(bBadGraph) { FFormatNamedArguments Args; Args.Add(TEXT("VariableName"), FText::FromString(VariableString)); Args.Add(TEXT("Scope"), FText::FromString(HoveredGraph->GetName())); StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); if(IsFromBlueprint(FBlueprintEditorUtils::FindBlueprintForGraph(HoveredGraph)) && VariableProperty->GetOuter()->IsA(UFunction::StaticClass())) { Message = FText::Format( LOCTEXT("IncorrectGraphForLocalVariable_Error", "Cannot place local variable '{VariableName}' in external scope '{Scope}'"), Args); } else { Message = FText::Format( LOCTEXT("IncorrectGraphForVariable_Error", "Cannot place variable '{VariableName}' in external scope '{Scope}'"), Args); } } else if (PinUnderCursor != NULL) { FFormatNamedArguments Args; Args.Add(TEXT("PinUnderCursor"), FText::FromString(PinUnderCursor->PinName)); Args.Add(TEXT("VariableName"), FText::FromString(VariableString)); if(CanVariableBeDropped(VariableProperty, *PinUnderCursor->GetOwningNode()->GetGraph())) { const UEdGraphSchema_K2* Schema = CastChecked<const UEdGraphSchema_K2>(PinUnderCursor->GetSchema()); const bool bIsRead = PinUnderCursor->Direction == EGPD_Input; const UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNode(PinUnderCursor->GetOwningNode()); const bool bReadOnlyProperty = FBlueprintEditorUtils::IsPropertyReadOnlyInCurrentBlueprint(Blueprint, VariableProperty); const bool bCanWriteIfNeeded = bIsRead || !bReadOnlyProperty; FEdGraphPinType VariablePinType; Schema->ConvertPropertyToPinType(VariableProperty, VariablePinType); const bool bTypeMatch = Schema->ArePinTypesCompatible(VariablePinType, PinUnderCursor->PinType); Args.Add(TEXT("PinUnderCursor"), FText::FromString(PinUnderCursor->PinName)); if (bTypeMatch && bCanWriteIfNeeded) { StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")); if (bIsRead) { Message = FText::Format(LOCTEXT("MakeThisEqualThat_PinEqualVariableName", "Make {PinUnderCursor} = {VariableName}"), Args); } else { Message = FText::Format(LOCTEXT("MakeThisEqualThat_VariableNameEqualPin", "Make {VariableName} = {PinUnderCursor}"), Args); } } else { StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); if (!bCanWriteIfNeeded) { Message = FText::Format(LOCTEXT("ReadOnlyVar_Error", "Cannot write to read-only variable '{VariableName}'"), Args); } else { Message = FText::Format(LOCTEXT("NotCompatible_Error", "The type of '{VariableName}' is not compatible with {PinUnderCursor}"), Args); } } } else { Args.Add(TEXT("Scope"), FText::FromString(PinUnderCursor->GetOwningNode()->GetGraph()->GetName())); StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); Message = FText::Format( LOCTEXT("IncorrectGraphForPin_Error", "Cannot place local variable '{VariableName}' in external scope '{Scope}'"), Args); } } else if (VarNodeUnderCursor != NULL) { FFormatNamedArguments Args; Args.Add(TEXT("VariableName"), FText::FromString(VariableString)); if(CanVariableBeDropped(VariableProperty, *VarNodeUnderCursor->GetGraph())) { const bool bIsRead = VarNodeUnderCursor->IsA(UK2Node_VariableGet::StaticClass()); const UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNode(VarNodeUnderCursor); const bool bReadOnlyProperty = FBlueprintEditorUtils::IsPropertyReadOnlyInCurrentBlueprint(Blueprint, VariableProperty); const bool bCanWriteIfNeeded = bIsRead || !bReadOnlyProperty; if (bCanWriteIfNeeded) { Args.Add(TEXT("ReadOrWrite"), bIsRead ? LOCTEXT("Read", "read") : LOCTEXT("Write", "write")); if(WillBreakLinks(VarNodeUnderCursor, VariableProperty)) { StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OKWarn")); Message = FText::Format( LOCTEXT("ChangeNodeToWarnBreakLinks", "Change node to {ReadOrWrite} '{VariableName}', WARNING this will break links!"), Args); } else { StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")); Message = FText::Format( LOCTEXT("ChangeNodeTo", "Change node to {ReadOrWrite} '{VariableName}'"), Args); } } else { StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); Message = FText::Format( LOCTEXT("ReadOnlyVar_Error", "Cannot write to read-only variable '{VariableName}'"), Args); } } else { Args.Add(TEXT("Scope"), FText::FromString(VarNodeUnderCursor->GetGraph()->GetName())); StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); Message = FText::Format( LOCTEXT("IncorrectGraphForNodeReplace_Error", "Cannot replace node with local variable '{VariableName}' in external scope '{Scope}'"), Args); } } else if (!HoveredCategoryName.IsEmpty()) { // Find Blueprint that made this class and get category of variable FText Category; UBlueprint* Blueprint; // Find the Blueprint for this property if(Cast<UFunction>(VariableSource.Get())) { Blueprint = UBlueprint::GetBlueprintFromClass(Cast<UClass>(VariableSource->GetOuter())); } else { Blueprint = UBlueprint::GetBlueprintFromClass(Cast<UClass>(VariableSource.Get())); } if(Blueprint != NULL) { Category = FBlueprintEditorUtils::GetBlueprintVariableCategory(Blueprint, VariableProperty->GetFName(), GetLocalVariableScope() ); } // See if class is native UClass* OuterClass = Cast<UClass>(VariableProperty->GetOuter()); if(OuterClass || Cast<UFunction>(VariableProperty->GetOuter())) { const bool bIsNativeVar = (OuterClass && OuterClass->ClassGeneratedBy == NULL); FFormatNamedArguments Args; Args.Add(TEXT("VariableName"), FText::FromString(VariableString)); Args.Add(TEXT("HoveredCategoryName"), HoveredCategoryName); if (bIsNativeVar) { StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); Message = FText::Format( LOCTEXT("ChangingCatagoryNotThisVar", "Cannot change category for variable '{VariableName}'"), Args ); } else if (Category.EqualTo(HoveredCategoryName)) { StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); Message = FText::Format( LOCTEXT("ChangingCatagoryAlreadyIn", "Variable '{VariableName}' is already in category '{HoveredCategoryName}'"), Args ); } else { StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")); Message = FText::Format( LOCTEXT("ChangingCatagoryOk", "Move variable '{VariableName}' to category '{HoveredCategoryName}'"), Args ); } } } else if (HoveredAction.IsValid()) { if(HoveredAction.Pin()->GetTypeId() == FEdGraphSchemaAction_K2Var::StaticGetTypeId()) { FEdGraphSchemaAction_K2Var* VarAction = (FEdGraphSchemaAction_K2Var*)HoveredAction.Pin().Get(); FName TargetVarName = VarAction->GetVariableName(); // Needs to have a valid index to move it (this excludes variables added through other means, like timelines/components int32 MoveVarIndex = INDEX_NONE; int32 TargetVarIndex = INDEX_NONE; UBlueprint* Blueprint = UBlueprint::GetBlueprintFromClass(Cast<UClass>(VariableSource.Get())); if(Blueprint != NULL) { MoveVarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VariableName); TargetVarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, TargetVarName); } FFormatNamedArguments Args; Args.Add(TEXT("VariableName"), FText::FromString(VariableString)); Args.Add(TEXT("TargetVarName"), FText::FromName(TargetVarName)); if(MoveVarIndex == INDEX_NONE) { StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); Message = FText::Format( LOCTEXT("MoveVarDiffClass", "Cannot reorder variable '{VariableName}'."), Args ); } else if(TargetVarIndex == INDEX_NONE) { StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); Message = FText::Format( LOCTEXT("MoveVarOther", "Cannot reorder variable '{VariableName}' before '{TargetVarName}'."), Args ); } else if(VariableName == TargetVarName) { StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); Message = FText::Format( LOCTEXT("MoveVarYourself", "Cannot reorder variable '{VariableName}' before itself."), Args ); } else { StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.OK")); Message = FText::Format( LOCTEXT("MoveVarOK", "Reorder variable '{VariableName}' before '{TargetVarName}'"), Args ); } } } else if (bAltDrag && !bCanMakeSetter) { FFormatNamedArguments Args; Args.Add(TEXT("VariableName"), FText::FromString(VariableString)); StatusSymbol = FEditorStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")); Message = FText::Format(LOCTEXT("CannotPlaceSetter", "Variable '{VariableName}' is readonly, you cannot set this variable."), Args); } // Draw variable icon else { StatusSymbol = FBlueprintEditor::GetVarIconAndColor(VariableSource.Get(), VariableName, IconColor); Message = FText::FromString(VariableString); } SetSimpleFeedbackMessage(StatusSymbol, IconColor, Message); }
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); }
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); }