EM_Task/CoreUObject/Private/UObject/UObjectHash.cpp

1662 lines
58 KiB
C++
Raw Normal View History

2026-02-13 16:18:33 +08:00
// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
UObjectHash.cpp: Unreal object name hashes
=============================================================================*/
#include "UObject/UObjectHash.h"
#include "UObject/Class.h"
#include "UObject/Package.h"
#include "Misc/AsciiSet.h"
#include "Misc/PackageName.h"
#include "HAL/IConsoleManager.h"
DEFINE_LOG_CATEGORY_STATIC(LogUObjectHash, Log, All);
DECLARE_CYCLE_STAT(TEXT("GetObjectsOfClass"), STAT_Hash_GetObjectsOfClass, STATGROUP_UObjectHash);
DECLARE_CYCLE_STAT(TEXT("HashObject"), STAT_Hash_HashObject, STATGROUP_UObjectHash);
DECLARE_CYCLE_STAT(TEXT("UnhashObject"), STAT_Hash_UnhashObject, STATGROUP_UObjectHash);
#if UE_GC_TRACK_OBJ_AVAILABLE
DEFINE_STAT(STAT_Hash_NumObjects);
#endif
// Global UObject array instance
FUObjectArray GUObjectArray;
/**
* This implementation will use more space than the UE3 implementation. The goal was to make UObjects smaller to save L2 cache space.
* The hash is rarely used at runtime. A more space-efficient implementation is possible.
*/
/*
* Special hash bucket to conserve memory.
* Contains a pointer to head element and an optional list of items if more than one element exists in the bucket.
* The item list is only allocated if needed.
*/
struct FHashBucket
{
friend struct FHashBucketIterator;
/** This always empty set is used to get an iterator if the bucket doesn't use a TSet (has only 1 element) */
static TSet<UObjectBase*> EmptyBucket;
/*
* If these are both null, this bucket is empty
* If the first one is null, but the second one is non-null, then the second one is a TSet pointer
* If the first one is not null, then it is a uobject ptr, and the second ptr is either null or a second element
*/
void* ElementsOrSetPtr[2];
#if !UE_BUILD_SHIPPING
/** If true this bucket is being iterated over and no Add or Remove operations are allowed */
int32 ReadOnlyLock;
FORCEINLINE void Lock()
{
ReadOnlyLock++;
}
FORCEINLINE void Unlock()
{
ReadOnlyLock--;
check(ReadOnlyLock >= 0);
}
#endif // !UE_BUILD_SHIPPING
FORCEINLINE TSet<UObjectBase*>* GetSet()
{
if (ElementsOrSetPtr[1] && !ElementsOrSetPtr[0])
{
return (TSet<UObjectBase*>*)ElementsOrSetPtr[1];
}
return nullptr;
}
FORCEINLINE const TSet<UObjectBase*>* GetSet() const
{
if (ElementsOrSetPtr[1] && !ElementsOrSetPtr[0])
{
return (TSet<UObjectBase*>*)ElementsOrSetPtr[1];
}
return nullptr;
}
/** Constructor */
FORCEINLINE FHashBucket()
{
ElementsOrSetPtr[0] = nullptr;
ElementsOrSetPtr[1] = nullptr;
#if !UE_BUILD_SHIPPING
ReadOnlyLock = 0;
#endif // !UE_BUILD_SHIPPING
}
FORCEINLINE ~FHashBucket()
{
delete GetSet();
}
/** Adds an Object to the bucket */
FORCEINLINE void Add(UObjectBase* Object)
{
#if !UE_BUILD_SHIPPING
UE_CLOG(ReadOnlyLock != 0, LogObj, Fatal, TEXT("Trying to add %s to a hash bucket that is currently being iterated over which is not allowed and may lead to undefined behavior!"),
*static_cast<UObject*>(Object)->GetFullName());
#endif // !UE_BUILD_SHIPPING
TSet<UObjectBase*>* Items = GetSet();
if (Items)
{
Items->Add(Object);
}
else if (ElementsOrSetPtr[0] && ElementsOrSetPtr[1])
{
Items = new TSet<UObjectBase*>();
Items->Add((UObjectBase*)ElementsOrSetPtr[0]);
Items->Add((UObjectBase*)ElementsOrSetPtr[1]);
Items->Add(Object);
ElementsOrSetPtr[0] = nullptr;
ElementsOrSetPtr[1] = Items;
}
else if (ElementsOrSetPtr[0])
{
ElementsOrSetPtr[1] = Object;
}
else
{
ElementsOrSetPtr[0] = Object;
checkSlow(!ElementsOrSetPtr[1]);
}
}
/** Removes an Object from the bucket */
FORCEINLINE int32 Remove(UObjectBase* Object)
{
#if !UE_BUILD_SHIPPING
UE_CLOG(ReadOnlyLock != 0, LogObj, Fatal, TEXT("Trying to remove %s from a hash bucket that is currently being iterated over which is not allowed and may lead to undefined behavior!"),
*static_cast<UObject*>(Object)->GetFullName());
#endif // !UE_BUILD_SHIPPING
int32 Result = 0;
TSet<UObjectBase*>* Items = GetSet();
if (Items)
{
Result = Items->Remove(Object);
if (Items->Num() <= 2)
{
auto It = TSet<UObjectBase*>::TIterator(*Items);
ElementsOrSetPtr[0] = *It;
checkSlow((bool)It);
++It;
ElementsOrSetPtr[1] = *It;
delete Items;
}
}
else if (Object == ElementsOrSetPtr[1])
{
Result = 1;
ElementsOrSetPtr[1] = nullptr;
}
else if (Object == ElementsOrSetPtr[0])
{
Result = 1;
ElementsOrSetPtr[0] = ElementsOrSetPtr[1];
ElementsOrSetPtr[1] = nullptr;
}
return Result;
}
/** Checks if an Object exists in this bucket */
FORCEINLINE bool Contains(UObjectBase* Object) const
{
const TSet<UObjectBase*>* Items = GetSet();
if (Items)
{
return Items->Contains(Object);
}
return Object == ElementsOrSetPtr[0] || Object == ElementsOrSetPtr[1];
}
/** Returns the number of Objects in this bucket */
FORCEINLINE int32 Num() const
{
const TSet<UObjectBase*>* Items = GetSet();
if (Items)
{
return Items->Num();
}
return !!ElementsOrSetPtr[0] + !!ElementsOrSetPtr[1];
}
/** Returns the amount of memory allocated for and by Items TSet */
FORCEINLINE uint32 GetItemsSize() const
{
const TSet<UObjectBase*>* Items = GetSet();
if (Items)
{
return (uint32)sizeof(*Items) + Items->GetAllocatedSize();
}
return 0;
}
void Compact()
{
TSet<UObjectBase*>* Items = GetSet();
if (Items)
{
Items->Compact();
}
}
private:
/** Gets an iterator for the TSet in this bucket or for the EmptyBucker if Items is null */
FORCEINLINE TSet<UObjectBase*>::TIterator GetIteratorForSet()
{
TSet<UObjectBase*>* Items = GetSet();
return Items ? Items->CreateIterator() : EmptyBucket.CreateIterator();
}
};
TSet<UObjectBase*> FHashBucket::EmptyBucket;
/** Hash Bucket Iterator. Iterates over all Objects in the bucket */
struct FHashBucketIterator
{
FHashBucket& Bucket;
TSet<UObjectBase*>::TIterator SetIterator;
bool bItems;
bool bReachedEndNoItems;
bool bSecondItem;
FORCEINLINE FHashBucketIterator(FHashBucket& InBucket)
: Bucket(InBucket), SetIterator(InBucket.GetIteratorForSet()), bItems(!!InBucket.GetSet()), bReachedEndNoItems(!InBucket.ElementsOrSetPtr[0] && !InBucket.ElementsOrSetPtr[1]), bSecondItem(false)
{
}
/** Advances the iterator to the next element. */
FORCEINLINE FHashBucketIterator& operator++()
{
if (bItems)
{
++SetIterator;
}
else
{
bReachedEndNoItems = bSecondItem || !Bucket.ElementsOrSetPtr[1];
bSecondItem = true;
}
return *this;
}
/** conversion to "bool" returning true if the iterator is valid. */
FORCEINLINE explicit operator bool() const
{
if (bItems)
{
return (bool)SetIterator;
}
else
{
return !bReachedEndNoItems;
}
}
/** inverse of the "bool" operator */
FORCEINLINE bool operator!() const
{
return !(bool)*this;
}
FORCEINLINE UObjectBase*& operator*()
{
if (bItems)
{
return *SetIterator;
}
else
{
return (UObjectBase*&)Bucket.ElementsOrSetPtr[!!bSecondItem];
}
}
};
class FUObjectHashTables
{
/** Critical section that guards against concurrent adds from multiple threads */
FCriticalSection CriticalSection;
public:
/** Hash sets */
TMap<int32, FHashBucket> Hash;
TMultiMap<int32, class UObjectBase*> HashOuter;
/** Map of object to their outers, used to avoid an object iterator to find such things. **/
TMap<UObjectBase*, FHashBucket> ObjectOuterMap;
TMap<UClass*, FHashBucket> ClassToObjectListMap;
TMap<UClass*, TSet<UClass*>> ClassToChildListMap;
TAtomic<uint64> ClassToChildListMapVersion;
/** Map of package to the object their contain. */
TMap<UPackage*, FHashBucket> PackageToObjectListMap;
/** Map of object to their external package. */
TMap<UObjectBase*, UPackage*> ObjectToPackageMap;
FUObjectHashTables()
: ClassToChildListMapVersion(0)
{
}
void ShrinkMaps()
{
double StartTime = FPlatformTime::Seconds();
Hash.Compact();
for (auto& Pair: Hash)
{
Pair.Value.Compact();
}
HashOuter.Compact();
ObjectOuterMap.Compact();
for (auto& Pair: ObjectOuterMap)
{
Pair.Value.Compact();
}
ClassToObjectListMap.Compact();
for (auto& Pair: ClassToObjectListMap)
{
Pair.Value.Compact();
}
ClassToChildListMap.Compact();
for (auto& Pair: ClassToChildListMap)
{
Pair.Value.Compact();
}
PackageToObjectListMap.Compact();
for (auto& Pair: PackageToObjectListMap)
{
Pair.Value.Compact();
}
ObjectToPackageMap.Compact();
UE_LOG(LogUObjectHash, Log, TEXT("Compacting FUObjectHashTables data took %6.2fms"), 1000.0f * float(FPlatformTime::Seconds() - StartTime));
}
/** Checks if the Hash/Object pair exists in the FName hash table */
FORCEINLINE bool PairExistsInHash(int32 InHash, UObjectBase* Object)
{
bool bResult = false;
FHashBucket* Bucket = Hash.Find(InHash);
if (Bucket)
{
bResult = Bucket->Contains(Object);
}
return bResult;
}
/** Adds the Hash/Object pair to the FName hash table */
FORCEINLINE void AddToHash(int32 InHash, UObjectBase* Object)
{
FHashBucket& Bucket = Hash.FindOrAdd(InHash);
Bucket.Add(Object);
}
/** Removes the Hash/Object pair from the FName hash table */
FORCEINLINE int32 RemoveFromHash(int32 InHash, UObjectBase* Object)
{
int32 NumRemoved = 0;
FHashBucket* Bucket = Hash.Find(InHash);
if (Bucket)
{
NumRemoved = Bucket->Remove(Object);
if (Bucket->Num() == 0)
{
Hash.Remove(InHash);
}
}
return NumRemoved;
}
FORCEINLINE void Lock()
{
CriticalSection.Lock();
}
FORCEINLINE void Unlock()
{
CriticalSection.Unlock();
}
static FUObjectHashTables& Get()
{
static FUObjectHashTables Singleton;
return Singleton;
}
};
class FHashTableLock
{
#if THREADSAFE_UOBJECTS
FUObjectHashTables* Tables;
#endif
public:
FORCEINLINE FHashTableLock(FUObjectHashTables& InTables)
{
#if THREADSAFE_UOBJECTS
if (!(IsGarbageCollecting() && IsInGameThread()))
{
Tables = &InTables;
InTables.Lock();
}
else
{
Tables = nullptr;
}
#else
check(IsInGameThread());
#endif
}
FORCEINLINE ~FHashTableLock()
{
#if THREADSAFE_UOBJECTS
if (Tables)
{
Tables->Unlock();
}
#endif
}
};
/**
* Calculates the object's hash just using the object's name index
*
* @param ObjName the object's name to use the index of
*/
static FORCEINLINE int32 GetObjectHash(FName ObjName)
{
return GetTypeHash(ObjName);
}
/**
* Calculates the object's hash just using the object's name index
* XORed with the outer. Yields much better spread in the hash
* buckets, but requires knowledge of the outer, which isn't available
* in all cases.
*
* @param ObjName the object's name to use the index of
* @param Outer the object's outer pointer treated as an int32
*/
static FORCEINLINE int32 GetObjectOuterHash(FName ObjName, PTRINT Outer)
{
return GetTypeHash(ObjName) + (Outer >> 6);
}
UObject* StaticFindObjectFastExplicitThreadSafe(FUObjectHashTables& ThreadHash, const UClass* ObjectClass, FName ObjectName, const FString& ObjectPathName, bool bExactClass, EObjectFlags ExcludeFlags /*=0*/)
{
const EInternalObjectFlags ExclusiveInternalFlags = EInternalObjectFlags::Unreachable;
// Find an object with the specified name and (optional) class, in any package; if bAnyPackage is false, only matches top-level packages
int32 Hash = GetObjectHash(ObjectName);
FHashTableLock HashLock(ThreadHash);
FHashBucket* Bucket = ThreadHash.Hash.Find(Hash);
if (Bucket)
{
for (FHashBucketIterator It(*Bucket); It; ++It)
{
UObject* Object = (UObject*)*It;
if ((Object->GetFName() == ObjectName)
/* Don't return objects that have any of the exclusive flags set */
&& !Object->HasAnyFlags(ExcludeFlags)
&& !Object->HasAnyInternalFlags(ExclusiveInternalFlags)
/** If a class was specified, check that the object is of the correct class */
&& (ObjectClass == nullptr || (bExactClass ? Object->GetClass() == ObjectClass : Object->IsA(ObjectClass))))
{
FString ObjectPath = Object->GetPathName();
/** Finally check the explicit path */
if (ObjectPath == ObjectPathName)
{
checkf(!Object->IsUnreachable(), TEXT("%s"), *Object->GetFullName());
return Object;
}
}
}
}
return nullptr;
}
/**
* Variation of StaticFindObjectFast that uses explicit path.
*
* @param ObjectClass The to be found object's class
* @param ObjectName The to be found object's class
* @param ObjectPathName Full path name for the object to search for
* @param ExactClass Whether to require an exact match with the passed in class
* @param ExclusiveFlags Ignores objects that contain any of the specified exclusive flags
* @return Returns a pointer to the found object or nullptr if none could be found
*/
UObject* StaticFindObjectFastExplicit(const UClass* ObjectClass, FName ObjectName, const FString& ObjectPathName, bool bExactClass, EObjectFlags ExcludeFlags /*=0*/)
{
checkSlow(FPackageName::IsShortPackageName(ObjectName)); //@Package name transition, we aren't checking the name here because we know this is only used for texture
// Find an object with the specified name and (optional) class, in any package; if bAnyPackage is false, only matches top-level packages
const int32 Hash = GetObjectHash(ObjectName);
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
UObject* Result = StaticFindObjectFastExplicitThreadSafe(ThreadHash, ObjectClass, ObjectName, ObjectPathName, bExactClass, ExcludeFlags);
return Result;
}
static bool NameEndsWith(FName Name, FName Suffix)
{
if (Name == Suffix)
{
return true;
}
if (Name.GetNumber() != Suffix.GetNumber())
{
return false;
}
TCHAR PlainName[NAME_SIZE];
TCHAR PlainSuffix[NAME_SIZE];
uint32 NameLen = Name.GetPlainNameString(PlainName);
uint32 SuffixLen = Suffix.GetPlainNameString(PlainSuffix);
return NameLen >= SuffixLen && FCString::Strnicmp(PlainName + NameLen - SuffixLen, PlainSuffix, SuffixLen) == 0;
}
// Splits an object path into FNames representing an outer chain.
//
// Input path examples: "Object", "Package.Object", "Object:Subobject", "Object:Subobject.Nested", "Package.Object:Subobject", "Package.Object:Subobject.NestedSubobject"
struct FObjectSearchPath
{
FName Inner;
TArray<FName, TInlineAllocator<8>> Outers;
explicit FObjectSearchPath(FName InPath)
{
TCHAR Buffer[NAME_SIZE];
InPath.GetPlainNameString(Buffer);
constexpr FAsciiSet DotColon(".:");
const TCHAR* Begin = Buffer;
const TCHAR* End = FAsciiSet::FindFirstOrEnd(Begin, DotColon);
while (*End != '\0')
{
Outers.Add(FName(End - Begin, Begin));
Begin = End + 1;
End = FAsciiSet::FindFirstOrEnd(Begin, DotColon);
}
Inner = Outers.Num() == 0 ? InPath : FName(FStringView(Begin, End - Begin), InPath.GetNumber());
}
bool MatchOuterNames(UObject* Outer) const
{
if (Outers.Num() == 0)
{
return true;
}
for (int32 Idx = Outers.Num() - 1; Idx > 0; --Idx)
{
if (!Outer || Outer->GetFName() != Outers[Idx])
{
return false;
}
Outer = Outer->GetOuter();
}
return Outer && NameEndsWith(Outer->GetFName(), Outers[0]);
}
};
UObject* StaticFindObjectInPackageInternal(FUObjectHashTables& ThreadHash, const UClass* ObjectClass, const UPackage* ObjectPackage, FName ObjectName, bool bExactClass, EObjectFlags ExcludeFlags, EInternalObjectFlags ExclusiveInternalFlags)
{
ExclusiveInternalFlags |= EInternalObjectFlags::Unreachable;
UObject* Result = nullptr;
if (FHashBucket* Inners = ThreadHash.PackageToObjectListMap.Find(ObjectPackage))
{
#if !UE_BUILD_SHIPPING
Inners->Lock();
#endif // !UE_BUILD_SHIPPING
for (FHashBucketIterator It(*Inners); It; ++It)
{
UObject* Object = static_cast<UObject*>(*It);
if
/* check that the name matches the name we're searching for */
((Object->GetFName() == ObjectName)
/* Don't return objects that have any of the exclusive flags set */
&& !Object->HasAnyFlags(ExcludeFlags)
/** Do not return ourselves (Packages currently have themselves as their package. )*/
&& Object != ObjectPackage
/** If a class was specified, check that the object is of the correct class */
&& (ObjectClass == nullptr || (bExactClass ? Object->GetClass() == ObjectClass : Object->IsA(ObjectClass)))
/** Include (or not) pending kill objects */
&& !Object->HasAnyInternalFlags(ExclusiveInternalFlags))
{
checkf(!Object->IsUnreachable(), TEXT("%s"), *Object->GetFullName());
Result = Object;
break;
}
}
#if !UE_BUILD_SHIPPING
Inners->Unlock();
#endif // !UE_BUILD_SHIPPING
}
return Result;
}
UObject* StaticFindObjectFastInternalThreadSafe(FUObjectHashTables& ThreadHash, const UClass* ObjectClass, const UObject* ObjectPackage, FName ObjectName, bool bExactClass, bool bAnyPackage, EObjectFlags ExcludeFlags, EInternalObjectFlags ExclusiveInternalFlags)
{
ExclusiveInternalFlags |= EInternalObjectFlags::Unreachable;
// If they specified an outer use that during the hashing
UObject* Result = nullptr;
if (ObjectPackage != nullptr)
{
int32 Hash = GetObjectOuterHash(ObjectName, (PTRINT)ObjectPackage);
FHashTableLock HashLock(ThreadHash);
for (TMultiMap<int32, class UObjectBase*>::TConstKeyIterator HashIt(ThreadHash.HashOuter, Hash); HashIt; ++HashIt)
{
UObject* Object = (UObject*)HashIt.Value();
if
/* check that the name matches the name we're searching for */
((Object->GetFName() == ObjectName)
/* Don't return objects that have any of the exclusive flags set */
&& !Object->HasAnyFlags(ExcludeFlags)
/* check that the object has the correct Outer */
&& Object->GetOuter() == ObjectPackage
/** If a class was specified, check that the object is of the correct class */
&& (ObjectClass == nullptr || (bExactClass ? Object->GetClass() == ObjectClass : Object->IsA(ObjectClass)))
/** Include (or not) pending kill objects */
&& !Object->HasAnyInternalFlags(ExclusiveInternalFlags))
{
checkf(!Object->IsUnreachable(), TEXT("%s"), *Object->GetFullName());
if (Result)
{
UE_LOG(LogUObjectHash, Warning, TEXT("Ambiguous search, could be %s or %s"), *GetFullNameSafe(Result), *GetFullNameSafe(Object));
}
else
{
Result = Object;
}
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
break;
#endif
}
}
#if WITH_EDITOR
// if the search fail and the OuterPackage is a UPackage, lookup potential external package
if (Result == nullptr && ObjectPackage->IsA(UPackage::StaticClass()))
{
Result = StaticFindObjectInPackageInternal(ThreadHash, ObjectClass, static_cast<const UPackage*>(ObjectPackage), ObjectName, bExactClass, ExcludeFlags, ExclusiveInternalFlags);
}
#endif
}
else
{
FObjectSearchPath SearchPath(ObjectName);
const int32 Hash = GetObjectHash(SearchPath.Inner);
FHashTableLock HashLock(ThreadHash);
FHashBucket* Bucket = ThreadHash.Hash.Find(Hash);
if (Bucket)
{
for (FHashBucketIterator It(*Bucket); It; ++It)
{
UObject* Object = (UObject*)*It;
if ((Object->GetFName() == SearchPath.Inner)
/* Don't return objects that have any of the exclusive flags set */
&& !Object->HasAnyFlags(ExcludeFlags)
/*If there is no package (no InObjectPackage specified, and InName's package is "")
and the caller specified any_package, then accept it, regardless of its package.
Or, if the object is a top-level package then accept it immediately.*/
&& (bAnyPackage || !Object->GetOuter())
/** If a class was specified, check that the object is of the correct class */
&& (ObjectClass == nullptr || (bExactClass ? Object->GetClass() == ObjectClass : Object->IsA(ObjectClass)))
/** Include (or not) pending kill objects */
&& !Object->HasAnyInternalFlags(ExclusiveInternalFlags)
/** Ensure that the partial path provided matches the object found */
&& SearchPath.MatchOuterNames(Object->GetOuter()))
{
checkf(!Object->IsUnreachable(), TEXT("%s"), *Object->GetFullName());
if (Result)
{
UE_LOG(LogUObjectHash, Warning, TEXT("Ambiguous path search, could be %s or %s"), *GetFullNameSafe(Result), *GetFullNameSafe(Object));
}
else
{
Result = Object;
}
#if (UE_BUILD_SHIPPING || UE_BUILD_TEST)
break;
#endif
}
}
}
}
// Not found.
return Result;
}
UObject* StaticFindObjectFastInternal(const UClass* ObjectClass, const UObject* ObjectPackage, FName ObjectName, bool bExactClass, bool bAnyPackage, EObjectFlags ExcludeFlags, EInternalObjectFlags ExclusiveInternalFlags)
{
INC_DWORD_STAT(STAT_FindObjectFast);
check(ObjectPackage != ANY_PACKAGE); // this could never have returned anything but nullptr
// If they specified an outer use that during the hashing
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
UObject* Result = StaticFindObjectFastInternalThreadSafe(ThreadHash, ObjectClass, ObjectPackage, ObjectName, bExactClass, bAnyPackage, ExcludeFlags | RF_NewerVersionExists, ExclusiveInternalFlags);
return Result;
}
// Assumes that ThreadHash's critical is already locked
FORCEINLINE static void AddToOuterMap(FUObjectHashTables& ThreadHash, UObjectBase* Object)
{
FHashBucket& Bucket = ThreadHash.ObjectOuterMap.FindOrAdd(Object->GetOuter());
checkSlow(!Bucket.Contains(Object)); // if it already exists, something is wrong with the external code
Bucket.Add(Object);
}
// Assumes that ThreadHash's critical is already locked
FORCEINLINE static void AddToClassMap(FUObjectHashTables& ThreadHash, UObjectBase* Object)
{
{
check(Object->GetClass());
FHashBucket& ObjectList = ThreadHash.ClassToObjectListMap.FindOrAdd(Object->GetClass());
ObjectList.Add(Object);
}
UObjectBaseUtility* ObjectWithUtility = static_cast<UObjectBaseUtility*>(Object);
if (ObjectWithUtility->IsA(UClass::StaticClass()))
{
UClass* Class = static_cast<UClass*>(ObjectWithUtility);
UClass* SuperClass = Class->GetSuperClass();
if (SuperClass)
{
TSet<UClass*>& ChildList = ThreadHash.ClassToChildListMap.FindOrAdd(SuperClass);
bool bIsAlreadyInSetPtr = false;
ChildList.Add(Class, &bIsAlreadyInSetPtr);
ThreadHash.ClassToChildListMapVersion++;
check(!bIsAlreadyInSetPtr); // if it already exists, something is wrong with the external code
}
}
}
// Assumes that ThreadHash's critical is already locked
FORCEINLINE static void AddToPackageMap(FUObjectHashTables& ThreadHash, UObjectBase* Object, UPackage* Package)
{
check(Package != nullptr);
FHashBucket& Bucket = ThreadHash.PackageToObjectListMap.FindOrAdd(Package);
checkSlow(!Bucket.Contains(Object)); // if it already exists, something is wrong with the external code
Bucket.Add(Object);
}
// Assumes that ThreadHash's critical is already locked
FORCEINLINE static UPackage* AssignExternalPackageToObject(FUObjectHashTables& ThreadHash, UObjectBase* Object, UPackage* Package)
{
UPackage*& ExternalPackageRef = ThreadHash.ObjectToPackageMap.FindOrAdd(Object);
UPackage* OldPackage = ExternalPackageRef;
ExternalPackageRef = Package;
return OldPackage;
}
// Assumes that ThreadHash's critical is already locked
FORCEINLINE static void RemoveFromOuterMap(FUObjectHashTables& ThreadHash, UObjectBase* Object)
{
FHashBucket& Bucket = ThreadHash.ObjectOuterMap.FindOrAdd(Object->GetOuter());
int32 NumRemoved = Bucket.Remove(Object);
UE_CLOG(NumRemoved != 1, LogUObjectHash, Fatal, TEXT("Internal Error: RemoveFromOuterMap NumRemoved = %d for %s"), NumRemoved, *GetFullNameSafe((UObjectBaseUtility*)Object));
if (!Bucket.Num())
{
ThreadHash.ObjectOuterMap.Remove(Object->GetOuter());
}
}
// Assumes that ThreadHash's critical is already locked
FORCEINLINE static void RemoveFromClassMap(FUObjectHashTables& ThreadHash, UObjectBase* Object)
{
UObjectBaseUtility* ObjectWithUtility = static_cast<UObjectBaseUtility*>(Object);
{
FHashBucket& ObjectList = ThreadHash.ClassToObjectListMap.FindOrAdd(Object->GetClass());
int32 NumRemoved = ObjectList.Remove(Object);
// must have existed, else something is wrong with the external code
UE_CLOG(NumRemoved != 1, LogUObjectHash, Fatal, TEXT("Internal Error: RemoveFromClassMap NumRemoved = %d for %s"), NumRemoved, *GetFullNameSafe(ObjectWithUtility));
if (!ObjectList.Num())
{
ThreadHash.ClassToObjectListMap.Remove(Object->GetClass());
}
}
if (ObjectWithUtility->IsA(UClass::StaticClass()))
{
UClass* Class = static_cast<UClass*>(ObjectWithUtility);
UClass* SuperClass = Class->GetSuperClass();
if (SuperClass)
{
// Remove the class from the SuperClass' child list
TSet<UClass*>& ChildList = ThreadHash.ClassToChildListMap.FindOrAdd(SuperClass);
int32 NumRemoved = ChildList.Remove(Class);
// must have existed, else something is wrong with the external code
UE_CLOG(NumRemoved != 1, LogUObjectHash, Fatal, TEXT("Internal Error: RemoveFromClassMap NumRemoved = %d for %s"), NumRemoved, *GetFullNameSafe(ObjectWithUtility));
if (!ChildList.Num())
{
ThreadHash.ClassToChildListMap.Remove(SuperClass);
}
ThreadHash.ClassToChildListMapVersion++;
}
}
}
// Assumes that ThreadHash's critical is already locked
FORCEINLINE static void RemoveFromPackageMap(FUObjectHashTables& ThreadHash, UObjectBase* Object, UPackage* Package)
{
check(Package != nullptr);
FHashBucket& Bucket = ThreadHash.PackageToObjectListMap.FindOrAdd(Package);
int32 NumRemoved = Bucket.Remove(Object);
UE_CLOG(NumRemoved != 1, LogUObjectHash, Fatal, TEXT("Internal Error: RemoveFromPackageMap NumRemoved = %d for %s"), NumRemoved, *GetFullNameSafe((UObjectBaseUtility*)Object));
if (!Bucket.Num())
{
ThreadHash.PackageToObjectListMap.Remove(Package);
}
}
// Assumes that ThreadHash's critical is already locked
FORCEINLINE static UPackage* UnassignExternalPackageFromObject(FUObjectHashTables& ThreadHash, UObjectBase* Object)
{
UPackage* OldPackage = nullptr;
ThreadHash.ObjectToPackageMap.RemoveAndCopyValue(Object, OldPackage);
return OldPackage;
}
void ShrinkUObjectHashTables()
{
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock HashLock(ThreadHash);
ThreadHash.ShrinkMaps();
}
uint64 GetRegisteredClassesVersionNumber()
{
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
return ThreadHash.ClassToChildListMapVersion;
}
static void ShrinkUObjectHashTablesDel(const TArray<FString>& Args)
{
ShrinkUObjectHashTables();
}
static FAutoConsoleCommand ShrinkUObjectHashTablesCmd(
TEXT("ShrinkUObjectHashTables"),
TEXT("Shrinks all of the UObject hash tables."),
FConsoleCommandWithArgsDelegate::CreateStatic(&ShrinkUObjectHashTablesDel));
void GetObjectsWithOuter(const class UObjectBase* Outer, TArray<UObject*>& Results, bool bIncludeNestedObjects, EObjectFlags ExclusionFlags, EInternalObjectFlags ExclusionInternalFlags)
{
checkf(Outer != nullptr, TEXT("Getting objects with a null outer is no longer supported. If you want to get all packages you might consider using GetObjectsOfClass instead."));
#if WITH_EDITOR
// uncomment ensure to more easily find place where GetObjectsWithOuter should be replaced with GetObjectsWithPackage
if (!/*ensure*/ (!Outer->GetClass()->IsChildOf(UPackage::StaticClass())))
{
GetObjectsWithPackage((UPackage*)Outer, Results, bIncludeNestedObjects, ExclusionFlags, ExclusionInternalFlags);
return;
}
#endif
// We don't want to return any objects that are currently being background loaded unless we're using the object iterator during async loading.
ExclusionInternalFlags |= EInternalObjectFlags::Unreachable;
if (!IsInAsyncLoadingThread())
{
ExclusionInternalFlags |= EInternalObjectFlags::AsyncLoading;
}
int32 StartNum = Results.Num();
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock HashLock(ThreadHash);
FHashBucket* Inners = ThreadHash.ObjectOuterMap.Find(Outer);
if (Inners)
{
for (FHashBucketIterator It(*Inners); It; ++It)
{
UObject* Object = static_cast<UObject*>(*It);
if (!Object->HasAnyFlags(ExclusionFlags) && !Object->HasAnyInternalFlags(ExclusionInternalFlags))
{
Results.Add(Object);
}
}
int32 MaxResults = GUObjectArray.GetObjectArrayNum();
while (StartNum != Results.Num() && bIncludeNestedObjects)
{
int32 RangeStart = StartNum;
int32 RangeEnd = Results.Num();
StartNum = RangeEnd;
for (int32 Index = RangeStart; Index < RangeEnd; Index++)
{
FHashBucket* InnerInners = ThreadHash.ObjectOuterMap.Find(Results[Index]);
if (InnerInners)
{
for (FHashBucketIterator It(*InnerInners); It; ++It)
{
UObject* Object = static_cast<UObject*>(*It);
if (!Object->HasAnyFlags(ExclusionFlags) && !Object->HasAnyInternalFlags(ExclusionInternalFlags))
{
Results.Add(Object);
}
}
}
}
check(Results.Num() <= MaxResults); // otherwise we have a cycle in the outer chain, which should not be possible
}
}
}
void ForEachObjectWithOuter(const class UObjectBase* Outer, TFunctionRef<void(UObject*)> Operation, bool bIncludeNestedObjects, EObjectFlags ExclusionFlags, EInternalObjectFlags ExclusionInternalFlags)
{
checkf(Outer != nullptr, TEXT("Getting objects with a null outer is no longer supported. If you want to get all packages you might consider using GetObjectsOfClass instead."));
#if WITH_EDITOR
// uncomment ensure to more easily find place where ForEachObjectWithOuter should be replaced with ForEachObjectWithPackage
if (!/*ensure*/ (!Outer->GetClass()->IsChildOf(UPackage::StaticClass())))
{
ForEachObjectWithPackage((UPackage*)Outer, [Operation](UObject* InObject)
{
Operation(InObject);
return true;
},
bIncludeNestedObjects, ExclusionFlags, ExclusionInternalFlags);
return;
}
#endif
// We don't want to return any objects that are currently being background loaded unless we're using the object iterator during async loading.
ExclusionInternalFlags |= EInternalObjectFlags::Unreachable;
if (!IsInAsyncLoadingThread())
{
ExclusionInternalFlags |= EInternalObjectFlags::AsyncLoading;
}
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock HashLock(ThreadHash);
TArray<FHashBucket*, TInlineAllocator<1>> AllInners;
if (FHashBucket* Inners = ThreadHash.ObjectOuterMap.Find(Outer))
{
AllInners.Add(Inners);
}
while (AllInners.Num())
{
FHashBucket* Inners = AllInners.Pop();
#if !UE_BUILD_SHIPPING
Inners->Lock();
#endif // !UE_BUILD_SHIPPING
for (FHashBucketIterator It(*Inners); It; ++It)
{
UObject* Object = static_cast<UObject*>(*It);
if (!Object->HasAnyFlags(ExclusionFlags) && !Object->HasAnyInternalFlags(ExclusionInternalFlags))
{
Operation(Object);
}
if (bIncludeNestedObjects)
{
if (FHashBucket* ObjectInners = ThreadHash.ObjectOuterMap.Find(Object))
{
AllInners.Add(ObjectInners);
}
}
}
#if !UE_BUILD_SHIPPING
Inners->Unlock();
#endif // !UE_BUILD_SHIPPING
}
}
UObjectBase* FindObjectWithOuter(const class UObjectBase* Outer, const class UClass* ClassToLookFor, FName NameToLookFor)
{
UObject* Result = nullptr;
check(Outer);
// We don't want to return any objects that are currently being background loaded unless we're using the object iterator during async loading.
EInternalObjectFlags ExclusionInternalFlags = EInternalObjectFlags::Unreachable;
if (!IsInAsyncLoadingThread())
{
ExclusionInternalFlags = EInternalObjectFlags::AsyncLoading;
}
if (NameToLookFor != NAME_None)
{
Result = StaticFindObjectFastInternal(ClassToLookFor, static_cast<const UObject*>(Outer), NameToLookFor, false, false, RF_NoFlags, ExclusionInternalFlags);
}
else
{
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock HashLock(ThreadHash);
FHashBucket* Inners = ThreadHash.ObjectOuterMap.Find(Outer);
if (Inners)
{
for (FHashBucketIterator It(*Inners); It; ++It)
{
UObject* Object = static_cast<UObject*>(*It);
if (Object->HasAnyInternalFlags(ExclusionInternalFlags))
{
continue;
}
if (ClassToLookFor && !Object->IsA(ClassToLookFor))
{
continue;
}
Result = Object;
break;
}
}
}
return Result;
}
void GetObjectsWithPackage(const class UPackage* Package, TArray<UObject*>& Results, bool bIncludeNestedObjects, EObjectFlags ExclusionFlags, EInternalObjectFlags ExclusionInternalFlags)
{
ForEachObjectWithPackage(Package, [&Results](UObject* Object)
{
Results.Add(Object);
return true;
},
bIncludeNestedObjects, ExclusionFlags, ExclusionInternalFlags);
}
void ForEachObjectWithPackage(const class UPackage* Package, TFunctionRef<bool(UObject*)> Operation, bool bIncludeNestedObjects, EObjectFlags ExclusionFlags, EInternalObjectFlags ExclusionInternalFlags)
{
check(Package != nullptr);
// We don't want to return any objects that are currently being background loaded unless we're using the object iterator during async loading.
ExclusionInternalFlags |= EInternalObjectFlags::Unreachable;
if (!IsInAsyncLoadingThread())
{
ExclusionInternalFlags |= EInternalObjectFlags::AsyncLoading;
}
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock HashLock(ThreadHash);
TArray<FHashBucket*, TInlineAllocator<1>> AllInners;
// Add the object bucket that have this package as an external package
if (FHashBucket* Inners = ThreadHash.PackageToObjectListMap.Find(Package))
{
AllInners.Add(Inners);
}
// Add the object bucket that have this package as an outer
if (FHashBucket* ObjectInners = ThreadHash.ObjectOuterMap.Find(Package))
{
AllInners.Add(ObjectInners);
}
while (AllInners.Num())
{
FHashBucket* Inners = AllInners.Pop();
#if !UE_BUILD_SHIPPING
Inners->Lock();
#endif // !UE_BUILD_SHIPPING
for (FHashBucketIterator It(*Inners); It; ++It)
{
UObject* Object = static_cast<UObject*>(*It);
UPackage* ObjectPackage = Object->GetExternalPackageInternal();
bool bIsInPackage = ObjectPackage == Package || ObjectPackage == nullptr;
if (!Object->HasAnyFlags(ExclusionFlags) &&
!Object->HasAnyInternalFlags(ExclusionInternalFlags) &&
bIsInPackage)
{
if (!Operation(Object))
{
AllInners.Empty(AllInners.Max());
break;
}
}
if (bIncludeNestedObjects && bIsInPackage)
{
if (FHashBucket* ObjectInners = ThreadHash.ObjectOuterMap.Find(Object))
{
AllInners.Add(ObjectInners);
}
}
}
#if !UE_BUILD_SHIPPING
Inners->Unlock();
#endif // !UE_BUILD_SHIPPING
}
}
/** Helper function that returns all the children of the specified class recursively */
template <typename ClassType, typename ArrayAllocator>
static void RecursivelyPopulateDerivedClasses(FUObjectHashTables& ThreadHash, const UClass* ParentClass, TArray<ClassType, ArrayAllocator>& OutAllDerivedClass)
{
// Start search with the parent class at virtual index Num-1, then continue searching from index Num as things are added
int32 SearchIndex = OutAllDerivedClass.Num() - 1;
const UClass* SearchClass = ParentClass;
while (1)
{
TSet<UClass*>* ChildSet = ThreadHash.ClassToChildListMap.Find(SearchClass);
if (ChildSet)
{
for (UClass* ChildClass: *ChildSet)
{
OutAllDerivedClass.Add(ChildClass);
}
}
// Now search at next index, if it has been filled in by code above
SearchIndex++;
if (SearchIndex < OutAllDerivedClass.Num())
{
SearchClass = OutAllDerivedClass[SearchIndex];
}
else
{
return;
}
}
}
void GetObjectsOfClass(const UClass* ClassToLookFor, TArray<UObject*>& Results, bool bIncludeDerivedClasses, EObjectFlags ExclusionFlags, EInternalObjectFlags ExclusionInternalFlags)
{
SCOPE_CYCLE_COUNTER(STAT_Hash_GetObjectsOfClass);
ForEachObjectOfClass(ClassToLookFor, [&Results](UObject* Object)
{
Results.Add(Object);
},
bIncludeDerivedClasses, ExclusionFlags, ExclusionInternalFlags);
check(Results.Num() <= GUObjectArray.GetObjectArrayNum()); // otherwise we have a cycle in the outer chain, which should not be possible
}
FORCEINLINE void ForEachObjectOfClasses_Implementation(FUObjectHashTables& ThreadHash, TArrayView<const UClass*> ClassesToLookFor, TFunctionRef<void(UObject*)> Operation, EObjectFlags ExcludeFlags /*= RF_ClassDefaultObject*/, EInternalObjectFlags ExclusionInternalFlags /*= EInternalObjectFlags::None*/)
{
// We don't want to return any objects that are currently being background loaded unless we're using the object iterator during async loading.
ExclusionInternalFlags |= EInternalObjectFlags::Unreachable;
if (!IsInAsyncLoadingThread())
{
ExclusionInternalFlags |= EInternalObjectFlags::AsyncLoading;
}
for (const UClass* SearchClass: ClassesToLookFor)
{
FHashBucket* List = ThreadHash.ClassToObjectListMap.Find(SearchClass);
if (List)
{
for (FHashBucketIterator ObjectIt(*List); ObjectIt; ++ObjectIt)
{
UObject* Object = static_cast<UObject*>(*ObjectIt);
if (!Object->HasAnyFlags(ExcludeFlags) && !Object->HasAnyInternalFlags(ExclusionInternalFlags))
{
Operation(Object);
}
}
}
}
}
void ForEachObjectOfClass(const UClass* ClassToLookFor, TFunctionRef<void(UObject*)> Operation, bool bIncludeDerivedClasses, EObjectFlags ExclusionFlags, EInternalObjectFlags ExclusionInternalFlags)
{
// Most classes searched for have around 10 subclasses, some have hundreds
TArray<const UClass*, TInlineAllocator<16>> ClassesToSearch;
ClassesToSearch.Add(ClassToLookFor);
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock HashLock(ThreadHash);
if (bIncludeDerivedClasses)
{
RecursivelyPopulateDerivedClasses(ThreadHash, ClassToLookFor, ClassesToSearch);
}
ForEachObjectOfClasses_Implementation(ThreadHash, ClassesToSearch, Operation, ExclusionFlags, ExclusionInternalFlags);
}
void ForEachObjectOfClasses(TArrayView<const UClass*> ClassesToLookFor, TFunctionRef<void(UObject*)> Operation, EObjectFlags ExcludeFlags /*= RF_ClassDefaultObject*/, EInternalObjectFlags ExclusionInternalFlags /*= EInternalObjectFlags::None*/)
{
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock HashLock(ThreadHash);
ForEachObjectOfClasses_Implementation(ThreadHash, ClassesToLookFor, Operation, ExcludeFlags, ExclusionInternalFlags);
}
void GetDerivedClasses(const UClass* ClassToLookFor, TArray<UClass*>& Results, bool bRecursive)
{
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock HashLock(ThreadHash);
if (bRecursive)
{
RecursivelyPopulateDerivedClasses(ThreadHash, ClassToLookFor, Results);
}
else
{
TSet<UClass*>* DerivedClasses = ThreadHash.ClassToChildListMap.Find(ClassToLookFor);
if (DerivedClasses)
{
Results.Append(DerivedClasses->Array());
}
}
}
bool ClassHasInstancesAsyncLoading(const UClass* ClassToLookFor)
{
TArray<const UClass*> ClassesToSearch;
ClassesToSearch.Add(ClassToLookFor);
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock HashLock(ThreadHash);
RecursivelyPopulateDerivedClasses(ThreadHash, ClassToLookFor, ClassesToSearch);
for (const UClass* SearchClass: ClassesToSearch)
{
FHashBucket* List = ThreadHash.ClassToObjectListMap.Find(SearchClass);
if (List)
{
for (FHashBucketIterator ObjectIt(*List); ObjectIt; ++ObjectIt)
{
UObject* Object = static_cast<UObject*>(*ObjectIt);
if (Object->HasAnyInternalFlags(EInternalObjectFlags::AsyncLoading))
{
return true;
}
}
}
}
return false;
}
void HashObject(UObjectBase* Object)
{
SCOPE_CYCLE_COUNTER(STAT_Hash_HashObject);
FName Name = Object->GetFName();
if (Name != NAME_None)
{
int32 Hash = 0;
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock HashLock(ThreadHash);
Hash = GetObjectHash(Name);
#if !UE_BUILD_TEST && !UE_BUILD_SHIPPING
// if it already exists, something is wrong with the external code
UE_CLOG(ThreadHash.PairExistsInHash(Hash, Object), LogUObjectHash, Fatal, TEXT("%s already exists in UObject hash!"), *GetFullNameSafe((UObjectBaseUtility*)Object));
#endif
ThreadHash.AddToHash(Hash, Object);
if (PTRINT Outer = (PTRINT)Object->GetOuter())
{
Hash = GetObjectOuterHash(Name, Outer);
checkSlow(!ThreadHash.HashOuter.FindPair(Hash, Object));
#if !UE_BUILD_TEST && !UE_BUILD_SHIPPING
// if it already exists, something is wrong with the external code
UE_CLOG(ThreadHash.HashOuter.FindPair(Hash, Object), LogUObjectHash, Fatal, TEXT("%s already exists in UObject Outer hash!"), *GetFullNameSafe((UObjectBaseUtility*)Object));
#endif
ThreadHash.HashOuter.Add(Hash, Object);
AddToOuterMap(ThreadHash, Object);
}
AddToClassMap(ThreadHash, Object);
}
}
/**
* Remove an object to the name hash tables
*
* @param Object Object to remove from the hash tables
*/
void UnhashObject(UObjectBase* Object)
{
SCOPE_CYCLE_COUNTER(STAT_Hash_UnhashObject);
FName Name = Object->GetFName();
if (Name != NAME_None)
{
int32 Hash = 0;
int32 NumRemoved = 0;
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock LockHash(ThreadHash);
Hash = GetObjectHash(Name);
NumRemoved = ThreadHash.RemoveFromHash(Hash, Object);
// must have existed, else something is wrong with the external code
UE_CLOG(NumRemoved != 1, LogUObjectHash, Fatal, TEXT("Internal Error: RemoveFromHash NumRemoved = %d for %s"), NumRemoved, *GetFullNameSafe((UObjectBaseUtility*)Object));
if (PTRINT Outer = (PTRINT)Object->GetOuter())
{
Hash = GetObjectOuterHash(Name, Outer);
NumRemoved = ThreadHash.HashOuter.RemoveSingle(Hash, Object);
// must have existed, else something is wrong with the external code
UE_CLOG(NumRemoved != 1, LogUObjectHash, Fatal, TEXT("Internal Error: Remove from HashOuter NumRemoved = %d for %s"), NumRemoved, *GetFullNameSafe((UObjectBaseUtility*)Object));
RemoveFromOuterMap(ThreadHash, Object);
}
RemoveFromClassMap(ThreadHash, Object);
}
}
void HashObjectExternalPackage(UObjectBase* Object, UPackage* Package)
{
if (Package)
{
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock LockHash(ThreadHash);
UPackage* OldPackage = AssignExternalPackageToObject(ThreadHash, Object, Package);
if (OldPackage != Package)
{
if (OldPackage)
{
RemoveFromPackageMap(ThreadHash, Object, OldPackage);
}
AddToPackageMap(ThreadHash, Object, Package);
}
}
else
{
UnhashObjectExternalPackage(Object);
}
}
void UnhashObjectExternalPackage(class UObjectBase* Object)
{
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock LockHash(ThreadHash);
UPackage* Package = UnassignExternalPackageFromObject(ThreadHash, Object);
if (Package)
{
RemoveFromPackageMap(ThreadHash, Object, Package);
}
}
UPackage* GetObjectExternalPackageThreadSafe(const UObjectBase* Object)
{
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
FHashTableLock LockHash(ThreadHash);
return ThreadHash.ObjectToPackageMap.FindRef(Object);
}
UPackage* GetObjectExternalPackageInternal(const UObjectBase* Object)
{
FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
return ThreadHash.ObjectToPackageMap.FindRef(Object);
}
/**
* Prevents any other threads from finding/adding UObjects (e.g. while GC is running)
*/
void LockUObjectHashTables()
{
#if THREADSAFE_UOBJECTS
FUObjectHashTables::Get().Lock();
#else
check(IsInGameThread());
#endif
}
/**
* Releases UObject hash tables lock (e.g. after GC has finished running)
*/
void UnlockUObjectHashTables()
{
#if THREADSAFE_UOBJECTS
FUObjectHashTables::Get().Unlock();
#else
check(IsInGameThread());
#endif
}
void LogHashStatisticsInternal(TMultiMap<int32, UObjectBase*>& Hash, FOutputDevice& Ar, const bool bShowHashBucketCollisionInfo)
{
TArray<int32> HashBuckets;
// Get the set of keys in use, which is the number of hash buckets
int32 SlotsInUse = Hash.GetKeys(HashBuckets);
int32 TotalCollisions = 0;
int32 MinCollisions = MAX_int32;
int32 MaxCollisions = 0;
int32 MaxBin = 0;
// Dump how many slots are in use
Ar.Logf(TEXT("Slots in use %d"), SlotsInUse);
// Work through each slot and figure out how many collisions
for (auto HashBucket: HashBuckets)
{
int32 Collisions = 0;
for (TMultiMap<int32, UObjectBase*>::TConstKeyIterator HashIt(Hash, HashBucket); HashIt; ++HashIt)
{
// There's one collision per object in a given bucket
Collisions++;
}
// Keep the global stats
TotalCollisions += Collisions;
if (Collisions > MaxCollisions)
{
MaxBin = HashBucket;
}
MaxCollisions = FMath::Max<int32>(Collisions, MaxCollisions);
MinCollisions = FMath::Min<int32>(Collisions, MinCollisions);
if (bShowHashBucketCollisionInfo)
{
// Now log the output
Ar.Logf(TEXT("\tSlot %d has %d collisions"), HashBucket, Collisions);
}
}
Ar.Logf(TEXT(""));
// Dump the first 30 objects in the worst bin for inspection
Ar.Logf(TEXT("Worst hash bucket contains:"));
int32 Count = 0;
for (TMultiMap<int32, UObjectBase*>::TConstKeyIterator HashIt(Hash, MaxBin); HashIt && Count < 30; ++HashIt)
{
UObject* Object = (UObject*)HashIt.Value();
Ar.Logf(TEXT("\tObject is %s (%s)"), *Object->GetName(), *Object->GetFullName());
Count++;
}
Ar.Logf(TEXT(""));
// Now dump how efficient the hash is
Ar.Logf(TEXT("Collision Stats: Best Case (%d), Average Case (%d), Worst Case (%d)"),
MinCollisions,
FMath::FloorToInt(((float)TotalCollisions / (float)SlotsInUse)),
MaxCollisions);
// Calculate Hashtable size
const uint32 HashtableAllocatedSize = Hash.GetAllocatedSize();
Ar.Logf(TEXT("Total memory allocated for Object Outer Hash: %u bytes."), HashtableAllocatedSize);
}
void LogHashStatisticsInternal(TMap<int32, FHashBucket>& Hash, FOutputDevice& Ar, const bool bShowHashBucketCollisionInfo)
{
TArray<int32> HashBuckets;
// Get the set of keys in use, which is the number of hash buckets
int32 SlotsInUse = Hash.Num();
int32 TotalCollisions = 0;
int32 MinCollisions = MAX_int32;
int32 MaxCollisions = 0;
int32 MaxBin = 0;
int32 NumBucketsWithMoreThanOneItem = 0;
// Dump how many slots are in use
Ar.Logf(TEXT("Slots in use %d"), SlotsInUse);
// Work through each slot and figure out how many collisions
for (auto& HashPair: Hash)
{
int32 Collisions = HashPair.Value.Num();
check(Collisions >= 0);
if (Collisions > 1)
{
NumBucketsWithMoreThanOneItem++;
}
// Keep the global stats
TotalCollisions += Collisions;
if (Collisions > MaxCollisions)
{
MaxBin = HashPair.Key;
}
MaxCollisions = FMath::Max<int32>(Collisions, MaxCollisions);
MinCollisions = FMath::Min<int32>(Collisions, MinCollisions);
if (bShowHashBucketCollisionInfo)
{
// Now log the output
Ar.Logf(TEXT("\tSlot %d has %d collisions"), HashPair.Key, Collisions);
}
}
Ar.Logf(TEXT(""));
// Dump the first 30 objects in the worst bin for inspection
Ar.Logf(TEXT("Worst hash bucket contains:"));
int32 Count = 0;
FHashBucket& WorstBucket = Hash.FindChecked(MaxBin);
for (FHashBucketIterator It(WorstBucket); It; ++It)
{
UObject* Object = (UObject*)*It;
Ar.Logf(TEXT("\tObject is %s (%s)"), *Object->GetName(), *Object->GetFullName());
Count++;
}
Ar.Logf(TEXT(""));
// Now dump how efficient the hash is
Ar.Logf(TEXT("Collision Stats: Best Case (%d), Average Case (%d), Worst Case (%d), Number of buckets with more than one item (%d/%d)"),
MinCollisions,
FMath::FloorToInt(((float)TotalCollisions / (float)SlotsInUse)),
MaxCollisions,
NumBucketsWithMoreThanOneItem,
SlotsInUse);
// Calculate Hashtable size
uint32 HashtableAllocatedSize = Hash.GetAllocatedSize();
// Calculate the size of a all Allocations inside of the buckets (TSet Items)
for (auto& Pair: Hash)
{
HashtableAllocatedSize += Pair.Value.GetItemsSize();
}
Ar.Logf(TEXT("Total memory allocated for and by Object Hash: %u bytes."), HashtableAllocatedSize);
}
void LogHashStatistics(FOutputDevice& Ar, const bool bShowHashBucketCollisionInfo)
{
Ar.Logf(TEXT("Hash efficiency statistics for the Object Hash"));
Ar.Logf(TEXT("-------------------------------------------------"));
Ar.Logf(TEXT(""));
FHashTableLock HashLock(FUObjectHashTables::Get());
LogHashStatisticsInternal(FUObjectHashTables::Get().Hash, Ar, bShowHashBucketCollisionInfo);
Ar.Logf(TEXT(""));
}
void LogHashOuterStatistics(FOutputDevice& Ar, const bool bShowHashBucketCollisionInfo)
{
Ar.Logf(TEXT("Hash efficiency statistics for the Outer Object Hash"));
Ar.Logf(TEXT("-------------------------------------------------"));
Ar.Logf(TEXT(""));
FHashTableLock HashLock(FUObjectHashTables::Get());
LogHashStatisticsInternal(FUObjectHashTables::Get().HashOuter, Ar, bShowHashBucketCollisionInfo);
Ar.Logf(TEXT(""));
uint32 HashOuterMapSize = 0;
for (TPair<UObjectBase*, FHashBucket>& OuterMapEntry: FUObjectHashTables::Get().ObjectOuterMap)
{
HashOuterMapSize += OuterMapEntry.Value.GetItemsSize();
}
Ar.Logf(TEXT("Total memory allocated for Object Outer Map: %u bytes."), HashOuterMapSize);
Ar.Logf(TEXT(""));
}
void LogHashMemoryOverheadStatistics(FOutputDevice& Ar, const bool bShowIndividualStats)
{
Ar.Logf(TEXT("UObject Hash Tables and Maps memory overhead"));
Ar.Logf(TEXT("-------------------------------------------------"));
FUObjectHashTables& HashTables = FUObjectHashTables::Get();
FHashTableLock HashLock(HashTables);
int64 TotalSize = 0;
{
int64 Size = HashTables.Hash.GetAllocatedSize();
for (const TPair<int32, FHashBucket>& Pair: HashTables.Hash)
{
Size += Pair.Value.GetItemsSize();
}
if (bShowIndividualStats)
{
Ar.Logf(TEXT("Memory used by UObject Hash: %lld bytes."), Size);
}
TotalSize += Size;
}
{
int64 Size = HashTables.HashOuter.GetAllocatedSize();
if (bShowIndividualStats)
{
Ar.Logf(TEXT("Memory used by UObject Outer Hash: %lld bytes."), Size);
}
TotalSize += Size;
}
{
int64 Size = HashTables.ObjectOuterMap.GetAllocatedSize();
for (const TPair<UObjectBase*, FHashBucket>& Pair: HashTables.ObjectOuterMap)
{
Size += Pair.Value.GetItemsSize();
}
if (bShowIndividualStats)
{
Ar.Logf(TEXT("Memory used by UObject Outer Map: %lld bytes."), Size);
}
TotalSize += Size;
}
{
int64 Size = HashTables.ClassToObjectListMap.GetAllocatedSize();
for (const TPair<UClass*, FHashBucket>& Pair: HashTables.ClassToObjectListMap)
{
Size += Pair.Value.GetItemsSize();
}
if (bShowIndividualStats)
{
Ar.Logf(TEXT("Memory used by UClass To UObject List Map: %lld bytes."), Size);
}
TotalSize += Size;
}
{
int64 Size = HashTables.ClassToChildListMap.GetAllocatedSize();
for (const TPair<UClass*, TSet<UClass*>>& Pair: HashTables.ClassToChildListMap)
{
Size += Pair.Value.GetAllocatedSize();
}
if (bShowIndividualStats)
{
Ar.Logf(TEXT("Memory used by UClass To Child UClass List Map: %lld bytes."), Size);
}
TotalSize += Size;
}
{
int64 Size = HashTables.PackageToObjectListMap.GetAllocatedSize();
for (const TPair<UPackage*, FHashBucket>& Pair: HashTables.PackageToObjectListMap)
{
Size += Pair.Value.GetItemsSize();
}
if (bShowIndividualStats)
{
Ar.Logf(TEXT("Memory used by UPackage To UObject List Map: %lld bytes."), Size);
}
TotalSize += Size;
}
{
int64 Size = HashTables.ObjectToPackageMap.GetAllocatedSize();
if (bShowIndividualStats)
{
Ar.Logf(TEXT("Memory used by UObject To External Package Map: %lld bytes."), Size);
}
TotalSize += Size;
}
{
int64 Size = GUObjectArray.GetAllocatedSize();
if (bShowIndividualStats)
{
Ar.Logf(TEXT("Memory used by UObjectArray: %lld bytes."), Size);
}
TotalSize += Size;
}
Ar.Logf(TEXT("Total memory allocated by Object hash tables and maps: %lld bytes (%.2f MB)."), TotalSize, (double)TotalSize / 1024.0 / 1024.0);
Ar.Logf(TEXT(""));
}