예제 #1
0
// 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);
}