EM_Task/CoreUObject/Private/UObject/UObjectClusters.cpp
Boshuang Zhao 5144a49c9b add
2026-02-13 16:18:33 +08:00

858 lines
34 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
UObjectClusters.cpp: Unreal UObject Cluster helper functions
=============================================================================*/
#include "UObject/UObjectClusters.h"
#include "HAL/ThreadSafeCounter.h"
#include "Containers/LockFreeList.h"
#include "Stats/Stats.h"
#include "HAL/IConsoleManager.h"
#include "UObject/ObjectMacros.h"
#include "UObject/UObjectGlobals.h"
#include "UObject/UObjectArray.h"
#include "UObject/UObjectBaseUtility.h"
#include "UObject/GarbageCollection.h"
#include "UObject/UObjectIterator.h"
#include "UObject/ReferenceChainSearch.h"
#include "UObject/LinkerLoad.h"
#include "UObject/FastReferenceCollector.h"
#include "UObject/Package.h"
#include "Misc/CommandLine.h"
int32 GCreateGCClusters = 1;
static FAutoConsoleVariableRef CCreateGCClusters(
TEXT("gc.CreateGCClusters"),
GCreateGCClusters,
TEXT("If true, the engine will attempt to create clusters of objects for better garbage collection performance."),
ECVF_Default);
int32 GAssetClustreringEnabled = 1;
static FAutoConsoleVariableRef CVarAssetClustreringEnabled(
TEXT("gc.AssetClustreringEnabled"),
GAssetClustreringEnabled,
TEXT("If true, the engine will attempt to create clusters from asset files."),
ECVF_Default);
int32 GMinGCClusterSize = 5;
static FAutoConsoleVariableRef CMinGCClusterSize(
TEXT("gc.MinGCClusterSize"),
GMinGCClusterSize,
TEXT("Minimum GC cluster size"),
ECVF_Default);
FUObjectClusterContainer::FUObjectClusterContainer()
: NumAllocatedClusters(0), bClustersNeedDissolving(false)
{
}
int32 FUObjectClusterContainer::AllocateCluster(int32 InRootObjectIndex)
{
int32 ClusterIndex = INDEX_NONE;
if (FreeClusterIndices.Num())
{
ClusterIndex = FreeClusterIndices.Pop(false);
}
else
{
ClusterIndex = Clusters.Add(FUObjectCluster());
}
FUObjectCluster& NewCluster = Clusters[ClusterIndex];
check(NewCluster.RootIndex == INDEX_NONE);
NewCluster.RootIndex = InRootObjectIndex;
NumAllocatedClusters++;
return ClusterIndex;
}
void FUObjectClusterContainer::FreeCluster(int32 InClusterIndex)
{
FUObjectCluster& Cluster = Clusters[InClusterIndex];
check(Cluster.RootIndex != INDEX_NONE);
FUObjectItem* RootItem = GUObjectArray.IndexToObject(Cluster.RootIndex);
check(RootItem->GetClusterIndex() == InClusterIndex);
RootItem->SetOwnerIndex(0);
RootItem->ClearFlags(EInternalObjectFlags::ClusterRoot);
for (int32 ReferencedClusterRootIndex: Cluster.ReferencedClusters)
{
if (ReferencedClusterRootIndex >= 0)
{
FUObjectItem* ReferencedClusterRootItem = GUObjectArray.IndexToObjectUnsafeForGC(ReferencedClusterRootIndex);
if (ReferencedClusterRootItem->GetOwnerIndex() < 0)
{
FUObjectCluster& ReferencedCluster = Clusters[ReferencedClusterRootItem->GetClusterIndex()];
ReferencedCluster.ReferencedByClusters.Remove(Cluster.RootIndex);
}
}
}
Cluster.RootIndex = INDEX_NONE;
Cluster.Objects.Reset();
Cluster.MutableObjects.Reset();
Cluster.ReferencedClusters.Reset();
Cluster.ReferencedByClusters.Reset();
Cluster.bNeedsDissolving = false;
FreeClusterIndices.Add(InClusterIndex);
NumAllocatedClusters--;
check(NumAllocatedClusters >= 0);
}
FUObjectCluster* FUObjectClusterContainer::GetObjectCluster(UObjectBaseUtility* ClusterRootOrObjectFromCluster)
{
check(ClusterRootOrObjectFromCluster);
const int32 OuterIndex = GUObjectArray.ObjectToIndex(ClusterRootOrObjectFromCluster);
FUObjectItem* OuterItem = GUObjectArray.IndexToObjectUnsafeForGC(OuterIndex);
int32 ClusterRootIndex = 0;
if (OuterItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot))
{
ClusterRootIndex = OuterIndex;
}
else
{
ClusterRootIndex = OuterItem->GetOwnerIndex();
}
FUObjectCluster* Cluster = nullptr;
if (ClusterRootIndex != 0)
{
const int32 ClusterIndex = ClusterRootIndex > 0 ? GUObjectArray.IndexToObject(ClusterRootIndex)->GetClusterIndex() : OuterItem->GetClusterIndex();
Cluster = &GUObjectClusters[ClusterIndex];
}
return Cluster;
}
void FUObjectClusterContainer::DissolveCluster(UObjectBaseUtility* ClusterRootOrObjectFromCluster)
{
FUObjectCluster* Cluster = GetObjectCluster(ClusterRootOrObjectFromCluster);
if (Cluster)
{
DissolveCluster(*Cluster);
}
}
void FUObjectClusterContainer::DissolveCluster(FUObjectCluster& Cluster)
{
FUObjectItem* RootObjectItem = GUObjectArray.IndexToObjectUnsafeForGC(Cluster.RootIndex);
// Unreachable or not, we won't need this array later
TArray<int32> ReferencedByClusters = MoveTemp(Cluster.ReferencedByClusters);
// Unreachable clusters will be removed by GC during BeginDestroy phase (unhashing)
if (!RootObjectItem->IsUnreachable())
{
#if UE_GCCLUSTER_VERBOSE_LOGGING
UObject* ClusterRootObject = static_cast<UObject*>(RootObjectItem->Object);
UE_LOG(LogObj, Log, TEXT("Dissolving cluster (%d) %s"), RootObjectItem->GetClusterIndex(), *ClusterRootObject->GetFullName());
#endif // UE_GCCLUSTER_VERBOSE_LOGGING
const int32 OldClusterIndex = RootObjectItem->GetClusterIndex();
for (int32 ClusterObjectIndex: Cluster.Objects)
{
FUObjectItem* ClusterObjectItem = GUObjectArray.IndexToObjectUnsafeForGC(ClusterObjectIndex);
ClusterObjectItem->SetOwnerIndex(0);
}
FreeCluster(OldClusterIndex);
}
// Recursively dissolve all clusters this cluster is directly referenced by
for (int32 ReferencedByClusterRootIndex: ReferencedByClusters)
{
FUObjectItem* ReferencedByClusterRootObjectItem = GUObjectArray.IndexToObjectUnsafeForGC(ReferencedByClusterRootIndex);
if (ReferencedByClusterRootObjectItem->GetOwnerIndex())
{
DissolveCluster(Clusters[ReferencedByClusterRootObjectItem->GetClusterIndex()]);
}
}
}
void FUObjectClusterContainer::DissolveClusterAndMarkObjectsAsUnreachable(FUObjectItem* RootObjectItem)
{
const int32 OldClusterIndex = RootObjectItem->GetClusterIndex();
FUObjectCluster& Cluster = Clusters[OldClusterIndex];
// Unreachable or not, we won't need this array later
TArray<int32> ReferencedByClusters = MoveTemp(Cluster.ReferencedByClusters);
#if UE_GCCLUSTER_VERBOSE_LOGGING
UObject* ClusterRootObject = static_cast<UObject*>(RootObjectItem->Object);
UE_LOG(LogObj, Log, TEXT("Dissolving cluster (%d) %s"), OldClusterIndex, *ClusterRootObject->GetFullName());
#endif
for (int32 ClusterObjectIndex: Cluster.Objects)
{
FUObjectItem* ClusterObjectItem = GUObjectArray.IndexToObjectUnsafeForGC(ClusterObjectIndex);
ClusterObjectItem->SetOwnerIndex(0);
ClusterObjectItem->SetFlags(EInternalObjectFlags::Unreachable);
}
#if !UE_GCCLUSTER_VERBOSE_LOGGING
UObject* ClusterRootObject = static_cast<UObject*>(RootObjectItem->Object);
#endif
ClusterRootObject->OnClusterMarkedAsPendingKill();
FreeCluster(OldClusterIndex);
// Recursively dissolve all clusters this cluster is directly referenced by
for (int32 ReferencedByClusterRootIndex: ReferencedByClusters)
{
FUObjectItem* ReferencedByClusterRootItem = GUObjectArray.IndexToObjectUnsafeForGC(ReferencedByClusterRootIndex);
if (ReferencedByClusterRootItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot))
{
ReferencedByClusterRootItem->SetFlags(EInternalObjectFlags::Unreachable);
DissolveClusterAndMarkObjectsAsUnreachable(ReferencedByClusterRootItem);
}
}
}
void FUObjectClusterContainer::DissolveClusters(bool bForceDissolveAllClusters /* = false */)
{
for (FUObjectCluster& Cluster: Clusters)
{
if (Cluster.RootIndex >= 0 && (Cluster.bNeedsDissolving || bForceDissolveAllClusters))
{
DissolveCluster(Cluster);
}
}
bClustersNeedDissolving = false;
}
int32 FUObjectClusterContainer::GetMinClusterSize() const
{
return FMath::Max(1, GMinGCClusterSize);
}
#if !UE_BUILD_SHIPPING
static bool DoesClusterContainObjects(const FUObjectCluster& Cluster, const TArray<int32>& Objects)
{
for (int32 ObjectIndex: Objects)
{
if (Cluster.RootIndex == ObjectIndex)
{
return true;
}
if (Cluster.Objects.Contains(ObjectIndex))
{
return true;
}
else if (Cluster.MutableObjects.Contains(ObjectIndex))
{
return true;
}
}
return false;
}
static void ParseObjectNameArrayForClusters(TArray<int32>& OutIndexArray, const TArray<FString>& InNameArray, bool bWarn = true)
{
for (const FString& ObjectName: InNameArray)
{
UObject* Res = StaticFindObject(UObject::StaticClass(), ANY_PACKAGE, *ObjectName);
if (Res)
{
int32 ObjectIndex = GUObjectArray.ObjectToIndex(Res);
OutIndexArray.Add(ObjectIndex);
}
else
{
UE_CLOG(bWarn, LogObj, Warning, TEXT("ParseObjectNameArrayForClusters can't find object \"%s\""), *ObjectName);
}
}
}
void DumpClusterToLog(const FUObjectCluster& Cluster, bool bHierarchy, bool bIndexOnly)
{
#if UE_GCCLUSTER_VERBOSE_LOGGING
static struct FVerboseClusterLoggingSettings
{
TArray<FString> WithObjects;
FVerboseClusterLoggingSettings()
{
FString ObjectsList;
FParse::Value(FCommandLine::Get(), TEXT("DumpClustersWithObjects="), ObjectsList);
ObjectsList.ParseIntoArray(WithObjects, TEXT(","));
}
bool DoesClusterContainRequestedObjects(const FUObjectCluster& InCluster)
{
bool bContainsObjects = true;
if (WithObjects.Num())
{
// We need to process the object name list each time we check it against a cluster because
// the objects may get loaded in and out as we create new clusters
TArray<int32> ObjectIndices;
ParseObjectNameArrayForClusters(ObjectIndices, WithObjects, false);
// If none of the objects is currently loaded and ObjectIndices is empty we will properly reject the cluster
bContainsObjects = DoesClusterContainObjects(InCluster, ObjectIndices);
}
return bContainsObjects;
}
} VerboseClusterLoggingSettings;
if (!VerboseClusterLoggingSettings.DoesClusterContainRequestedObjects(Cluster))
{
return;
}
#endif
FUObjectItem* RootItem = GUObjectArray.IndexToObjectUnsafeForGC(Cluster.RootIndex);
UObject* RootObject = static_cast<UObject*>(RootItem->Object);
UE_LOG(LogObj, Display, TEXT("%s (Index: %d), Size: %d, ReferencedClusters: %d"), *RootObject->GetFullName(), Cluster.RootIndex, Cluster.Objects.Num(), Cluster.ReferencedClusters.Num());
if (bHierarchy)
{
int32 Index = 0;
for (int32 ObjectIndex: Cluster.Objects)
{
if (!bIndexOnly)
{
FUObjectItem* ObjectItem = GUObjectArray.IndexToObjectUnsafeForGC(ObjectIndex);
UObject* Object = static_cast<UObject*>(ObjectItem->Object);
UE_LOG(LogObj, Display, TEXT(" [%.4d]: %s (Index: %d)"), Index++, *Object->GetFullName(), ObjectIndex);
}
else
{
UE_LOG(LogObj, Display, TEXT(" [%.4d]: %d"), Index++, ObjectIndex);
}
}
UE_LOG(LogObj, Display, TEXT(" Referenced clusters: %d"), Cluster.ReferencedClusters.Num());
for (int32 ClusterRootIndex: Cluster.ReferencedClusters)
{
if (ClusterRootIndex >= 0)
{
if (!bIndexOnly)
{
FUObjectItem* ClusterRootItem = GUObjectArray.IndexToObjectUnsafeForGC(ClusterRootIndex);
UObject* ClusterRootObject = static_cast<UObject*>(ClusterRootItem->Object);
UE_LOG(LogObj, Display, TEXT(" -> %s (Index: %d)"), *ClusterRootObject->GetFullName(), ClusterRootIndex);
}
else
{
UE_LOG(LogObj, Display, TEXT(" -> %d"), ClusterRootIndex);
}
}
else
{
UE_LOG(LogObj, Display, TEXT(" -> nullptr"));
}
}
UE_LOG(LogObj, Display, TEXT(" External (mutable) objects: %d"), Cluster.MutableObjects.Num());
for (int32 ObjectIndex: Cluster.MutableObjects)
{
if (ObjectIndex >= 0)
{
if (!bIndexOnly)
{
FUObjectItem* ObjectItem = GUObjectArray.IndexToObjectUnsafeForGC(ObjectIndex);
UObject* Object = static_cast<UObject*>(ObjectItem->Object);
UE_LOG(LogObj, Display, TEXT(" => %s (Index: %d)"), *Object->GetFullName(), ObjectIndex);
}
else
{
UE_LOG(LogObj, Display, TEXT(" => %d"), ObjectIndex);
}
}
else
{
UE_LOG(LogObj, Display, TEXT(" => nullptr"));
}
}
}
}
// Dumps all clusters to log.
void ListClusters(const TArray<FString>& Args)
{
const bool bHierarchy = Args.Contains(TEXT("Hierarchy"));
int32 MaxInterClusterReferences = 0;
int32 TotalInterClusterReferences = 0;
int32 MaxClusterSize = 0;
int32 TotalClusterObjects = 0;
TArray<FUObjectCluster*> AllClusters;
for (FUObjectCluster& Cluster: GUObjectClusters.GetClustersUnsafe())
{
if (Cluster.RootIndex != INDEX_NONE)
{
AllClusters.Add(&Cluster);
}
}
TArray<int32> WithObjects;
for (const FString& Arg: Args)
{
if (Arg == TEXT("SortByName"))
{
Algo::SortBy(AllClusters, [](FUObjectCluster* A)
{
return GUObjectArray.IndexToObject(A->RootIndex)->Object->GetFName();
},
FNameLexicalLess());
}
else if (Arg == TEXT("SortByObjectCount"))
{
Algo::SortBy(AllClusters, [](FUObjectCluster* A)
{
return A->Objects.Num();
});
}
else if (Arg == TEXT("SortByMutableObjectCount"))
{
Algo::SortBy(AllClusters, [](FUObjectCluster* A)
{
return A->MutableObjects.Num();
});
}
else if (Arg == TEXT("SortByReferencedClustersCount"))
{
Algo::SortBy(AllClusters, [](FUObjectCluster* A)
{
return A->ReferencedClusters.Num();
});
}
else if (Arg.StartsWith("With="))
{
FString ObjectsList = Arg.Mid(5);
TArray<FString> ObjectNames;
ObjectsList.ParseIntoArray(ObjectNames, TEXT(","));
ParseObjectNameArrayForClusters(WithObjects, ObjectNames);
}
}
int32 NumberOfClustersPrinted = 0;
for (FUObjectCluster* Cluster: AllClusters)
{
check(Cluster->RootIndex != INDEX_NONE);
MaxInterClusterReferences = FMath::Max(MaxInterClusterReferences, Cluster->ReferencedClusters.Num());
TotalInterClusterReferences += Cluster->ReferencedClusters.Num();
MaxClusterSize = FMath::Max(MaxClusterSize, Cluster->Objects.Num());
TotalClusterObjects += Cluster->Objects.Num();
bool bListCluster = true;
if (WithObjects.Num())
{
bListCluster = DoesClusterContainObjects(*Cluster, WithObjects);
}
if (bListCluster)
{
DumpClusterToLog(*Cluster, bHierarchy, false);
NumberOfClustersPrinted++;
}
}
UE_LOG(LogObj, Display, TEXT("Displayed %d clusters"), NumberOfClustersPrinted);
UE_LOG(LogObj, Display, TEXT("Total number of clusters: %d"), AllClusters.Num());
UE_LOG(LogObj, Display, TEXT("Maximum cluster size: %d"), MaxClusterSize);
UE_LOG(LogObj, Display, TEXT("Average cluster size: %d"), AllClusters.Num() ? (TotalClusterObjects / AllClusters.Num()) : 0);
UE_LOG(LogObj, Display, TEXT("Number of objects in GC clusters: %d"), TotalClusterObjects);
UE_LOG(LogObj, Display, TEXT("Maximum number of custer-to-cluster references: %d"), MaxInterClusterReferences);
UE_LOG(LogObj, Display, TEXT("Average number of custer-to-cluster references: %d"), AllClusters.Num() ? (TotalInterClusterReferences / AllClusters.Num()) : 0);
}
void FindStaleClusters(const TArray<FString>& Args)
{
// This is seriously slow.
UE_LOG(LogObj, Display, TEXT("Searching for stale clusters. This may take a while..."));
int32 NumStaleClusters = 0;
int32 TotalNumClusters = 0;
for (FRawObjectIterator It(true); It; ++It)
{
FUObjectItem* ObjectItem = *It;
if (ObjectItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot))
{
TotalNumClusters++;
UObject* ClusterRootObject = static_cast<UObject*>(ObjectItem->Object);
FReferenceChainSearch SearchRefs(ClusterRootObject, EReferenceChainSearchMode::ExternalOnly);
bool bReferenced = false;
if (SearchRefs.GetReferenceChains().Num() > 0)
{
for (const FReferenceChainSearch::FReferenceChain* ReferenceChain: SearchRefs.GetReferenceChains())
{
UObject* ReferencingObj = ReferenceChain->GetRootNode()->Object;
bReferenced = true;
break;
}
}
if (!bReferenced)
{
NumStaleClusters++;
UE_LOG(LogObj, Display, TEXT("Cluster %s has no external references:"), *ClusterRootObject->GetFullName());
SearchRefs.PrintResults();
}
}
}
UE_LOG(LogObj, Display, TEXT("Found %d clusters, including %d stale."), TotalNumClusters, NumStaleClusters);
}
void DumpRefsToCluster(FUObjectCluster* Cluster)
{
FUObjectItem* RootItem = GUObjectArray.IndexToObject(Cluster->RootIndex);
UE_LOG(LogObj, Display, TEXT("Dumping references to objects in cluster %s"), *static_cast<UObject*>(RootItem->Object)->GetFullName());
bool bIsReferenced = false;
for (int32 ObjectIndex: Cluster->Objects)
{
FUObjectItem* ObjectItem = GUObjectArray.IndexToObject(ObjectIndex);
FReferenceChainSearch SearchRefs(static_cast<UObject*>(ObjectItem->Object), EReferenceChainSearchMode::ExternalOnly | EReferenceChainSearchMode::Shortest);
if (SearchRefs.GetReferenceChains().Num())
{
bIsReferenced = true;
SearchRefs.PrintResults(true);
}
}
if (!bIsReferenced)
{
UE_LOG(LogObj, Display, TEXT("Cluster %s is not currently referenced by anything."), *static_cast<UObject*>(RootItem->Object)->GetFullName());
}
}
void DumpRefsToCluster(const TArray<FString>& Args)
{
// This is seriously slow.
UE_LOG(LogObj, Display, TEXT("Searching for references to clusteres. This may take a while..."));
TArray<int32> RootObjects;
for (const FString& Arg: Args)
{
if (Arg.StartsWith("Root="))
{
FString ObjectsList = Arg.Mid(5);
TArray<FString> ObjectNames;
ObjectsList.ParseIntoArray(ObjectNames, TEXT(","));
ParseObjectNameArrayForClusters(RootObjects, ObjectNames);
}
}
for (int32 RootIndex: RootObjects)
{
FUObjectItem* RootItem = GUObjectArray.IndexToObject(RootIndex);
if (RootItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot))
{
FUObjectCluster* Cluster = GUObjectClusters.GetObjectCluster(static_cast<UObject*>(RootItem->Object));
DumpRefsToCluster(Cluster);
}
}
}
static FAutoConsoleCommand ListClustersCommand(
TEXT("gc.ListClusters"),
TEXT("Dumps all clusters do output log. When 'Hiearchy' argument is specified lists all objects inside clusters."),
FConsoleCommandWithArgsDelegate::CreateStatic(ListClusters));
static FAutoConsoleCommand FindStaleClustersCommand(
TEXT("gc.FindStaleClusters"),
TEXT("Dumps all clusters do output log that are not referenced by anything."),
FConsoleCommandWithArgsDelegate::CreateStatic(FindStaleClusters));
static FAutoConsoleCommand DumpRefsToClusterCommand(
TEXT("gc.DumpRefsToCluster"),
TEXT("Dumps references to all objects within a cluster. Specify the cluster name with Root=Name."),
FConsoleCommandWithArgsDelegate::CreateStatic(DumpRefsToCluster));
#endif // !UE_BUILD_SHIPPING
/**
* Handles UObject references found by TFastReferenceCollector
*/
class FClusterReferenceProcessor: public FSimpleReferenceProcessorBase
{
int32 ClusterRootIndex;
FUObjectCluster& Cluster;
public:
FClusterReferenceProcessor(int32 InClusterRootIndex, FUObjectCluster& InCluster)
: ClusterRootIndex(InClusterRootIndex), Cluster(InCluster)
{}
static FString LoadFlagsToString(UObject* Obj)
{
FString Flags;
if (Obj)
{
if (Obj->HasAnyFlags(RF_NeedLoad))
{
Flags += TEXT("RF_NeedLoad");
}
if (Obj->HasAnyFlags(RF_NeedPostLoad))
{
if (Flags.Len())
{
Flags += TEXT("|");
}
Flags += TEXT("RF_NeedPostLoad");
}
}
else
{
Flags += TEXT("null");
}
return Flags;
}
UObject* GetClusterRoot()
{
return static_cast<UObject*>(GUObjectArray.IndexToObject(Cluster.RootIndex)->Object);
}
/**
* Adds an object to cluster (if possible)
*
* @param ObjectIndex UObject index in GUObjectArray
* @param ObjectItem UObject's entry in GUObjectArray
* @param Obj The object to add to cluster
* @param ObjectsToSerialize An array of remaining objects to serialize (Obj must be added to it if Obj can be added to cluster)
* @param bOuterAndClass If true, the Obj's Outer and Class will also be added to the cluster
*/
void AddObjectToCluster(int32 ObjectIndex, FUObjectItem* ObjectItem, UObject* Obj, TArray<UObject*>& ObjectsToSerialize, bool bOuterAndClass)
{
// If we haven't finished loading, we can't be sure we know all the references
checkf(!Obj->HasAnyFlags(RF_NeedLoad), TEXT("%s hasn't been loaded (%s) but is being added to cluster %s"),
*Obj->GetFullName(),
*LoadFlagsToString(Obj),
*GetClusterRoot()->GetFullName());
check(ObjectItem->GetOwnerIndex() == 0 || ObjectItem->GetOwnerIndex() == ClusterRootIndex || ObjectIndex == ClusterRootIndex || GUObjectArray.IsDisregardForGC(Obj));
check(Obj->CanBeInCluster());
if (ObjectIndex != ClusterRootIndex && ObjectItem->GetOwnerIndex() == 0 && !GUObjectArray.IsDisregardForGC(Obj) && !Obj->IsRooted())
{
ObjectsToSerialize.Add(Obj);
check(!ObjectItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot));
ObjectItem->SetOwnerIndex(ClusterRootIndex);
Cluster.Objects.Add(ObjectIndex);
if (bOuterAndClass)
{
UObject* ObjOuter = Obj->GetOuter();
if (ObjOuter)
{
HandleTokenStreamObjectReference(ObjectsToSerialize, Obj, ObjOuter, INDEX_NONE, true);
}
if (!Obj->GetClass()->HasAllClassFlags(CLASS_Native))
{
UObject* ObjectClass = Obj->GetClass();
HandleTokenStreamObjectReference(ObjectsToSerialize, Obj, ObjectClass, INDEX_NONE, true);
UObject* ObjectClassOuter = Obj->GetClass()->GetOuter();
HandleTokenStreamObjectReference(ObjectsToSerialize, Obj, ObjectClassOuter, INDEX_NONE, true);
}
}
}
}
/**
* Handles UObject reference from the token stream. Performance is critical here so we're FORCEINLINING this function.
*
* @param ObjectsToSerialize An array of remaining objects to serialize (Obj must be added to it if Obj can be added to cluster)
* @param ReferencingObject Object referencing the object to process.
* @param TokenIndex Index to the token stream where the reference was found.
* @param bAllowReferenceElimination True if reference elimination is allowed (ignored when constructing clusters).
*/
FORCEINLINE void HandleTokenStreamObjectReference(TArray<UObject*>& ObjectsToSerialize, UObject* ReferencingObject, UObject*& Object, const int32 TokenIndex, bool bAllowReferenceElimination)
{
if (Object)
{
// If we haven't finished loading, we can't be sure we know all the references so the object will be added as mutable reference
UE_CLOG(Object->HasAnyFlags(RF_NeedLoad), LogObj, Log, TEXT("%s hasn't been loaded (%s) but is being added to cluster %s"),
*Object->GetFullName(),
*LoadFlagsToString(Object),
*GetClusterRoot()->GetFullName());
FUObjectItem* ObjectItem = GUObjectArray.ObjectToObjectItem(Object);
// Add encountered object reference to list of to be serialized objects if it hasn't already been added.
if (ObjectItem->GetOwnerIndex() != ClusterRootIndex)
{
if (ObjectItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot) || ObjectItem->GetOwnerIndex() != 0)
{
// Simply reference this cluster and all clusters it's referencing
const int32 OtherClusterRootIndex = ObjectItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot) ? GUObjectArray.ObjectToIndex(Object) : ObjectItem->GetOwnerIndex();
FUObjectItem* OtherClusterRootItem = GUObjectArray.IndexToObject(OtherClusterRootIndex);
const int32 OtherClusterIndex = OtherClusterRootItem->GetClusterIndex();
FUObjectCluster& OtherCluster = GUObjectClusters[OtherClusterIndex];
Cluster.ReferencedClusters.AddUnique(OtherClusterRootIndex);
OtherCluster.ReferencedByClusters.AddUnique(ClusterRootIndex);
for (int32 OtherClusterReferencedCluster: OtherCluster.ReferencedClusters)
{
if (OtherClusterReferencedCluster != ClusterRootIndex)
{
Cluster.ReferencedClusters.AddUnique(OtherClusterReferencedCluster);
}
}
for (int32 OtherClusterReferencedMutableObjectIndex: OtherCluster.MutableObjects)
{
Cluster.MutableObjects.AddUnique(OtherClusterReferencedMutableObjectIndex);
}
}
else if (!GUObjectArray.IsDisregardForGC(Object)) // We know that disregard for GC objects will never be GC'd so no reference is necessary
{
check(ObjectItem->GetOwnerIndex() == 0);
// New object, add it to the cluster.
if (Object->CanBeInCluster() && !Object->HasAnyFlags(RF_NeedLoad | RF_NeedPostLoad) && !Object->IsRooted())
{
AddObjectToCluster(GUObjectArray.ObjectToIndex(Object), ObjectItem, Object, ObjectsToSerialize, true);
}
else
{
// If the object can't be in a cluster or is being loaded, adding to the mutable objects list (and we won't be processing it further)
Cluster.MutableObjects.AddUnique(GUObjectArray.ObjectToIndex(Object));
}
}
}
}
}
};
bool CanCreateObjectClusters()
{
return FPlatformProperties::RequiresCookedData() && !GIsInitialLoad && GCreateGCClusters && GAssetClustreringEnabled && !GUObjectArray.IsOpenForDisregardForGC();
}
/** Looks through objects loaded with a package and creates clusters from them */
void CreateClustersFromPackage(FLinkerLoad* PackageLinker, TArray<UObject*>& OutClusterObjects)
{
if (CanCreateObjectClusters())
{
check(PackageLinker);
for (FObjectExport& Export: PackageLinker->ExportMap)
{
if (Export.Object && Export.Object->CanBeClusterRoot())
{
OutClusterObjects.Add(Export.Object);
}
}
}
}
void UObjectBaseUtility::AddToCluster(UObjectBaseUtility* ClusterRootOrObjectFromCluster, bool bAddAsMutableObject /* = false */)
{
FUObjectCluster* Cluster = GUObjectClusters.GetObjectCluster(ClusterRootOrObjectFromCluster);
if (Cluster)
{
const int32 ClusterRootIndex = Cluster->RootIndex;
if (!bAddAsMutableObject)
{
FClusterReferenceProcessor Processor(ClusterRootIndex, *Cluster);
TFastReferenceCollector<
FClusterReferenceProcessor,
TDefaultReferenceCollector<FClusterReferenceProcessor>,
FGCArrayPool,
EFastReferenceCollectorOptions::AutogenerateTokenStream | EFastReferenceCollectorOptions::ProcessNoOpTokens>
ReferenceCollector(Processor, FGCArrayPool::Get());
FGCArrayStruct ArrayStruct;
TArray<UObject*>& ObjectsToProcess = ArrayStruct.ObjectsToSerialize;
UObject* ThisObject = static_cast<UObject*>(this);
Processor.HandleTokenStreamObjectReference(ObjectsToProcess, static_cast<UObject*>(ClusterRootOrObjectFromCluster), ThisObject, INDEX_NONE, true);
if (ObjectsToProcess.Num())
{
ReferenceCollector.CollectReferences(ArrayStruct);
}
#if UE_GCCLUSTER_VERBOSE_LOGGING
UObject* ClusterRootObject = static_cast<UObject*>(GUObjectArray.IndexToObjectUnsafeForGC(Cluster->RootIndex)->Object);
UE_LOG(LogObj, Log, TEXT("Added %s to cluster %s:"), *ThisObject->GetFullName(), *ClusterRootObject->GetFullName());
DumpClusterToLog(*Cluster, true, false);
#endif
}
else
{
// Adds this object's index to the MutableObjects array keeping it sorted and unique
const int32 ThisObjectIndex = GUObjectArray.ObjectToIndex(this);
int32 InsertedAt = INDEX_NONE;
for (int32 MutableObjectIndex = 0; MutableObjectIndex < Cluster->MutableObjects.Num() && InsertedAt == INDEX_NONE; ++MutableObjectIndex)
{
if (Cluster->MutableObjects[MutableObjectIndex] > ThisObjectIndex)
{
InsertedAt = Cluster->MutableObjects.Insert(ThisObjectIndex, MutableObjectIndex);
}
else if (Cluster->MutableObjects[MutableObjectIndex] == ThisObjectIndex)
{
InsertedAt = MutableObjectIndex;
}
}
if (InsertedAt == INDEX_NONE)
{
Cluster->MutableObjects.Add(ThisObjectIndex);
}
}
}
}
bool UObjectBaseUtility::CanBeInCluster() const
{
return OuterPrivate ? OuterPrivate->CanBeInCluster() : true;
}
void UObjectBaseUtility::CreateCluster()
{
check(GCreateGCClusters);
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UObjectBaseUtility::CreateCluster"), STAT_FArchiveRealtimeGC_CreateCluster, STATGROUP_GC);
FUObjectItem* RootItem = GUObjectArray.IndexToObject(InternalIndex);
if (RootItem->GetOwnerIndex() != 0 || RootItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot))
{
return;
}
// If we haven't finished loading, we can't be sure we know all the references
check(!HasAnyFlags(RF_NeedLoad));
// Create a new cluster, reserve an arbitrary amount of memory for it.
const int32 ClusterIndex = GUObjectClusters.AllocateCluster(InternalIndex);
FUObjectCluster& Cluster = GUObjectClusters[ClusterIndex];
Cluster.Objects.Reserve(64);
// Collect all objects referenced by cluster root and by all objects it's referencing
FClusterReferenceProcessor Processor(InternalIndex, Cluster);
TFastReferenceCollector<
FClusterReferenceProcessor,
TDefaultReferenceCollector<FClusterReferenceProcessor>,
FGCArrayPool,
EFastReferenceCollectorOptions::AutogenerateTokenStream | EFastReferenceCollectorOptions::ProcessNoOpTokens>
ReferenceCollector(Processor, FGCArrayPool::Get());
FGCArrayStruct ArrayStruct;
TArray<UObject*>& ObjectsToProcess = ArrayStruct.ObjectsToSerialize;
ObjectsToProcess.Add(static_cast<UObject*>(this));
ReferenceCollector.CollectReferences(ArrayStruct);
#if UE_BUILD_DEBUG
FGCArrayPool::Get().CheckLeaks();
#endif
check(RootItem->GetOwnerIndex() == 0);
RootItem->SetClusterIndex(ClusterIndex);
RootItem->SetFlags(EInternalObjectFlags::ClusterRoot);
if (Cluster.Objects.Num() >= GUObjectClusters.GetMinClusterSize())
{
// Add new cluster to the global cluster map.
Cluster.Objects.Sort();
Cluster.ReferencedClusters.Sort();
Cluster.MutableObjects.Sort();
#if UE_GCCLUSTER_VERBOSE_LOGGING
UE_LOG(LogObj, Log, TEXT("Created Cluster (%d) with %d objects, %d referenced clusters and %d mutable objects."),
ClusterIndex, Cluster.Objects.Num(), Cluster.ReferencedClusters.Num(), Cluster.MutableObjects.Num());
DumpClusterToLog(Cluster, true, false);
#endif
}
else
{
for (int32 ClusterObjectIndex: Cluster.Objects)
{
FUObjectItem* ClusterObjectItem = GUObjectArray.IndexToObjectUnsafeForGC(ClusterObjectIndex);
ClusterObjectItem->SetOwnerIndex(0);
}
GUObjectClusters.FreeCluster(ClusterIndex);
check(RootItem->GetOwnerIndex() == 0);
check(!RootItem->HasAnyFlags(EInternalObjectFlags::ClusterRoot));
}
}