bool FKismetConnectionDrawingPolicy::CanBuildRoadmap() const
{
	UBlueprint* TargetBP = FBlueprintEditorUtils::FindBlueprintForGraphChecked(GraphObj);
	UObject* ActiveObject = TargetBP->GetObjectBeingDebugged();

	return ActiveObject != NULL;
}
void FBlueprintCompileReinstancer::ReplaceInstancesOfClass(UClass* OldClass, UClass* NewClass, UObject*	OriginalCDO, TSet<UObject*>* ObjectsThatShouldUseOldStuff)
{
	USelection* SelectedActors;
	bool bSelectionChanged = false;
	TArray<UObject*> ObjectsToReplace;
	const bool bLogConversions = false; // for debugging

	// Map of old objects to new objects
	TMap<UObject*, UObject*> OldToNewInstanceMap;
	TMap<UClass*, UClass*>   OldToNewClassMap;
	OldToNewClassMap.Add(OldClass, NewClass);

	TMap<FStringAssetReference, UObject*> ReinstancedObjectsWeakReferenceMap;

	// actors being replace
	TArray<FActorReplacementHelper> ReplacementActors;

	// A list of objects (e.g. Blueprints) that potentially have editors open that we need to refresh
	TArray<UObject*> PotentialEditorsForRefreshing;

	// A list of component owners that need their construction scripts re-ran (because a component of theirs has been reinstanced)
	TSet<AActor*> OwnersToReconstruct;

	// Set global flag to let system know we are reconstructing blueprint instances
	TGuardValue<bool> GuardTemplateNameFlag(GIsReconstructingBlueprintInstances, true);

	struct FObjectRemappingHelper
	{
		void OnObjectsReplaced(const TMap<UObject*, UObject*>& InReplacedObjects)
		{
			ReplacedObjects.Append(InReplacedObjects);
		}

		TMap<UObject*, UObject*> ReplacedObjects;
	} ObjectRemappingHelper;

	FDelegateHandle OnObjectsReplacedHandle = GEditor->OnObjectsReplaced().AddRaw(&ObjectRemappingHelper,&FObjectRemappingHelper::OnObjectsReplaced);

	{ BP_SCOPED_COMPILER_EVENT_STAT(EKismetReinstancerStats_ReplaceInstancesOfClass);


		const bool bIncludeDerivedClasses = false;
		GetObjectsOfClass(OldClass, ObjectsToReplace, bIncludeDerivedClasses);
	
		SelectedActors = GEditor->GetSelectedActors();
		SelectedActors->BeginBatchSelectOperation();
		SelectedActors->Modify();
		
		// Then fix 'real' (non archetype) instances of the class
		for (UObject* OldObject : ObjectsToReplace)
		{
			// Skip non-archetype instances, EXCEPT for component templates
			const bool bIsComponent = NewClass->IsChildOf(UActorComponent::StaticClass());
			if ((!bIsComponent && OldObject->IsTemplate()) || OldObject->IsPendingKill())
			{
				continue;
			}

			UBlueprint* CorrespondingBlueprint  = Cast<UBlueprint>(OldObject->GetClass()->ClassGeneratedBy);
			UObject*    OldBlueprintDebugObject = nullptr;
			// If this object is being debugged, cache it off so we can preserve the 'object being debugged' association
			if ((CorrespondingBlueprint != nullptr) && (CorrespondingBlueprint->GetObjectBeingDebugged() == OldObject))
			{
				OldBlueprintDebugObject = OldObject;
			}

			AActor*  OldActor = Cast<AActor>(OldObject);
			UObject* NewUObject = nullptr;
			// if the object to replace is an actor...
			if (OldActor != nullptr)
			{
				FVector  Location = FVector::ZeroVector;
				FRotator Rotation = FRotator::ZeroRotator;
				if (USceneComponent* OldRootComponent = OldActor->GetRootComponent())
				{
					Location = OldActor->GetActorLocation();
					Rotation = OldActor->GetActorRotation();
				}

				// If this actor was spawned from an Archetype, we spawn the new actor from the new version of that archetype
				UObject* OldArchetype = OldActor->GetArchetype();
				UWorld*  World        = OldActor->GetWorld();
				AActor*  NewArchetype = Cast<AActor>(OldToNewInstanceMap.FindRef(OldArchetype));
				// Check that either this was an instance of the class directly, or we found a new archetype for it
				check(OldArchetype == OldClass->GetDefaultObject() || NewArchetype);

				// Spawn the new actor instance, in the same level as the original, but deferring running the construction script until we have transferred modified properties
				ULevel*  ActorLevel  = OldActor->GetLevel();
				UClass** MappedClass = OldToNewClassMap.Find(OldActor->GetClass());
				UClass*  SpawnClass  = MappedClass ? *MappedClass : NewClass;

				FActorSpawnParameters SpawnInfo;
				SpawnInfo.OverrideLevel      = ActorLevel;
				SpawnInfo.Template           = NewArchetype;
				SpawnInfo.bNoCollisionFail   = true;
				SpawnInfo.bDeferConstruction = true;

				// Temporarily remove the deprecated flag so we can respawn the Blueprint in the level
				const bool bIsClassDeprecated = SpawnClass->HasAnyClassFlags(CLASS_Deprecated);
				SpawnClass->ClassFlags &= ~CLASS_Deprecated;

				AActor* NewActor = World->SpawnActor(SpawnClass, &Location, &Rotation, SpawnInfo);
				// Reassign the deprecated flag if it was previously assigned
				if (bIsClassDeprecated)
				{
					SpawnClass->ClassFlags |= CLASS_Deprecated;
				}

				check(NewActor != nullptr);
				NewUObject = NewActor;
				// store the new actor for the second pass (NOTE: this detaches 
				// OldActor from all child/parent attachments)
				//
				// running the NewActor's construction-script is saved for that 
				// second pass (because the construction-script may reference 
				// another instance that hasn't been replaced yet).
				ReplacementActors.Add(FActorReplacementHelper(NewActor, OldActor));

				ReinstancedObjectsWeakReferenceMap.Add(OldObject, NewUObject);

				OldActor->DestroyConstructedComponents(); // don't want to serialize components from the old actor
				// Unregister native components so we don't copy any sub-components they generate for themselves (like UCameraComponent does)
				OldActor->UnregisterAllComponents();

				// Unregister any native components, might have cached state based on properties we are going to overwrite
				NewActor->UnregisterAllComponents(); 

				UEditorEngine::CopyPropertiesForUnrelatedObjects(OldActor, NewActor);
				// reset properties/streams
				NewActor->ResetPropertiesForConstruction(); 
				// register native components
				NewActor->RegisterAllComponents(); 

				// 
				// clean up the old actor (unselect it, remove it from the world, etc.)...

				if (OldActor->IsSelected())
				{
					GEditor->SelectActor(OldActor, /*bInSelected =*/false, /*bNotify =*/false);
					bSelectionChanged = true;
				}
				if (GEditor->Layers.IsValid()) // ensure(NULL != GEditor->Layers) ?? While cooking the Layers is NULL.
				{
					GEditor->Layers->DisassociateActorFromLayers(OldActor);
				}

 				World->EditorDestroyActor(OldActor, /*bShouldModifyLevel =*/true);
 				OldToNewInstanceMap.Add(OldActor, NewActor);
			}
			else
			{
				FName OldName(OldObject->GetFName());
				OldObject->Rename(NULL, OldObject->GetOuter(), REN_DoNotDirty | REN_DontCreateRedirectors);
				NewUObject = NewObject<UObject>(OldObject->GetOuter(), NewClass, OldName);
				check(NewUObject != nullptr);

				UEditorEngine::CopyPropertiesForUnrelatedObjects(OldObject, NewUObject);

				if (UAnimInstance* AnimTree = Cast<UAnimInstance>(NewUObject))
				{
					// Initialising the anim instance isn't enough to correctly set up the skeletal mesh again in a
					// paused world, need to initialise the skeletal mesh component that contains the anim instance.
					if (USkeletalMeshComponent* SkelComponent = Cast<USkeletalMeshComponent>(AnimTree->GetOuter()))
					{
						SkelComponent->InitAnim(true);
					}
				}

				OldObject->RemoveFromRoot();
				OldObject->MarkPendingKill();

				OldToNewInstanceMap.Add(OldObject, NewUObject);

				if (bIsComponent)
				{
					UActorComponent* Component = Cast<UActorComponent>(NewUObject);
					AActor* OwningActor = Component->GetOwner();
					if (OwningActor)
					{
						OwningActor->ResetOwnedComponents();

						// Check to see if they have an editor that potentially needs to be refreshed
						if (OwningActor->GetClass()->ClassGeneratedBy)
						{
							PotentialEditorsForRefreshing.AddUnique(OwningActor->GetClass()->ClassGeneratedBy);
						}

						// we need to keep track of actor instances that need 
						// their construction scripts re-ran (since we've just 
						// replaced a component they own)
						OwnersToReconstruct.Add(OwningActor);
					}
				}
			}

			// If this original object came from a blueprint and it was in the selected debug set, change the debugging to the new object.
			if ((CorrespondingBlueprint) && (OldBlueprintDebugObject) && (NewUObject))
			{
				CorrespondingBlueprint->SetObjectBeingDebugged(NewUObject);
			}

			if (bLogConversions)
			{
				UE_LOG(LogBlueprint, Log, TEXT("Converted instance '%s' to '%s'"), *OldObject->GetPathName(), *NewUObject->GetPathName());
			}
		}
	}

	GEditor->OnObjectsReplaced().Remove(OnObjectsReplacedHandle);

	// Now replace any pointers to the old archetypes/instances with pointers to the new one
	TArray<UObject*> SourceObjects;
	TArray<UObject*> DstObjects;

	OldToNewInstanceMap.GenerateKeyArray(SourceObjects);
	OldToNewInstanceMap.GenerateValueArray(DstObjects); // Also look for references in new spawned objects.

	SourceObjects.Append(DstObjects);

	FReplaceReferenceHelper::IncludeCDO(OldClass, NewClass, OldToNewInstanceMap, SourceObjects, OriginalCDO);
	FReplaceReferenceHelper::FindAndReplaceReferences(SourceObjects, ObjectsThatShouldUseOldStuff, ObjectsToReplace, OldToNewInstanceMap, ReinstancedObjectsWeakReferenceMap);

	{ BP_SCOPED_COMPILER_EVENT_STAT(EKismetReinstancerStats_ReplacementConstruction);

		// the process of setting up new replacement actors is split into two 
		// steps (this here, is the second)...
		// 
		// the "finalization" here runs the replacement actor's construction-
		// script and is left until late to account for a scenario where the 
		// construction-script attempts to modify another instance of the 
		// same class... if this were to happen above, in the ObjectsToReplace 
		// loop, then accessing that other instance would cause an assert in 
		// UProperty::ContainerPtrToValuePtrInternal() (which appropriatly 
		// complains that the other instance's type doesn't match because it 
		// hasn't been replaced yet... that's why we wait until after 
		// FArchiveReplaceObjectRef to run construction-scripts).
		for (FActorReplacementHelper& ReplacementActor : ReplacementActors)
		{
			ReplacementActor.Finalize(ObjectRemappingHelper.ReplacedObjects);
		}
	}

	SelectedActors->EndBatchSelectOperation();
	if (bSelectionChanged)
	{
		GEditor->NoteSelectionChange();
	}

	if (GEditor)
	{
		// Refresh any editors for objects that we've updated components for
		for (auto BlueprintAsset : PotentialEditorsForRefreshing)
		{
			FBlueprintEditor* BlueprintEditor = static_cast<FBlueprintEditor*>(FAssetEditorManager::Get().FindEditorForAsset(BlueprintAsset, /*bFocusIfOpen =*/false));
			if (BlueprintEditor)
			{
				BlueprintEditor->RefreshEditors();
			}
		}
	}

	// in the case where we're replacing component instances, we need to make 
	// sure to re-run their owner's construction scripts
	for (AActor* ActorInstance : OwnersToReconstruct)
	{
		ActorInstance->RerunConstructionScripts();
	}
}
Example #3
0
void UK2Node::GetPinHoverText(const UEdGraphPin& Pin, FString& HoverTextOut) const
{
	// start with the default hover text (from the pin's tool-tip)
	Super::GetPinHoverText(Pin, HoverTextOut);

	// if the Pin wasn't initialized with a tool-tip of its own
	if (HoverTextOut.IsEmpty())
	{
		UEdGraphSchema const* Schema = GetSchema();
		check(Schema != NULL);
		Schema->ConstructBasicPinTooltip(Pin, FText::GetEmpty(), HoverTextOut);
	}	

	UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(this);
	check(Blueprint != NULL);

	// If this resides in an intermediate graph, show the UObject name for debug purposes
	if(Blueprint->IntermediateGeneratedGraphs.Contains(GetGraph()))
	{
		HoverTextOut = FString::Printf(TEXT("%s\n\n%s"), *Pin.GetName(), *HoverTextOut);
	}

	UObject* ActiveObject = Blueprint->GetObjectBeingDebugged();
	// if there is no object being debugged, then we don't need to tack on any of the following data
	if (ActiveObject == NULL)
	{
		return;
	}

	// Switch the blueprint to the one that generated the object being debugged (e.g. in case we're inside a Macro BP while debugging)
	Blueprint = Cast<UBlueprint>(ActiveObject->GetClass()->ClassGeneratedBy);
	if (Blueprint == NULL)
	{
		return;
	}

	// if the blueprint doesn't have debug data, notify the user
	/*if (!FKismetDebugUtilities::HasDebuggingData(Blueprint))
	{
		HoverTextOut += TEXT("\n(NO DEBUGGING INFORMATION GENERATED, NEED TO RECOMPILE THE BLUEPRINT)");
	}*/

	//@TODO: For exec pins, show when they were last executed


	// grab the debug value of the pin
	FString WatchText;
	const FKismetDebugUtilities::EWatchTextResult WatchStatus = FKismetDebugUtilities::GetWatchText(/*inout*/ WatchText, Blueprint, ActiveObject, &Pin);

	// if this is an array pin, then we possibly have too many lines (too many entries)
	if (Pin.PinType.bIsArray)
	{
		int32 LineCounter = 0;
		int32 OriginalWatchTextLen = WatchText.Len();

		// walk the string, finding line breaks (counting lines)
		for (int32 NewWatchTextLen = 0; NewWatchTextLen < OriginalWatchTextLen; )
		{
			++LineCounter;

			int32 NewLineIndex = WatchText.Find("\n", ESearchCase::IgnoreCase,  ESearchDir::FromStart, NewWatchTextLen);
			// if we've reached the end of the string (it's not to long)
			if (NewLineIndex == INDEX_NONE)
			{
				break;
			}

			NewWatchTextLen = NewLineIndex + 1;
			// if we're at the end of the string (but it ends with a newline)
			if (NewWatchTextLen >= OriginalWatchTextLen)
			{
				break;
			}

			// if we've hit the max number of lines allowed in a tooltip
			if (LineCounter >= MaxArrayPinTooltipLineCount)
			{
				// truncate WatchText so it contains a finite number of lines
				WatchText  = WatchText.Left(NewWatchTextLen);
				WatchText += "..."; // WatchText should already have a trailing newline (no need to prepend this with one)
				break;
			}
		}
	} // if Pin.PinType.bIsArray...


	switch (WatchStatus)
	{
	case FKismetDebugUtilities::EWTR_Valid:
		HoverTextOut += FString::Printf(TEXT("\nCurrent value = %s"), *WatchText); //@TODO: Print out object being debugged name?
		break;
	case FKismetDebugUtilities::EWTR_NotInScope:
		HoverTextOut += TEXT("\n(Variable is not in scope)");
		break;

	default:
	case FKismetDebugUtilities::EWTR_NoDebugObject:
	case FKismetDebugUtilities::EWTR_NoProperty:
		break;
	}
}
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);
}