// 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 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(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 ReferencedByClusters = MoveTemp(Cluster.ReferencedByClusters); #if UE_GCCLUSTER_VERBOSE_LOGGING UObject* ClusterRootObject = static_cast(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(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& 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& OutIndexArray, const TArray& 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 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 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(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(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(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(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& Args) { const bool bHierarchy = Args.Contains(TEXT("Hierarchy")); int32 MaxInterClusterReferences = 0; int32 TotalInterClusterReferences = 0; int32 MaxClusterSize = 0; int32 TotalClusterObjects = 0; TArray AllClusters; for (FUObjectCluster& Cluster: GUObjectClusters.GetClustersUnsafe()) { if (Cluster.RootIndex != INDEX_NONE) { AllClusters.Add(&Cluster); } } TArray 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 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& 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(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(RootItem->Object)->GetFullName()); bool bIsReferenced = false; for (int32 ObjectIndex: Cluster->Objects) { FUObjectItem* ObjectItem = GUObjectArray.IndexToObject(ObjectIndex); FReferenceChainSearch SearchRefs(static_cast(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(RootItem->Object)->GetFullName()); } } void DumpRefsToCluster(const TArray& Args) { // This is seriously slow. UE_LOG(LogObj, Display, TEXT("Searching for references to clusteres. This may take a while...")); TArray RootObjects; for (const FString& Arg: Args) { if (Arg.StartsWith("Root=")) { FString ObjectsList = Arg.Mid(5); TArray 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(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(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& 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& 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& 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, FGCArrayPool, EFastReferenceCollectorOptions::AutogenerateTokenStream | EFastReferenceCollectorOptions::ProcessNoOpTokens> ReferenceCollector(Processor, FGCArrayPool::Get()); FGCArrayStruct ArrayStruct; TArray& ObjectsToProcess = ArrayStruct.ObjectsToSerialize; UObject* ThisObject = static_cast(this); Processor.HandleTokenStreamObjectReference(ObjectsToProcess, static_cast(ClusterRootOrObjectFromCluster), ThisObject, INDEX_NONE, true); if (ObjectsToProcess.Num()) { ReferenceCollector.CollectReferences(ArrayStruct); } #if UE_GCCLUSTER_VERBOSE_LOGGING UObject* ClusterRootObject = static_cast(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, FGCArrayPool, EFastReferenceCollectorOptions::AutogenerateTokenStream | EFastReferenceCollectorOptions::ProcessNoOpTokens> ReferenceCollector(Processor, FGCArrayPool::Get()); FGCArrayStruct ArrayStruct; TArray& ObjectsToProcess = ArrayStruct.ObjectsToSerialize; ObjectsToProcess.Add(static_cast(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)); } }