EM_Task/CoreUObject/Public/UObject/ReferenceChainSearch.h

246 lines
8.5 KiB
C
Raw Permalink Normal View History

2026-02-13 16:18:33 +08:00
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/Class.h"
#include "UObject/GarbageCollection.h"
#include "HAL/ThreadHeartBeat.h"
/** Search mode flags */
enum class EReferenceChainSearchMode
{
// Returns all reference chains found
Default = 0,
// Returns only reference chains from external objects
ExternalOnly = 1 << 0,
// Returns only the shortest reference chain for each rooted object
Shortest = 1 << 1,
// Returns only the longest reference chain for each rooted object
Longest = 1 << 2,
// Returns only the direct referencers
Direct = 1 << 3,
// Returns complete chains. (Ignoring non GC objects)
FullChain = 1 << 4,
// Print results
PrintResults = 1 << 16,
// Print ALL results (in some cases there may be thousands of reference chains)
PrintAllResults = 1 << 17,
};
ENUM_CLASS_FLAGS(EReferenceChainSearchMode);
class FReferenceChainSearch
{
// Reference chain searching is a very slow operation.
// Suspend the hang and hitch detectors for the lifetime of this instance.
FSlowHeartBeatScope SuspendHeartBeat;
FDisableHitchDetectorScope SuspendGameThreadHitch;
public:
/** Type of reference */
enum class EReferenceType
{
Unknown = 0,
Property = 1,
AddReferencedObjects
};
/** Extended information about a reference */
template <typename T>
struct TReferenceInfo
{
/** Object that is being referenced */
T* Object;
/** Type of reference to the object being referenced */
EReferenceType Type;
/** Name of the object or property that is referencing this object */
FName ReferencerName;
/** Default ctor */
TReferenceInfo()
: Object(nullptr), Type(EReferenceType::Unknown)
{}
/** Ctor */
TReferenceInfo(T* InObject, EReferenceType InType = EReferenceType::Unknown, const FName& InReferencerName = NAME_None)
: Object(InObject), Type(InType), ReferencerName(InReferencerName)
{}
bool operator==(const TReferenceInfo& Other) const
{
return Object == Other.Object;
}
friend uint32 GetTypeHash(const TReferenceInfo& Info)
{
return GetTypeHash(Info.Object);
}
/** Dumps this reference info to string. Does not include the object being referenced */
FString ToString() const
{
if (Type == EReferenceType::Property)
{
return FString::Printf(TEXT("->%s"), *ReferencerName.ToString());
}
else if (Type == EReferenceType::AddReferencedObjects)
{
if (!ReferencerName.IsNone())
{
return FString::Printf(TEXT("::AddReferencedObjects(): %s"), *ReferencerName.ToString());
}
else
{
return TEXT("::AddReferencedObjects()");
}
}
return FString();
}
};
/** Single node in the reference graph */
struct FGraphNode
{
FGraphNode()
: Object(nullptr), Visited(0)
{}
/** Object pointer */
UObject* Object;
/** Objects referenced by this object with reference info */
TSet<TReferenceInfo<FGraphNode>> ReferencedObjects;
/** Objects that have references to this object */
TSet<FGraphNode*> ReferencedByObjects;
/** Non-zero if this node has been already visited during reference search */
int32 Visited;
};
/** Convenience type definitions to avoid template hell */
typedef TReferenceInfo<UObject> FObjectReferenceInfo;
typedef TReferenceInfo<FGraphNode> FNodeReferenceInfo;
/** Reference chain. The first object in the list is the target object and the last object is a root object */
class FReferenceChain
{
friend class FReferenceChainSearch;
/** Nodes in this reference chain. The first node is the target object and the last one is a root object */
TArray<FGraphNode*> Nodes;
/** Reference information for Nodes */
TArray<FNodeReferenceInfo> ReferenceInfos;
/** Fills this chain with extended reference info for each node */
void FillReferenceInfo();
public:
FReferenceChain() {}
FReferenceChain(int32 ReserveDepth)
{
Nodes.Reserve(ReserveDepth);
}
/** Adds a new node to the chain */
void AddNode(FGraphNode* InNode)
{
Nodes.Add(InNode);
}
void InsertNode(FGraphNode* InNode)
{
Nodes.Insert(InNode, 0);
}
/** Gets a node from the chain */
FGraphNode* GetNode(int32 NodeIndex) const
{
return Nodes[NodeIndex];
}
FGraphNode* GetRootNode() const
{
return Nodes.Last();
}
/** Returns the number of nodes in the chain */
int32 Num() const
{
return Nodes.Num();
}
/** Returns a duplicate of this chain */
FReferenceChain* Split()
{
FReferenceChain* NewChain = new FReferenceChain(*this);
return NewChain;
}
/** Checks if this chain contains the specified node */
bool Contains(const FGraphNode* InNode) const
{
return Nodes.Contains(InNode);
}
/** Gets extended reference info for the specified node index */
const FNodeReferenceInfo& GetReferenceInfo(int32 NodeIndex) const
{
return ReferenceInfos[NodeIndex];
}
/** Check if this reference chain represents an external reference (the root is not in target object) */
bool IsExternal() const;
};
/** Constructs a new search engine and finds references to the specified object */
COREUOBJECT_API FReferenceChainSearch(UObject* InObjectToFindReferencesTo, EReferenceChainSearchMode Mode = EReferenceChainSearchMode::PrintResults);
/** Destructor */
COREUOBJECT_API ~FReferenceChainSearch();
/**
* Dumps results to log
* @param bDumpAllChains - if set to false, the output will be trimmed to the first 100 reference chains
*/
COREUOBJECT_API void PrintResults(bool bDumpAllChains = false) const;
/** Returns a string with a short report explaining the root path, will contain newlines */
COREUOBJECT_API FString GetRootPath() const;
/** Returns all reference chains */
COREUOBJECT_API const TArray<FReferenceChain*>& GetReferenceChains() const
{
return ReferenceChains;
}
private:
/** The object we're going to look for references to */
UObject* ObjectToFindReferencesTo;
/** All reference chain found during the search */
TArray<FReferenceChain*> ReferenceChains;
/** All nodes created during the search */
TMap<UObject*, FGraphNode*> AllNodes;
/** Performs the search */
void PerformSearch(EReferenceChainSearchMode SearchMode);
/** Performs the search */
void FindDirectReferencesForObjects();
/** Frees memory */
void Cleanup();
/** Tries to find a node for an object and if it doesn't exists creates a new one and returns it */
static FGraphNode* FindOrAddNode(TMap<UObject*, FGraphNode*>& AllNodes, UObject* InObjectToFindNodeFor);
/** Builds reference chains */
static int32 BuildReferenceChains(FGraphNode* TargetNode, TArray<FReferenceChain*>& ProducedChains, int32 ChainDepth, const int32 VisitCounter, EReferenceChainSearchMode SearchMode);
/** Builds reference chains */
static void BuildReferenceChains(FGraphNode* TargetNode, TArray<FReferenceChain*>& AllChains, EReferenceChainSearchMode SearchMode);
/** Builds reference chains for direct references only */
static void BuildReferenceChainsForDirectReferences(FGraphNode* TargetNode, TArray<FReferenceChain*>& AllChains, EReferenceChainSearchMode SearchMode);
/** Leaves only chains with unique root objects */
static void RemoveChainsWithDuplicatedRoots(TArray<FReferenceChain*>& AllChains);
/** Leaves only unique chains */
static void RemoveDuplicatedChains(TArray<FReferenceChain*>& AllChains);
/** Returns a string with all flags (we care about) set on an object */
static FString GetObjectFlags(UObject* InObject);
/** Dumps a reference chain to log */
static void DumpChain(FReferenceChain* Chain);
/** Writes a reference chain to a string */
static void WriteChain(FReferenceChain* Chain, FString& OutString);
};