// 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 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* GetSet() { if (ElementsOrSetPtr[1] && !ElementsOrSetPtr[0]) { return (TSet*)ElementsOrSetPtr[1]; } return nullptr; } FORCEINLINE const TSet* GetSet() const { if (ElementsOrSetPtr[1] && !ElementsOrSetPtr[0]) { return (TSet*)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(Object)->GetFullName()); #endif // !UE_BUILD_SHIPPING TSet* Items = GetSet(); if (Items) { Items->Add(Object); } else if (ElementsOrSetPtr[0] && ElementsOrSetPtr[1]) { Items = new TSet(); 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(Object)->GetFullName()); #endif // !UE_BUILD_SHIPPING int32 Result = 0; TSet* Items = GetSet(); if (Items) { Result = Items->Remove(Object); if (Items->Num() <= 2) { auto It = TSet::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* 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* 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* Items = GetSet(); if (Items) { return (uint32)sizeof(*Items) + Items->GetAllocatedSize(); } return 0; } void Compact() { TSet* 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::TIterator GetIteratorForSet() { TSet* Items = GetSet(); return Items ? Items->CreateIterator() : EmptyBucket.CreateIterator(); } }; TSet FHashBucket::EmptyBucket; /** Hash Bucket Iterator. Iterates over all Objects in the bucket */ struct FHashBucketIterator { FHashBucket& Bucket; TSet::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 Hash; TMultiMap HashOuter; /** Map of object to their outers, used to avoid an object iterator to find such things. **/ TMap ObjectOuterMap; TMap ClassToObjectListMap; TMap> ClassToChildListMap; TAtomic ClassToChildListMapVersion; /** Map of package to the object their contain. */ TMap PackageToObjectListMap; /** Map of object to their external package. */ TMap 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> 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(*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::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(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(Object); if (ObjectWithUtility->IsA(UClass::StaticClass())) { UClass* Class = static_cast(ObjectWithUtility); UClass* SuperClass = Class->GetSuperClass(); if (SuperClass) { TSet& 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(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(ObjectWithUtility); UClass* SuperClass = Class->GetSuperClass(); if (SuperClass) { // Remove the class from the SuperClass' child list TSet& 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& 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& 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(*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(*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 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> 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(*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(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(*It); if (Object->HasAnyInternalFlags(ExclusionInternalFlags)) { continue; } if (ClassToLookFor && !Object->IsA(ClassToLookFor)) { continue; } Result = Object; break; } } } return Result; } void GetObjectsWithPackage(const class UPackage* Package, TArray& 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 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> 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(*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 static void RecursivelyPopulateDerivedClasses(FUObjectHashTables& ThreadHash, const UClass* ParentClass, TArray& 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* 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& 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 ClassesToLookFor, TFunctionRef 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(*ObjectIt); if (!Object->HasAnyFlags(ExcludeFlags) && !Object->HasAnyInternalFlags(ExclusionInternalFlags)) { Operation(Object); } } } } } void ForEachObjectOfClass(const UClass* ClassToLookFor, TFunctionRef Operation, bool bIncludeDerivedClasses, EObjectFlags ExclusionFlags, EInternalObjectFlags ExclusionInternalFlags) { // Most classes searched for have around 10 subclasses, some have hundreds TArray> 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 ClassesToLookFor, TFunctionRef 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& Results, bool bRecursive) { FUObjectHashTables& ThreadHash = FUObjectHashTables::Get(); FHashTableLock HashLock(ThreadHash); if (bRecursive) { RecursivelyPopulateDerivedClasses(ThreadHash, ClassToLookFor, Results); } else { TSet* DerivedClasses = ThreadHash.ClassToChildListMap.Find(ClassToLookFor); if (DerivedClasses) { Results.Append(DerivedClasses->Array()); } } } bool ClassHasInstancesAsyncLoading(const UClass* ClassToLookFor) { TArray 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(*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& Hash, FOutputDevice& Ar, const bool bShowHashBucketCollisionInfo) { TArray 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::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(Collisions, MaxCollisions); MinCollisions = FMath::Min(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::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& Hash, FOutputDevice& Ar, const bool bShowHashBucketCollisionInfo) { TArray 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(Collisions, MaxCollisions); MinCollisions = FMath::Min(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& 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& 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& 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& 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>& 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& 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("")); }