EM_Task/CoreUObject/Private/Serialization/ArchiveTraceRoute.cpp

427 lines
16 KiB
C++
Raw Normal View History

2026-02-13 16:18:33 +08:00
// Copyright Epic Games, Inc. All Rights Reserved.
#include "Serialization/ArchiveTraceRoute.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/UObjectIterator.h"
#include "Containers/List.h"
#include "UObject/UnrealType.h"
/*----------------------------------------------------------------------------
FArchiveTraceRoute
----------------------------------------------------------------------------*/
TMap<UObject*, FProperty*> FArchiveTraceRoute::FindShortestRootPath(UObject* Obj, bool bIncludeTransients, EObjectFlags KeepFlags)
{
// Take snapshot of object flags that will be restored once marker goes out of scope.
FScopedObjectFlagMarker ObjectFlagMarker;
TMap<UObject*, FTraceRouteRecord> Routes;
FArchiveTraceRoute Rt(Obj, Routes, bIncludeTransients, KeepFlags);
TMap<UObject*, FProperty*> Result;
// No routes are reported if the object wasn't rooted.
if (Routes.Num() > 0 || Obj->HasAnyFlags(KeepFlags))
{
TArray<FTraceRouteRecord> Records;
Routes.GenerateValueArray(Records);
// the target object is NOT included in the result, so add it first. Then iterate over the route
// backwards, following the trail from the target object to the root object.
Result.Add(Obj, NULL);
for (int32 RecordIndex = Records.Num() - 1; RecordIndex >= 0; RecordIndex--)
{
FTraceRouteRecord& Record = Records[RecordIndex];
// To keep same behavior as previous, it will set last one for now until Ron comes back
for (int32 ReferenceIndex = 0; ReferenceIndex < Record.ReferencerProperties.Num(); ++ReferenceIndex)
{
if (Record.ReferencerProperties[ReferenceIndex])
{
Result.Add(Record.GraphNode->NodeObject, Record.ReferencerProperties[ReferenceIndex]);
break;
}
}
}
}
#if 0
// eventually we'll merge this archive with the FArchiveFindCulprit, since we have all the same information already
// but before we can do so, we'll need to change the TraceRouteRecord's ReferencerProperty to a TArray so that we
// can report when an object has multiple references to another object
FObjectGraphNode* ObjNode = Rt.ObjectGraph.FindRef(Obj);
if ( ObjNode != NULL && ObjNode->ReferencerRecords.Num() > 0 )
{
UE_LOG(LogSerialization, Log, TEXT("Referencers of %s (from FArchiveTraceRoute):"), *Obj->GetFullName() );
int32 RefIndex=0;
for ( TMap<UObject*,FTraceRouteRecord>::TIterator It(ObjNode->ReferencerRecords); It; ++It )
{
UObject* Object = It.Key();
FTraceRouteRecord& Record = It.Value();
UE_LOG(LogSerialization, Log, TEXT(" %i) %s (%s)"), RefIndex++, *Object->GetFullName(), *Record.ReferencerProperty->GetFullName());
}
}
#endif
return Result;
}
/**
* Retuns path to root created by e.g. FindShortestRootPath via a string.
*
* @param TargetObject object marking one end of the route
* @param Route route to print to log.
* @param String of root path
*/
FString FArchiveTraceRoute::PrintRootPath(const TMap<UObject*, FProperty*>& Route, const UObject* TargetObject)
{
FString RouteString;
for (TMap<UObject*, FProperty*>::TConstIterator MapIt(Route); MapIt; ++MapIt)
{
UObject* Object = MapIt.Key();
FProperty* Property = MapIt.Value();
FString ObjectReachability;
if (Object == TargetObject)
{
ObjectReachability = TEXT(" [target]");
}
if (Object->IsRooted())
{
ObjectReachability += TEXT(" (root)");
}
if (Object->IsNative())
{
ObjectReachability += TEXT(" (native)");
}
if (Object->HasAnyFlags(RF_Standalone))
{
ObjectReachability += TEXT(" (standalone)");
}
if (ObjectReachability == TEXT(""))
{
ObjectReachability = TEXT(" ");
}
FString ReferenceSource;
if (Property != NULL)
{
ReferenceSource = FString::Printf(TEXT("%s (%s)"), *ObjectReachability, *Property->GetFullName());
}
else
{
ReferenceSource = ObjectReachability;
}
RouteString += FString::Printf(TEXT(" %s%s%s"), *Object->GetFullName(), *ReferenceSource, LINE_TERMINATOR);
}
if (!Route.Num())
{
RouteString = TEXT(" (Object is not currently rooted)\r\n");
}
return RouteString;
}
FArchiveTraceRoute::FArchiveTraceRoute(UObject* TargetObject, TMap<UObject*, FTraceRouteRecord>& InRoutes, bool bShouldIncludeTransients, EObjectFlags KeepFlags)
: CurrentReferencer(NULL), Depth(0), bIncludeTransients(bShouldIncludeTransients), RequiredFlags(KeepFlags)
{
// this object is part of the root set; don't have to do anything
if (TargetObject == NULL || TargetObject->HasAnyFlags(KeepFlags))
{
return;
}
ArIsObjectReferenceCollector = true;
TSparseArray<UObject*> RootObjects;
// allocate enough memory for all objects
ObjectGraph.Empty(GUObjectArray.GetObjectArrayNum());
RootObjects.Empty(GUObjectArray.GetObjectArrayNum() / 2);
// search for objects that have the right flags and add them to the list of objects that we're going to start with
// all other objects need to be tagged so that we can tell whether they've been serialized or not.
for (FThreadSafeObjectIterator It; It; ++It)
{
UObject* CurrentObject = *It;
if (CurrentObject->HasAnyFlags(RequiredFlags) || CurrentObject->IsRooted())
{
// make sure it isn't tagged
CurrentObject->UnMark(OBJECTMARK_TagExp);
RootObjects.Add(CurrentObject);
ObjectGraph.Add(CurrentObject, new FObjectGraphNode(CurrentObject));
}
else
{
CurrentObject->Mark(OBJECTMARK_TagExp);
}
}
// Populate the ObjectGraph - this serializes our root set to map out the relationships between all rooted objects
GenerateObjectGraph(RootObjects);
// we won't be adding any additional objects for the arrays and graphs, so free up any memory not being used.
RootObjects.Shrink();
ObjectGraph.Shrink();
// we're done with serialization; clear the tags so that we don't interfere with anything else
for (FThreadSafeObjectIterator It; It; ++It)
{
It->UnMark(OBJECTMARK_TagExp);
}
// Now we calculate the shortest path from the target object to a rooted object; if the target object isn't
// in the object graph, it means it isn't rooted.
FObjectGraphNode* ObjectNode = ObjectGraph.FindRef(TargetObject);
if (ObjectNode)
{
ObjectNode->ReferenceDepth = 0;
// This method sets up the ReferenceDepth values for all relevant nodes
CalculateReferenceDepthsForNode(ObjectNode);
int32 LowestDepth = MAX_int32;
FRouteLink ClosestLink;
// Next, we find the root object that has the lowest Depth value
//@fixme - we might have multiple root objects which have the same depth value
// TArray<TDoubleLinkedList<FObjectGraphNode*> > ShortestRoutes;
TDoubleLinkedList<FObjectGraphNode*> ShortestRoute;
for (int32 RootObjectIndex = 0; RootObjectIndex < RootObjects.Num(); RootObjectIndex++)
{
FObjectGraphNode* RootObjectNode = ObjectGraph.FindRef(RootObjects[RootObjectIndex]);
FindClosestLink(RootObjectNode, LowestDepth, ClosestLink);
}
// At this point, we should know which root object has the shortest depth from the target object. Push that
// link into our linked list and recurse into the links to navigate our way through the reference chain to the
// target object.
ShortestRoute.AddHead(ClosestLink.LinkParent);
if (ClosestLink.LinkChild != NULL)
{
ShortestRoute.AddTail(ClosestLink.LinkChild);
while (FindClosestLink(ClosestLink.LinkChild, LowestDepth, ClosestLink) /* || LowestDepth > 0*/)
{
// at this point, LinkChild will be different than it was when we last evaluated the conditional
ShortestRoute.AddTail(ClosestLink.LinkChild);
}
}
// since we know that the target object is rooted, there should be at least one route to a root object;
// therefore LowestDepth should always be zero or there is a bug somewhere...
check(LowestDepth == 0);
// Finally, fill in the output value - it will start with the rooted object and follow the chain of object references
// to the target object. However, the node for the target object itself is NOT included in this result.
for (TDoubleLinkedList<FObjectGraphNode*>::TIterator It(ShortestRoute.GetHead()); It; ++It)
{
FObjectGraphNode* CurrentNode = *It;
InRoutes.Add(CurrentNode->NodeObject, FTraceRouteRecord(CurrentNode, CurrentNode->ReferencerProperties));
}
}
}
/**
* Searches through the objects referenced by CurrentNode for a record with a Depth lower than LowestDepth.
*
* @param CurrentNode the node containing the list of referenced objects that will be searched.
* @param LowestDepth the current number of links we are from the target object.
* @param ClosestLink if a trace route record is found with a lower depth value than LowestDepth, the link is saved to this value.
*
* @return true if a closer link was discovered; false if no links were closer than lowest depth, or if we've reached the target object.
*/
bool FArchiveTraceRoute::FindClosestLink(FObjectGraphNode* CurrentNode, int32& LowestDepth, FRouteLink& ClosestLink)
{
bool bResult = false;
if (CurrentNode != NULL)
{
for (TMap<UObject*, FTraceRouteRecord>::TIterator RefIt(CurrentNode->ReferencedObjects); RefIt; ++RefIt)
{
FTraceRouteRecord& Record = RefIt.Value();
// a ReferenceDepth of MAX_int32 means that this object was not part of the target object's reference graph
if (Record.GraphNode->ReferenceDepth < MAX_int32)
{
if (Record.GraphNode->ReferenceDepth == 0)
{
// found the target
if (CurrentNode->ReferenceDepth < LowestDepth)
{
// the target object is referenced directly by a rooted object
ClosestLink = FRouteLink(CurrentNode, NULL);
}
LowestDepth = CurrentNode->ReferenceDepth - 1;
bResult = false;
break;
}
else if (Record.GraphNode->ReferenceDepth < LowestDepth)
{
LowestDepth = Record.GraphNode->ReferenceDepth;
ClosestLink = FRouteLink(CurrentNode, Record.GraphNode);
bResult = true;
}
else if (Record.GraphNode->ReferenceDepth == LowestDepth)
{
// once we've changed this to an array, push this link on
}
}
}
}
return bResult;
}
/**
* Destructor. Deletes all FObjectGraphNodes created by this archive.
*/
FArchiveTraceRoute::~FArchiveTraceRoute()
{
for (TMap<UObject*, FObjectGraphNode*>::TIterator It(ObjectGraph); It; ++It)
{
delete It.Value();
It.Value() = NULL;
}
}
FArchive& FArchiveTraceRoute::operator<<(class UObject*& Obj)
{
if (Obj != NULL && (bIncludeTransients || !Obj->HasAnyFlags(RF_Transient)))
{
// grab the object graph node for this object and its referencer, creating them if necessary
FObjectGraphNode* CurrentObjectNode = ObjectGraph.FindRef(Obj);
if (CurrentObjectNode == NULL)
{
CurrentObjectNode = ObjectGraph.Add(Obj, new FObjectGraphNode(Obj));
}
FObjectGraphNode* ReferencerNode = ObjectGraph.FindRef(CurrentReferencer);
if (ReferencerNode == NULL)
{
ReferencerNode = ObjectGraph.Add(CurrentReferencer, new FObjectGraphNode(CurrentReferencer));
}
if (Obj != CurrentReferencer)
{
FTraceRouteRecord* Record = ReferencerNode->ReferencedObjects.Find(Obj);
// now record the references between this object and the one referencing it
if (!Record)
{
ReferencerNode->ReferencedObjects.Add(Obj, FTraceRouteRecord(CurrentObjectNode, GetSerializedProperty()));
}
else
{
Record->Add(GetSerializedProperty());
}
Record = CurrentObjectNode->ReferencerRecords.Find(CurrentReferencer);
if (!Record)
{
CurrentObjectNode->ReferencerRecords.Add(CurrentReferencer, FTraceRouteRecord(ReferencerNode, GetSerializedProperty()));
}
else
{
Record->Add(GetSerializedProperty());
}
}
// if this object is still tagged for serialization, add it to the list
if (Obj->HasAnyMarks(OBJECTMARK_TagExp))
{
Obj->UnMark(OBJECTMARK_TagExp);
ObjectsToSerialize.Add(Obj);
}
}
return *this;
}
/**
* Serializes the objects in the specified set; any objects encountered during serialization
* of an object are added to the object set and processed until no new objects are added.
*
* @param Objects the original set of objects to serialize; the original set will be preserved.
*/
void FArchiveTraceRoute::GenerateObjectGraph(TSparseArray<UObject*>& Objects)
{
const int32 LastRootObjectIndex = Objects.Num();
for (int32 ObjIndex = 0; ObjIndex < Objects.Num(); ObjIndex++)
{
CurrentReferencer = Objects[ObjIndex];
CurrentReferencer->UnMark(OBJECTMARK_TagExp);
// Serialize this object
if (CurrentReferencer->HasAnyFlags(RF_ClassDefaultObject))
{
CurrentReferencer->GetClass()->SerializeDefaultObject(CurrentReferencer, *this);
}
else
{
CurrentReferencer->Serialize(*this);
}
// ObjectsToSerialize will contain only those objects which were encountered while serializing CurrentReferencer
// that weren't already in the list of objects to be serialized.
if (ObjectsToSerialize.Num() > 0)
{
Objects += ObjectsToSerialize;
ObjectsToSerialize.Empty();
}
}
Objects.RemoveAt(LastRootObjectIndex, Objects.Num() - LastRootObjectIndex);
}
/**
* Recursively iterates over the referencing objects for the specified node, marking each with
* the current Depth value. Stops once it reaches a route root.
*
* @param ObjectNode the node to evaluate.
*/
void FArchiveTraceRoute::CalculateReferenceDepthsForNode(FObjectGraphNode* ObjectNode)
{
check(ObjectNode);
Depth++;
TSparseArray<FObjectGraphNode*> RecurseRecords;
// for each referencer, set the current depth. Do this before recursing into this object's
// referencers to minimize the number of times we have to revisit nodes
for (TMap<UObject*, FTraceRouteRecord>::TIterator It(ObjectNode->ReferencerRecords); It; ++It)
{
FTraceRouteRecord& Record = It.Value();
checkSlow(Record.GraphNode);
if (Record.GraphNode->ReferenceDepth > Depth)
{
Record.GraphNode->ReferenceDepth = Depth;
Record.GraphNode->ReferencerProperties.Append(Record.ReferencerProperties);
RecurseRecords.Add(Record.GraphNode);
}
}
for (TSparseArray<FObjectGraphNode*>::TIterator It(RecurseRecords); It; ++It)
{
FObjectGraphNode* CurrentNode = *It;
It.RemoveCurrent();
// this record may have been encountered by processing another node; if that resulted in a shorter
// route for this record, just skip it.
if (CurrentNode->ReferenceDepth == Depth)
{
// if the object from this node has one of the required flags, don't process this object's referencers
// as it's considered a "root" for the route
if (!CurrentNode->NodeObject->HasAnyFlags(RequiredFlags) && !CurrentNode->NodeObject->IsRooted())
{
CalculateReferenceDepthsForNode(CurrentNode);
}
}
}
Depth--;
}