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

1398 lines
51 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
UObjectBase.cpp: Unreal UObject base class
=============================================================================*/
#include "UObject/UObjectBase.h"
#include "Misc/MessageDialog.h"
#include "Misc/ConfigCacheIni.h"
#include "HAL/IConsoleManager.h"
#include "HAL/LowLevelMemTracker.h"
#include "Misc/FeedbackContext.h"
#include "Modules/ModuleManager.h"
#include "UObject/UObjectAllocator.h"
#include "UObject/UObjectHash.h"
#include "UObject/Class.h"
#include "UObject/UObjectIterator.h"
#include "UObject/Package.h"
#include "Templates/Casts.h"
#include "UObject/GCObject.h"
#include "UObject/LinkerLoad.h"
#include "Misc/CommandLine.h"
#include "Interfaces/IPluginManager.h"
#include "Serialization/LoadTimeTrace.h"
DEFINE_LOG_CATEGORY_STATIC(LogUObjectBase, Log, All);
DEFINE_STAT(STAT_UObjectsStatGroupTester);
DECLARE_CYCLE_STAT(TEXT("CreateStatID"), STAT_CreateStatID, STATGROUP_StatSystem);
DEFINE_LOG_CATEGORY_STATIC(LogUObjectBootstrap, Display, Display);
/** Whether uobject system is initialized. */
namespace Internal
{
static bool& GetUObjectSubsystemInitialised()
{
static bool ObjInitialized = false;
return ObjInitialized;
}
}; // namespace Internal
bool UObjectInitialized()
{
return Internal::GetUObjectSubsystemInitialised();
}
/** Objects to automatically register once the object system is ready. */
struct FPendingRegistrantInfo
{
const TCHAR* Name;
const TCHAR* PackageName;
FPendingRegistrantInfo(const TCHAR* InName, const TCHAR* InPackageName)
: Name(InName), PackageName(InPackageName)
{}
static TMap<UObjectBase*, FPendingRegistrantInfo>& GetMap()
{
static TMap<UObjectBase*, FPendingRegistrantInfo> PendingRegistrantInfo;
return PendingRegistrantInfo;
}
};
/** Objects to automatically register once the object system is ready. */
struct FPendingRegistrant
{
UObjectBase* Object;
FPendingRegistrant* NextAutoRegister;
FPendingRegistrant(UObjectBase* InObject)
: Object(InObject), NextAutoRegister(NULL)
{}
};
static FPendingRegistrant* GFirstPendingRegistrant = NULL;
static FPendingRegistrant* GLastPendingRegistrant = NULL;
#if USE_PER_MODULE_UOBJECT_BOOTSTRAP
static TMap<FName, TArray<FPendingRegistrant*>>& GetPerModuleBootstrapMap()
{
static TMap<FName, TArray<FPendingRegistrant*>> PendingRegistrantInfo;
return PendingRegistrantInfo;
}
#endif
/**
* Constructor used for bootstrapping
* @param InClass possibly NULL, this gives the class of the new object, if known at this time
* @param InFlags RF_Flags to assign
*/
UObjectBase::UObjectBase(EObjectFlags InFlags)
: ObjectFlags(InFlags), InternalIndex(INDEX_NONE), ClassPrivate(nullptr), OuterPrivate(nullptr)
{}
/**
* Constructor used by StaticAllocateObject
* @param InClass non NULL, this gives the class of the new object, if known at this time
* @param InFlags RF_Flags to assign
* @param InOuter outer for this object
* @param InName name of the new object
* @param InObjectArchetype archetype to assign
*/
UObjectBase::UObjectBase(UClass* InClass, EObjectFlags InFlags, EInternalObjectFlags InInternalFlags, UObject* InOuter, FName InName)
: ObjectFlags(InFlags), InternalIndex(INDEX_NONE), ClassPrivate(InClass), OuterPrivate(InOuter)
{
check(ClassPrivate);
// Add to global table.
AddObject(InName, InInternalFlags);
}
/**
* Final destructor, removes the object from the object array, and indirectly, from any annotations
**/
UObjectBase::~UObjectBase()
{
// If not initialized, skip out.
if (UObjectInitialized() && ClassPrivate && !GIsCriticalError)
{
// Validate it.
check(IsValidLowLevel());
check(GetFName() == NAME_None);
GUObjectArray.FreeUObjectIndex(this);
}
}
/**
* Convert a boot-strap registered class into a real one, add to uobject array, etc
*
* @param UClassStaticClass Now that it is known, fill in UClass::StaticClass() as the class
*/
void UObjectBase::DeferredRegister(UClass* UClassStaticClass, const TCHAR* PackageName, const TCHAR* InName)
{
check(UObjectInitialized());
// Set object properties.
UPackage* Package = CreatePackage(PackageName);
check(Package);
Package->SetPackageFlags(PKG_CompiledIn);
OuterPrivate = Package;
check(UClassStaticClass);
check(!ClassPrivate);
ClassPrivate = UClassStaticClass;
// Add to the global object table.
AddObject(FName(InName), EInternalObjectFlags::None);
// At this point all compiled-in objects should have already been fully constructed so it's safe to remove the NotFullyConstructed flag
// which was set in FUObjectArray::AllocateUObjectIndex (called from AddObject)
GUObjectArray.IndexToObject(InternalIndex)->ClearFlags(EInternalObjectFlags::PendingConstruction);
// Make sure that objects disregarded for GC are part of root set.
check(!GUObjectArray.IsDisregardForGC(this) || GUObjectArray.IndexToObject(InternalIndex)->IsRootSet());
UE_LOG(LogUObjectBootstrap, Verbose, TEXT("UObjectBase::DeferredRegister %s %s"), PackageName, InName);
}
/**
* Add a newly created object to the name hash tables and the object array
*
* @param Name name to assign to this uobject
*/
void UObjectBase::AddObject(FName InName, EInternalObjectFlags InSetInternalFlags)
{
NamePrivate = InName;
EInternalObjectFlags InternalFlagsToSet = InSetInternalFlags;
if (!IsInGameThread())
{
InternalFlagsToSet |= EInternalObjectFlags::Async;
}
if (ObjectFlags & RF_MarkAsRootSet)
{
InternalFlagsToSet |= EInternalObjectFlags::RootSet;
ObjectFlags &= ~RF_MarkAsRootSet;
}
if (ObjectFlags & RF_MarkAsNative)
{
InternalFlagsToSet |= EInternalObjectFlags::Native;
ObjectFlags &= ~RF_MarkAsNative;
}
GUObjectArray.AllocateUObjectIndex(this);
check(InName != NAME_None && InternalIndex >= 0);
if (InternalFlagsToSet != EInternalObjectFlags::None)
{
GUObjectArray.IndexToObject(InternalIndex)->SetFlags(InternalFlagsToSet);
}
HashObject(this);
check(IsValidLowLevel());
}
/**
* Just change the FName and Outer and rehash into name hash tables. For use by higher level rename functions.
*
* @param NewName new name for this object
* @param NewOuter new outer for this object, if NULL, outer will be unchanged
*/
void UObjectBase::LowLevelRename(FName NewName, UObject* NewOuter)
{
#if STATS || ENABLE_STATNAMEDEVENTS_UOBJECT
((UObject*)this)->ResetStatID(); // reset the stat id since this thing now has a different name
#endif
UnhashObject(this);
check(InternalIndex >= 0);
NamePrivate = NewName;
if (NewOuter)
{
OuterPrivate = NewOuter;
}
HashObject(this);
}
UPackage* UObjectBase::GetExternalPackage() const
{
// if we have no outer, consider this a package, packages returns themselves as their external package
if (OuterPrivate == nullptr)
{
return CastChecked<UPackage>((UObject*)(this));
}
UPackage* ExternalPackage = nullptr;
if ((GetFlags() & RF_HasExternalPackage) != 0)
{
ExternalPackage = GetObjectExternalPackageThreadSafe(this);
// if the flag is set there should be an override set.
ensure(ExternalPackage);
}
return ExternalPackage;
}
UPackage* UObjectBase::GetExternalPackageInternal() const
{
// if we have no outer, consider this a package, packages returns themselves as their external package
if (OuterPrivate == nullptr)
{
return CastChecked<UPackage>((UObject*)(this));
}
return (GetFlags() & RF_HasExternalPackage) != 0 ? GetObjectExternalPackageInternal(this) : nullptr;
}
void UObjectBase::SetExternalPackage(UPackage* InPackage)
{
HashObjectExternalPackage(this, InPackage);
if (InPackage)
{
SetFlagsTo(GetFlags() | RF_HasExternalPackage);
}
else
{
SetFlagsTo(GetFlags() & ~RF_HasExternalPackage);
}
}
void UObjectBase::SetClass(UClass* NewClass)
{
#if STATS || ENABLE_STATNAMEDEVENTS_UOBJECT
((UObject*)this)->ResetStatID(); // reset the stat id since this thing now has a different name
#endif
UnhashObject(this);
#if USE_UBER_GRAPH_PERSISTENT_FRAME
UClass* OldClass = ClassPrivate;
ClassPrivate->DestroyPersistentUberGraphFrame((UObject*)this);
#endif
ClassPrivate = NewClass;
#if USE_UBER_GRAPH_PERSISTENT_FRAME
ClassPrivate->CreatePersistentUberGraphFrame((UObject*)this, /*bCreateOnlyIfEmpty =*/false, /*bSkipSuperClass =*/false, OldClass);
#endif
HashObject(this);
}
/**
* Checks to see if the object appears to be valid
* @return true if this appears to be a valid object
*/
bool UObjectBase::IsValidLowLevel() const
{
if (this == nullptr)
{
UE_LOG(LogUObjectBase, Warning, TEXT("NULL object"));
return false;
}
if (!ClassPrivate)
{
UE_LOG(LogUObjectBase, Warning, TEXT("Object is not registered"));
return false;
}
return GUObjectArray.IsValid(this);
}
bool UObjectBase::IsValidLowLevelFast(bool bRecursive /*= true*/) const
{
// As DEFAULT_ALIGNMENT is defined to 0 now, I changed that to the original numerical value here
const int32 AlignmentCheck = MIN_ALIGNMENT - 1;
// Check 'this' pointer before trying to access any of the Object's members
if ((this == nullptr) || (UPTRINT)this < 0x100)
{
UE_LOG(LogUObjectBase, Error, TEXT("\'this\' pointer is invalid."));
return false;
}
if ((UPTRINT)this & AlignmentCheck)
{
UE_LOG(LogUObjectBase, Error, TEXT("\'this\' pointer is misaligned."));
return false;
}
if (*(void**)this == nullptr)
{
UE_LOG(LogUObjectBase, Error, TEXT("Virtual functions table is invalid."));
return false;
}
// These should all be 0.
const UPTRINT CheckZero = (ObjectFlags & ~RF_AllFlags) | ((UPTRINT)ClassPrivate & AlignmentCheck) | ((UPTRINT)OuterPrivate & AlignmentCheck);
if (!!CheckZero)
{
UE_LOG(LogUObjectBase, Error, TEXT("Object flags are invalid or either Class or Outer is misaligned"));
return false;
}
// These should all be non-NULL (except CDO-alignment check which should be 0)
if (ClassPrivate == nullptr || ClassPrivate->ClassDefaultObject == nullptr || ((UPTRINT)ClassPrivate->ClassDefaultObject & AlignmentCheck) != 0)
{
UE_LOG(LogUObjectBase, Error, TEXT("Class pointer is invalid or CDO is invalid."));
return false;
}
// Avoid infinite recursion so call IsValidLowLevelFast on the class object with bRecirsive = false.
if (bRecursive && !ClassPrivate->IsValidLowLevelFast(false))
{
UE_LOG(LogUObjectBase, Error, TEXT("Class object failed IsValidLowLevelFast test."));
return false;
}
// Lightweight versions of index checks.
if (!GUObjectArray.IsValidIndex(this) || !NamePrivate.IsValidIndexFast())
{
UE_LOG(LogUObjectBase, Error, TEXT("Object array index or name index is invalid."));
return false;
}
return true;
}
void UObjectBase::EmitBaseReferences(UClass* RootClass)
{
static const FName ClassPropertyName(TEXT("Class"));
static const FName OuterPropertyName(TEXT("Outer"));
// Mark UObject class reference as persistent object reference so that it (ClassPrivate) doesn't get nulled when a class
// is marked as pending kill. Nulling ClassPrivate may leave the object in a broken state if it doesn't get GC'd in the same
// GC call as its class. And even if it gets GC'd in the same call as its class it may break inside of GC (for example when traversing TMap references)
RootClass->EmitObjectReference(STRUCT_OFFSET(UObjectBase, ClassPrivate), ClassPropertyName, GCRT_PersistentObject);
RootClass->EmitObjectReference(STRUCT_OFFSET(UObjectBase, OuterPrivate), OuterPropertyName, GCRT_PersistentObject);
RootClass->EmitExternalPackageReference();
}
#if USE_PER_MODULE_UOBJECT_BOOTSTRAP
static void UObjectReleaseModuleRegistrants(FName Module)
{
TMap<FName, TArray<FPendingRegistrant*>>& PerModuleMap = GetPerModuleBootstrapMap();
FName Package = IPluginManager::Get().PackageNameFromModuleName(Module);
FName ScriptName = *(FString(TEXT("/Script/")) + Package.ToString());
TArray<FPendingRegistrant*>* Array = PerModuleMap.Find(ScriptName);
if (Array)
{
SCOPED_BOOT_TIMING("UObjectReleaseModuleRegistrants");
for (FPendingRegistrant* PendingRegistration: *Array)
{
if (GLastPendingRegistrant)
{
GLastPendingRegistrant->NextAutoRegister = PendingRegistration;
}
else
{
check(!GFirstPendingRegistrant);
GFirstPendingRegistrant = PendingRegistration;
}
GLastPendingRegistrant = PendingRegistration;
}
UE_LOG(LogUObjectBootstrap, Verbose, TEXT("UObjectReleaseModuleRegistrants %d items in %s"), Array->Num(), *ScriptName.ToString());
PerModuleMap.Remove(ScriptName);
}
else
{
UE_LOG(LogUObjectBootstrap, Verbose, TEXT("UObjectReleaseModuleRegistrants no items in %s"), *ScriptName.ToString());
}
}
void UObjectReleaseAllModuleRegistrants()
{
SCOPED_BOOT_TIMING("UObjectReleaseAllModuleRegistrants");
TMap<FName, TArray<FPendingRegistrant*>>& PerModuleMap = GetPerModuleBootstrapMap();
for (auto& Pair: PerModuleMap)
{
for (FPendingRegistrant* PendingRegistration: Pair.Value)
{
if (GLastPendingRegistrant)
{
GLastPendingRegistrant->NextAutoRegister = PendingRegistration;
}
else
{
check(!GFirstPendingRegistrant);
GFirstPendingRegistrant = PendingRegistration;
}
GLastPendingRegistrant = PendingRegistration;
}
UE_LOG(LogUObjectBootstrap, Verbose, TEXT("UObjectReleaseAllModuleRegistrants %d items in %s"), Pair.Value.Num(), *Pair.Key.ToString());
}
PerModuleMap.Empty();
ProcessNewlyLoadedUObjects();
}
static void DumpPendingUObjectModules(const TArray<FString>& Args)
{
TMap<FName, TArray<FPendingRegistrant*>>& PerModuleMap = GetPerModuleBootstrapMap();
for (auto& Pair: PerModuleMap)
{
UE_LOG(LogUObjectBootstrap, Display, TEXT("Not yet loaded: %d items in %s"), Pair.Value.Num(), *Pair.Key.ToString());
}
}
static FAutoConsoleCommand DumpPendingUObjectModulesCmd(
TEXT("DumpPendingUObjectModules"),
TEXT("When doing per-module UObject bootstrapping, show the modules that are not yet loaded."),
FConsoleCommandWithArgsDelegate::CreateStatic(&DumpPendingUObjectModules));
#endif
/** Enqueue the registration for this object. */
void UObjectBase::Register(const TCHAR* PackageName, const TCHAR* InName)
{
TMap<UObjectBase*, FPendingRegistrantInfo>& PendingRegistrants = FPendingRegistrantInfo::GetMap();
FPendingRegistrant* PendingRegistration = new FPendingRegistrant(this);
PendingRegistrants.Add(this, FPendingRegistrantInfo(InName, PackageName));
#if USE_PER_MODULE_UOBJECT_BOOTSTRAP
if (FName(PackageName) != FName("/Script/CoreUObject"))
{
TMap<FName, TArray<FPendingRegistrant*>>& PerModuleMap = GetPerModuleBootstrapMap();
PerModuleMap.FindOrAdd(FName(PackageName)).Add(PendingRegistration);
}
else
#endif
{
if (GLastPendingRegistrant)
{
GLastPendingRegistrant->NextAutoRegister = PendingRegistration;
}
else
{
check(!GFirstPendingRegistrant);
GFirstPendingRegistrant = PendingRegistration;
}
GLastPendingRegistrant = PendingRegistration;
}
}
/**
* Dequeues registrants from the list of pending registrations into an array.
* The contents of the array is preserved, and the new elements are appended.
*/
static void DequeuePendingAutoRegistrants(TArray<FPendingRegistrant>& OutPendingRegistrants)
{
// We process registrations in the order they were enqueued, since each registrant ensures
// its dependencies are enqueued before it enqueues itself.
FPendingRegistrant* NextPendingRegistrant = GFirstPendingRegistrant;
GFirstPendingRegistrant = NULL;
GLastPendingRegistrant = NULL;
while (NextPendingRegistrant)
{
FPendingRegistrant* PendingRegistrant = NextPendingRegistrant;
OutPendingRegistrants.Add(*PendingRegistrant);
NextPendingRegistrant = PendingRegistrant->NextAutoRegister;
delete PendingRegistrant;
};
}
/**
* Process the auto register objects adding them to the UObject array
*/
static void UObjectProcessRegistrants()
{
SCOPED_BOOT_TIMING("UObjectProcessRegistrants");
check(UObjectInitialized());
// Make list of all objects to be registered.
TArray<FPendingRegistrant> PendingRegistrants;
DequeuePendingAutoRegistrants(PendingRegistrants);
for (int32 RegistrantIndex = 0; RegistrantIndex < PendingRegistrants.Num(); ++RegistrantIndex)
{
const FPendingRegistrant& PendingRegistrant = PendingRegistrants[RegistrantIndex];
UObjectForceRegistration(PendingRegistrant.Object, false);
check(PendingRegistrant.Object->GetClass()); // should have been set by DeferredRegister
// Register may have resulted in new pending registrants being enqueued, so dequeue those.
DequeuePendingAutoRegistrants(PendingRegistrants);
}
}
void UObjectForceRegistration(UObjectBase* Object, bool bCheckForModuleRelease)
{
TMap<UObjectBase*, FPendingRegistrantInfo>& PendingRegistrants = FPendingRegistrantInfo::GetMap();
FPendingRegistrantInfo* Info = PendingRegistrants.Find(Object);
if (Info)
{
const TCHAR* PackageName = Info->PackageName;
#if USE_PER_MODULE_UOBJECT_BOOTSTRAP
if (bCheckForModuleRelease)
{
UObjectReleaseModuleRegistrants(FName(PackageName));
}
#endif
const TCHAR* Name = Info->Name;
PendingRegistrants.Remove(Object); // delete this first so that it doesn't try to do it twice
Object->DeferredRegister(UClass::StaticClass(), PackageName, Name);
}
}
/**
* Struct containing the function pointer and package name of a UStruct to be registered with UObject system
*/
struct FPendingStructRegistrant
{
class UScriptStruct* (*RegisterFn)();
const TCHAR* PackageName;
FPendingStructRegistrant() {}
FPendingStructRegistrant(class UScriptStruct* (*Fn)(), const TCHAR* InPackageName)
: RegisterFn(Fn), PackageName(InPackageName)
{
}
FORCEINLINE bool operator==(const FPendingStructRegistrant& Other) const
{
return RegisterFn == Other.RegisterFn;
}
};
static TArray<FPendingStructRegistrant>& GetDeferredCompiledInStructRegistration()
{
static TArray<FPendingStructRegistrant> DeferredCompiledInRegistration;
return DeferredCompiledInRegistration;
}
TMap<FName, UScriptStruct* (*)()>& GetDynamicStructMap()
{
static TMap<FName, UScriptStruct* (*)()> DynamicStructMap;
return DynamicStructMap;
}
void UObjectCompiledInDeferStruct(class UScriptStruct* (*InRegister)(), const TCHAR* PackageName, const TCHAR* ObjectName, bool bDynamic, const TCHAR* DynamicPathName)
{
if (!bDynamic)
{
// we do reregister StaticStruct in hot reload
FPendingStructRegistrant Registrant(InRegister, PackageName);
checkSlow(!GetDeferredCompiledInStructRegistration().Contains(Registrant));
GetDeferredCompiledInStructRegistration().Add(Registrant);
}
else
{
GetDynamicStructMap().Add(DynamicPathName, InRegister);
}
NotifyRegistrationEvent(PackageName, ObjectName, ENotifyRegistrationType::NRT_Struct, ENotifyRegistrationPhase::NRP_Added, (UObject * (*)())(InRegister), bDynamic);
}
class UScriptStruct* GetStaticStruct(class UScriptStruct* (*InRegister)(), UObject* StructOuter, const TCHAR* StructName, SIZE_T Size, uint32 Crc)
{
NotifyRegistrationEvent(*StructOuter->GetOutermost()->GetName(), StructName, ENotifyRegistrationType::NRT_Struct, ENotifyRegistrationPhase::NRP_Started);
UScriptStruct* Result = (*InRegister)();
NotifyRegistrationEvent(*StructOuter->GetOutermost()->GetName(), StructName, ENotifyRegistrationType::NRT_Struct, ENotifyRegistrationPhase::NRP_Finished);
return Result;
}
/**
* Struct containing the function pointer and package name of a UEnum to be registered with UObject system
*/
struct FPendingEnumRegistrant
{
class UEnum* (*RegisterFn)();
const TCHAR* PackageName;
FPendingEnumRegistrant() {}
FPendingEnumRegistrant(class UEnum* (*Fn)(), const TCHAR* InPackageName)
: RegisterFn(Fn), PackageName(InPackageName)
{
}
FORCEINLINE bool operator==(const FPendingEnumRegistrant& Other) const
{
return RegisterFn == Other.RegisterFn;
}
};
// Same thing as GetDeferredCompiledInStructRegistration but for UEnums declared in header files without UClasses.
static TArray<FPendingEnumRegistrant>& GetDeferredCompiledInEnumRegistration()
{
static TArray<FPendingEnumRegistrant> DeferredCompiledInRegistration;
return DeferredCompiledInRegistration;
}
TMap<FName, UEnum* (*)()>& GetDynamicEnumMap()
{
static TMap<FName, UEnum* (*)()> DynamicEnumMap;
return DynamicEnumMap;
}
void UObjectCompiledInDeferEnum(class UEnum* (*InRegister)(), const TCHAR* PackageName, const TCHAR* ObjectName, bool bDynamic, const TCHAR* DynamicPathName)
{
if (!bDynamic)
{
// we do reregister StaticStruct in hot reload
FPendingEnumRegistrant Registrant(InRegister, PackageName);
checkSlow(!GetDeferredCompiledInEnumRegistration().Contains(Registrant));
GetDeferredCompiledInEnumRegistration().Add(Registrant);
}
else
{
GetDynamicEnumMap().Add(DynamicPathName, InRegister);
}
NotifyRegistrationEvent(PackageName, ObjectName, ENotifyRegistrationType::NRT_Enum, ENotifyRegistrationPhase::NRP_Added, (UObject * (*)())(InRegister), bDynamic);
}
class UEnum* GetStaticEnum(class UEnum* (*InRegister)(), UObject* EnumOuter, const TCHAR* EnumName)
{
NotifyRegistrationEvent(*EnumOuter->GetOutermost()->GetName(), EnumName, ENotifyRegistrationType::NRT_Enum, ENotifyRegistrationPhase::NRP_Started);
UEnum* Result = (*InRegister)();
NotifyRegistrationEvent(*EnumOuter->GetOutermost()->GetName(), EnumName, ENotifyRegistrationType::NRT_Enum, ENotifyRegistrationPhase::NRP_Finished);
return Result;
}
static TArray<class UClass* (*)()>& GetDeferredCompiledInRegistration()
{
static TArray<class UClass* (*)()> DeferredCompiledInRegistration;
return DeferredCompiledInRegistration;
}
/** Classes loaded with a module, deferred until we register them all in one go */
static TArray<FFieldCompiledInInfo*>& GetDeferredClassRegistration()
{
static TArray<FFieldCompiledInInfo*> DeferredClassRegistration;
return DeferredClassRegistration;
}
#if WITH_HOT_RELOAD
/** Map of deferred class registration info (including size and reflection info) */
static TMap<FName, FFieldCompiledInInfo*>& GetDeferRegisterClassMap()
{
static TMap<FName, FFieldCompiledInInfo*> DeferRegisterClassMap;
return DeferRegisterClassMap;
}
/** Classes that changed during hot-reload and need to be re-instanced */
static TArray<FFieldCompiledInInfo*>& GetHotReloadClasses()
{
static TArray<FFieldCompiledInInfo*> HotReloadClasses;
return HotReloadClasses;
}
#endif
/** Removes prefix from the native class name */
FString UObjectBase::RemoveClassPrefix(const TCHAR* ClassName)
{
static const TCHAR* DeprecatedPrefix = TEXT("DEPRECATED_");
FString NameWithoutPrefix(ClassName);
NameWithoutPrefix.MidInline(1, MAX_int32, false);
if (NameWithoutPrefix.StartsWith(DeprecatedPrefix))
{
NameWithoutPrefix.MidInline(FCString::Strlen(DeprecatedPrefix), MAX_int32, false);
}
return NameWithoutPrefix;
}
void UClassCompiledInDefer(FFieldCompiledInInfo* ClassInfo, const TCHAR* Name, SIZE_T ClassSize, uint32 Crc)
{
const FName CPPClassName = Name;
#if WITH_HOT_RELOAD
// Check for existing classes
TMap<FName, FFieldCompiledInInfo*>& DeferMap = GetDeferRegisterClassMap();
FFieldCompiledInInfo** ExistingClassInfo = DeferMap.Find(CPPClassName);
ClassInfo->bHasChanged = !ExistingClassInfo || (*ExistingClassInfo)->Size != ClassInfo->Size || (*ExistingClassInfo)->Crc != ClassInfo->Crc;
if (ExistingClassInfo)
{
// Class exists, this can only happen during hot-reload
checkf(GIsHotReload, TEXT("Trying to recreate class '%s' outside of hot reload!"), *CPPClassName.ToString());
// Get the native name
FString NameWithoutPrefix = UObjectBase::RemoveClassPrefix(Name);
UClass* ExistingClass = FindObjectChecked<UClass>(ANY_PACKAGE, *NameWithoutPrefix);
if (ClassInfo->bHasChanged)
{
// Rename the old class and move it to transient package
ExistingClass->RemoveFromRoot();
ExistingClass->ClearFlags(RF_Standalone | RF_Public);
ExistingClass->GetDefaultObject()->RemoveFromRoot();
ExistingClass->GetDefaultObject()->ClearFlags(RF_Standalone | RF_Public);
const FName OldClassRename = MakeUniqueObjectName(GetTransientPackage(), ExistingClass->GetClass(), *FString::Printf(TEXT("HOTRELOADED_%s"), *NameWithoutPrefix));
ExistingClass->Rename(*OldClassRename.ToString(), GetTransientPackage());
ExistingClass->SetFlags(RF_Transient);
ExistingClass->AddToRoot();
// Make sure enums de-register their names BEFORE we create the new class, otherwise there will be name conflicts
TArray<UObject*> ClassSubobjects;
GetObjectsWithOuter(ExistingClass, ClassSubobjects);
for (auto ClassSubobject: ClassSubobjects)
{
if (auto Enum = dynamic_cast<UEnum*>(ClassSubobject))
{
Enum->RemoveNamesFromMasterList();
}
}
}
ClassInfo->OldClass = ExistingClass;
GetHotReloadClasses().Add(ClassInfo);
*ExistingClassInfo = ClassInfo;
}
else
{
DeferMap.Add(CPPClassName, ClassInfo);
}
#endif
// We will either create a new class or update the static class pointer of the existing one
GetDeferredClassRegistration().Add(ClassInfo);
}
TMap<FName, FDynamicClassStaticData>& GetDynamicClassMap()
{
static TMap<FName, FDynamicClassStaticData> DynamicClassMap;
return DynamicClassMap;
}
void UObjectCompiledInDefer(UClass* (*InRegister)(), UClass* (*InStaticClass)(), const TCHAR* Name, const TCHAR* PackageName, bool bDynamic, const TCHAR* DynamicPathName, void (*InInitSearchableValues)(TMap<FName, FName>&))
{
if (!bDynamic)
{
#if WITH_HOT_RELOAD
// Either add all classes if not hot-reloading, or those which have changed
TMap<FName, FFieldCompiledInInfo*>& DeferMap = GetDeferRegisterClassMap();
if (!GIsHotReload || DeferMap.FindChecked(Name)->bHasChanged)
#endif
{
FString NoPrefix(UObjectBase::RemoveClassPrefix(Name));
NotifyRegistrationEvent(PackageName, *NoPrefix, ENotifyRegistrationType::NRT_Class, ENotifyRegistrationPhase::NRP_Added, (UObject * (*)())(InRegister), false);
NotifyRegistrationEvent(PackageName, *(FString(DEFAULT_OBJECT_PREFIX) + NoPrefix), ENotifyRegistrationType::NRT_ClassCDO, ENotifyRegistrationPhase::NRP_Added, (UObject * (*)())(InRegister), false);
TArray<UClass* (*)()>& DeferredCompiledInRegistration = GetDeferredCompiledInRegistration();
checkSlow(!DeferredCompiledInRegistration.Contains(InRegister));
DeferredCompiledInRegistration.Add(InRegister);
}
}
else
{
FDynamicClassStaticData ClassFunctions;
ClassFunctions.ZConstructFn = InRegister;
ClassFunctions.StaticClassFn = InStaticClass;
if (InInitSearchableValues)
{
InInitSearchableValues(ClassFunctions.SelectedSearchableValues);
}
GetDynamicClassMap().Add(FName(DynamicPathName), ClassFunctions);
FString OriginalPackageName = DynamicPathName;
check(OriginalPackageName.EndsWith(Name));
OriginalPackageName.RemoveFromEnd(FString(Name));
check(OriginalPackageName.EndsWith(TEXT(".")));
OriginalPackageName.RemoveFromEnd(FString(TEXT(".")));
NotifyRegistrationEvent(*OriginalPackageName, Name, ENotifyRegistrationType::NRT_Class, ENotifyRegistrationPhase::NRP_Added, (UObject * (*)())(InRegister), true);
NotifyRegistrationEvent(*OriginalPackageName, *(FString(DEFAULT_OBJECT_PREFIX) + Name), ENotifyRegistrationType::NRT_ClassCDO, ENotifyRegistrationPhase::NRP_Added, (UObject * (*)())(InRegister), true);
}
}
/** Register all loaded classes */
void UClassRegisterAllCompiledInClasses()
{
#if WITH_HOT_RELOAD
TArray<UClass*> AddedClasses;
#endif
SCOPED_BOOT_TIMING("UClassRegisterAllCompiledInClasses");
TArray<FFieldCompiledInInfo*>& DeferredClassRegistration = GetDeferredClassRegistration();
for (const FFieldCompiledInInfo* Class: DeferredClassRegistration)
{
UClass* RegisteredClass = Class->Register();
#if WITH_HOT_RELOAD
if (GIsHotReload && Class->OldClass == nullptr)
{
AddedClasses.Add(RegisteredClass);
}
#endif
}
DeferredClassRegistration.Empty();
#if WITH_HOT_RELOAD
if (AddedClasses.Num() > 0)
{
FCoreUObjectDelegates::RegisterHotReloadAddedClassesDelegate.Broadcast(AddedClasses);
}
#endif
}
#if WITH_HOT_RELOAD
/** Re-instance all existing classes that have changed during hot-reload */
void UClassReplaceHotReloadClasses()
{
TArray<FFieldCompiledInInfo*>& HotReloadClasses = GetHotReloadClasses();
if (FCoreUObjectDelegates::RegisterClassForHotReloadReinstancingDelegate.IsBound())
{
for (const FFieldCompiledInInfo* Class: HotReloadClasses)
{
check(Class->OldClass);
UClass* RegisteredClass = nullptr;
if (Class->bHasChanged)
{
RegisteredClass = Class->Register();
}
FCoreUObjectDelegates::RegisterClassForHotReloadReinstancingDelegate.Broadcast(Class->OldClass, RegisteredClass, Class->bHasChanged ? EHotReloadedClassFlags::Changed : EHotReloadedClassFlags::None);
}
}
FCoreUObjectDelegates::ReinstanceHotReloadedClassesDelegate.Broadcast();
HotReloadClasses.Empty();
}
#endif
/**
* Load any outstanding compiled in default properties
*/
static void UObjectLoadAllCompiledInDefaultProperties()
{
TRACE_LOADTIME_REQUEST_GROUP_SCOPE(TEXT("UObjectLoadAllCompiledInDefaultProperties"));
static FName LongEnginePackageName(TEXT("/Script/Engine"));
TArray<UClass* (*)()>& DeferredCompiledInRegistration = GetDeferredCompiledInRegistration();
const bool bHaveRegistrants = DeferredCompiledInRegistration.Num() != 0;
if (bHaveRegistrants)
{
SCOPED_BOOT_TIMING("UObjectLoadAllCompiledInDefaultProperties");
TArray<UClass*> NewClasses;
TArray<UClass*> NewClassesInCoreUObject;
TArray<UClass*> NewClassesInEngine;
TArray<UClass* (*)()> PendingRegistrants = MoveTemp(DeferredCompiledInRegistration);
for (UClass* (*Registrant)(): PendingRegistrants)
{
UClass* Class = Registrant();
UE_LOG(LogUObjectBootstrap, Verbose, TEXT("UObjectLoadAllCompiledInDefaultProperties After Registrant %s %s"), *Class->GetOutermost()->GetName(), *Class->GetName());
if (Class->GetOutermost()->GetFName() == GLongCoreUObjectPackageName)
{
NewClassesInCoreUObject.Add(Class);
}
else if (Class->GetOutermost()->GetFName() == LongEnginePackageName)
{
NewClassesInEngine.Add(Class);
}
else
{
NewClasses.Add(Class);
}
}
{
SCOPED_BOOT_TIMING("CoreUObject Classes");
for (UClass* Class: NewClassesInCoreUObject) // we do these first because we assume these never trigger loads
{
UE_LOG(LogUObjectBootstrap, Verbose, TEXT("GetDefaultObject Begin %s %s"), *Class->GetOutermost()->GetName(), *Class->GetName());
Class->GetDefaultObject();
UE_LOG(LogUObjectBootstrap, Verbose, TEXT("GetDefaultObject End %s %s"), *Class->GetOutermost()->GetName(), *Class->GetName());
}
}
{
SCOPED_BOOT_TIMING("Engine Classes");
for (UClass* Class: NewClassesInEngine) // we do these second because we want to bring the engine up before the game
{
UE_LOG(LogUObjectBootstrap, Verbose, TEXT("GetDefaultObject Begin %s %s"), *Class->GetOutermost()->GetName(), *Class->GetName());
Class->GetDefaultObject();
UE_LOG(LogUObjectBootstrap, Verbose, TEXT("GetDefaultObject End %s %s"), *Class->GetOutermost()->GetName(), *Class->GetName());
}
}
{
SCOPED_BOOT_TIMING("Other Classes");
for (UClass* Class: NewClasses)
{
UE_LOG(LogUObjectBootstrap, Verbose, TEXT("GetDefaultObject Begin %s %s"), *Class->GetOutermost()->GetName(), *Class->GetName());
Class->GetDefaultObject();
UE_LOG(LogUObjectBootstrap, Verbose, TEXT("GetDefaultObject End %s %s"), *Class->GetOutermost()->GetName(), *Class->GetName());
}
}
FFeedbackContext& ErrorsFC = UClass::GetDefaultPropertiesFeedbackContext();
if (ErrorsFC.GetNumErrors() || ErrorsFC.GetNumWarnings())
{
TArray<FString> AllErrorsAndWarnings;
ErrorsFC.GetErrorsAndWarningsAndEmpty(AllErrorsAndWarnings);
FString AllInOne;
UE_LOG(LogUObjectBase, Warning, TEXT("-------------- Default Property warnings and errors:"));
for (const FString& ErrorOrWarning: AllErrorsAndWarnings)
{
UE_LOG(LogUObjectBase, Warning, TEXT("%s"), *ErrorOrWarning);
AllInOne += ErrorOrWarning;
AllInOne += TEXT("\n");
}
FMessageDialog::Open(EAppMsgType::Ok, FText::Format(NSLOCTEXT("Core", "DefaultPropertyWarningAndErrors", "Default Property warnings and errors:\n{0}"), FText::FromString(AllInOne)));
}
}
}
/**
* Call StaticStruct for each struct...this sets up the internal singleton, and important works correctly with hot reload
*/
static void UObjectLoadAllCompiledInStructs()
{
SCOPED_BOOT_TIMING("UObjectLoadAllCompiledInStructs");
TArray<FPendingEnumRegistrant> PendingEnumRegistrants = MoveTemp(GetDeferredCompiledInEnumRegistration());
TArray<FPendingStructRegistrant> PendingStructRegistrants = MoveTemp(GetDeferredCompiledInStructRegistration());
{
SCOPED_BOOT_TIMING("UObjectLoadAllCompiledInStructs - CreatePackages (could be optimized!)");
// Load Enums first
for (const FPendingEnumRegistrant& EnumRegistrant: PendingEnumRegistrants)
{
// Make sure the package exists in case it does not contain any UObjects
CreatePackage(EnumRegistrant.PackageName);
}
for (const FPendingStructRegistrant& StructRegistrant: PendingStructRegistrants)
{
// Make sure the package exists in case it does not contain any UObjects or UEnums
CreatePackage(StructRegistrant.PackageName);
}
}
// Load Structs
for (const FPendingEnumRegistrant& EnumRegistrant: PendingEnumRegistrants)
{
EnumRegistrant.RegisterFn();
}
for (const FPendingStructRegistrant& StructRegistrant: PendingStructRegistrants)
{
StructRegistrant.RegisterFn();
}
}
void ProcessNewlyLoadedUObjects(FName Package, bool bCanProcessNewlyLoadedObjects)
{
SCOPED_BOOT_TIMING("ProcessNewlyLoadedUObjects");
#if USE_PER_MODULE_UOBJECT_BOOTSTRAP
if (Package != NAME_None)
{
UObjectReleaseModuleRegistrants(Package);
}
#endif
if (!bCanProcessNewlyLoadedObjects)
{
return;
}
LLM_SCOPE(ELLMTag::UObject);
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("ProcessNewlyLoadedUObjects"), STAT_ProcessNewlyLoadedUObjects, STATGROUP_ObjectVerbose);
UClassRegisterAllCompiledInClasses();
const TArray<UClass* (*)()>& DeferredCompiledInRegistration = GetDeferredCompiledInRegistration();
const TArray<FPendingStructRegistrant>& DeferredCompiledInStructRegistration = GetDeferredCompiledInStructRegistration();
const TArray<FPendingEnumRegistrant>& DeferredCompiledInEnumRegistration = GetDeferredCompiledInEnumRegistration();
bool bNewUObjects = false;
while (GFirstPendingRegistrant || DeferredCompiledInRegistration.Num() || DeferredCompiledInStructRegistration.Num() || DeferredCompiledInEnumRegistration.Num())
{
bNewUObjects = true;
UObjectProcessRegistrants();
UObjectLoadAllCompiledInStructs();
FCoreUObjectDelegates::CompiledInUObjectsRegisteredDelegate.Broadcast(Package);
UObjectLoadAllCompiledInDefaultProperties();
}
#if WITH_HOT_RELOAD
UClassReplaceHotReloadClasses();
#endif
if (bNewUObjects && !GIsInitialLoad)
{
UClass::AssembleReferenceTokenStreams();
}
}
static int32 GVarMaxObjectsNotConsideredByGC;
static FAutoConsoleVariableRef CMaxObjectsNotConsideredByGC(
TEXT("gc.MaxObjectsNotConsideredByGC"),
GVarMaxObjectsNotConsideredByGC,
TEXT("Placeholder console variable, currently not used in runtime."),
ECVF_Default);
static int32 GSizeOfPermanentObjectPool;
static FAutoConsoleVariableRef CSizeOfPermanentObjectPool(
TEXT("gc.SizeOfPermanentObjectPool"),
GSizeOfPermanentObjectPool,
TEXT("Placeholder console variable, currently not used in runtime."),
ECVF_Default);
static int32 GMaxObjectsInEditor;
static FAutoConsoleVariableRef CMaxObjectsInEditor(
TEXT("gc.MaxObjectsInEditor"),
GMaxObjectsInEditor,
TEXT("Placeholder console variable, currently not used in runtime."),
ECVF_Default);
static int32 GMaxObjectsInGame;
static FAutoConsoleVariableRef CMaxObjectsInGame(
TEXT("gc.MaxObjectsInGame"),
GMaxObjectsInGame,
TEXT("Placeholder console variable, currently not used in runtime."),
ECVF_Default);
/**
* Final phase of UObject initialization. all auto register objects are added to the main data structures.
*/
void UObjectBaseInit()
{
SCOPED_BOOT_TIMING("UObjectBaseInit");
// Zero initialize and later on get value from .ini so it is overridable per game/ platform...
int32 MaxObjectsNotConsideredByGC = 0;
int32 SizeOfPermanentObjectPool = 0;
int32 MaxUObjects = 2 * 1024 * 1024; // Default to ~2M UObjects
bool bPreAllocateUObjectArray = false;
// To properly set MaxObjectsNotConsideredByGC look for "Log: XXX objects as part of root set at end of initial load."
// in your log file. This is being logged from LaunchEnglineLoop after objects have been added to the root set.
// Disregard for GC relies on seekfree loading for interaction with linkers. We also don't want to use it in the Editor, for which
// FPlatformProperties::RequiresCookedData() will be false. Please note that GIsEditor and FApp::IsGame() are not valid at this point.
if (FPlatformProperties::RequiresCookedData())
{
FString Value;
bool bIsCookOnTheFly = FParse::Value(FCommandLine::Get(), TEXT("-filehostip="), Value);
if (bIsCookOnTheFly)
{
GCreateGCClusters = false;
}
else
{
GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.MaxObjectsNotConsideredByGC"), MaxObjectsNotConsideredByGC, GEngineIni);
// Not used on PC as in-place creation inside bigger pool interacts with the exit purge and deleting UObject directly.
GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.SizeOfPermanentObjectPool"), SizeOfPermanentObjectPool, GEngineIni);
}
// Maximum number of UObjects in cooked game
GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.MaxObjectsInGame"), MaxUObjects, GEngineIni);
// If true, the UObjectArray will pre-allocate all entries for UObject pointers
GConfig->GetBool(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.PreAllocateUObjectArray"), bPreAllocateUObjectArray, GEngineIni);
}
else
{
#if IS_PROGRAM
// Maximum number of UObjects for programs can be low
MaxUObjects = 300000; // Default to 100K for programs
GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.MaxObjectsInProgram"), MaxUObjects, GEngineIni);
#else
// Maximum number of UObjects in the editor
GConfig->GetInt(TEXT("/Script/Engine.GarbageCollectionSettings"), TEXT("gc.MaxObjectsInEditor"), MaxUObjects, GEngineIni);
#endif
}
if (MaxObjectsNotConsideredByGC <= 0 && SizeOfPermanentObjectPool > 0)
{
// If permanent object pool is enabled but disregard for GC is disabled, GC will mark permanent object pool objects
// as unreachable and may destroy them so disable permanent object pool too.
// An alternative would be to make GC not mark permanent object pool objects as unreachable but then they would have to
// be considered as root set objects because they could be referencing objects from outside of permanent object pool.
// This would be inconsistent and confusing and also counter productive (the more root set objects the more expensive MarkAsUnreachable phase is).
SizeOfPermanentObjectPool = 0;
UE_LOG(LogInit, Warning, TEXT("Disabling permanent object pool because disregard for GC is disabled (gc.MaxObjectsNotConsideredByGC=%d)."), MaxObjectsNotConsideredByGC);
}
// Log what we're doing to track down what really happens as log in LaunchEngineLoop doesn't report those settings in pristine form.
UE_LOG(LogInit, Log, TEXT("%s for max %d objects, including %i objects not considered by GC, pre-allocating %i bytes for permanent pool."),
bPreAllocateUObjectArray ? TEXT("Pre-allocating") : TEXT("Presizing"),
MaxUObjects, MaxObjectsNotConsideredByGC, SizeOfPermanentObjectPool);
GUObjectAllocator.AllocatePermanentObjectPool(SizeOfPermanentObjectPool);
GUObjectArray.AllocateObjectPool(MaxUObjects, MaxObjectsNotConsideredByGC, bPreAllocateUObjectArray);
void InitAsyncThread();
InitAsyncThread();
// Note initialized.
Internal::GetUObjectSubsystemInitialised() = true;
UObjectProcessRegistrants();
}
/**
* Final phase of UObject shutdown
*/
void UObjectBaseShutdown()
{
void ShutdownAsyncThread();
ShutdownAsyncThread();
GUObjectArray.ShutdownUObjectArray();
Internal::GetUObjectSubsystemInitialised() = false;
}
/**
* Helper function that can be used inside the debuggers watch window. E.g. "DebugFName(Class)".
*
* @param Object Object to look up the name for
* @return Associated name
*/
const TCHAR* DebugFName(UObject* Object)
{
if (Object)
{
// Hardcoded static array. This function is only used inside the debugger so it should be fine to return it.
static TCHAR TempName[256];
FName Name = Object->GetFName();
FCString::Strcpy(TempName, *FName::SafeString(Name.GetDisplayIndex(), Name.GetNumber()));
return TempName;
}
else
{
return TEXT("NULL");
}
}
/**
* Helper function that can be used inside the debuggers watch window. E.g. "DebugFName(Object)".
*
* @param Object Object to look up the name for
* @return Fully qualified path name
*/
const TCHAR* DebugPathName(UObject* Object)
{
if (Object)
{
// Hardcoded static array. This function is only used inside the debugger so it should be fine to return it.
static TCHAR PathName[1024];
PathName[0] = 0;
// Keep track of how many outers we have as we need to print them in inverse order.
UObject* TempObject = Object;
int32 OuterCount = 0;
while (TempObject)
{
TempObject = TempObject->GetOuter();
OuterCount++;
}
// Iterate over each outer + self in reverse oder and append name.
for (int32 OuterIndex = OuterCount - 1; OuterIndex >= 0; OuterIndex--)
{
// Move to outer name.
TempObject = Object;
for (int32 i = 0; i < OuterIndex; i++)
{
TempObject = TempObject->GetOuter();
}
// Dot separate entries.
if (OuterIndex != OuterCount - 1)
{
FCString::Strcat(PathName, TEXT("."));
}
// And app end the name.
FCString::Strcat(PathName, DebugFName(TempObject));
}
return PathName;
}
else
{
return TEXT("None");
}
}
/**
* Helper function that can be used inside the debuggers watch window. E.g. "DebugFName(Object)".
*
* @param Object Object to look up the name for
* @return Fully qualified path name prepended by class name
*/
const TCHAR* DebugFullName(UObject* Object)
{
if (Object)
{
// Hardcoded static array. This function is only used inside the debugger so it should be fine to return it.
static TCHAR FullName[1024];
FullName[0] = 0;
// Class Full.Path.Name
FCString::Strcat(FullName, DebugFName(Object->GetClass()));
FCString::Strcat(FullName, TEXT(" "));
FCString::Strcat(FullName, DebugPathName(Object));
return FullName;
}
else
{
return TEXT("None");
}
}
#if WITH_HOT_RELOAD
namespace
{
struct FObjectCompiledInfo
{
/** Registered struct info (including size and reflection info) */
static TMap<TTuple<UObject*, FName>, FObjectCompiledInfo>& GetRegisteredInfo()
{
static TMap<TTuple<UObject*, FName>, FObjectCompiledInfo> StructOrEnumCompiledInfoMap;
return StructOrEnumCompiledInfoMap;
}
FObjectCompiledInfo(SIZE_T InClassSize, uint32 InCrc)
: Size(InClassSize), Crc(InCrc)
{
}
SIZE_T Size;
uint32 Crc;
};
template <typename TType>
TType* FindExistingObjectIfHotReload(UObject* Outer, const TCHAR* Name, SIZE_T Size, uint32 Crc)
{
TTuple<UObject*, FName> Key(Outer, Name);
bool bChanged = true;
if (FObjectCompiledInfo* Info = FObjectCompiledInfo::GetRegisteredInfo().Find(Key))
{
// Hot-reloaded struct
bChanged = Info->Size != Size || Info->Crc != Crc;
Info->Size = Size;
Info->Crc = Crc;
}
else
{
// New struct
FObjectCompiledInfo::GetRegisteredInfo().Add(Key, FObjectCompiledInfo(Size, Crc));
}
if (!GIsHotReload)
{
return nullptr;
}
TType* Existing = FindObject<TType>(Outer, Name);
if (!Existing)
{
// New type added during hot-reload
UE_LOG(LogClass, Log, TEXT("Could not find existing type %s for HotReload. Assuming new"), Name);
return nullptr;
}
// Existing type, make sure we destroy the old one if it has changed
if (bChanged)
{
// Make sure the old struct is not used by anything
Existing->ClearFlags(RF_Standalone | RF_Public);
Existing->RemoveFromRoot();
const FName OldRename = MakeUniqueObjectName(GetTransientPackage(), Existing->GetClass(), *FString::Printf(TEXT("HOTRELOADED_%s"), Name));
Existing->Rename(*OldRename.ToString(), GetTransientPackage());
return nullptr;
}
UE_LOG(LogClass, Log, TEXT("%s HotReload."), Name);
return Existing;
}
} // namespace
#endif // WITH_HOT_RELOAD
UScriptStruct* FindExistingStructIfHotReloadOrDynamic(UObject* Outer, const TCHAR* StructName, SIZE_T Size, uint32 Crc, bool bIsDynamic)
{
#if WITH_HOT_RELOAD
UScriptStruct* Result = FindExistingObjectIfHotReload<UScriptStruct>(Outer, StructName, Size, Crc);
#else
UScriptStruct* Result = nullptr;
#endif
if (!Result && bIsDynamic)
{
Result = Cast<UScriptStruct>(StaticFindObjectFast(UScriptStruct::StaticClass(), Outer, StructName));
}
return Result;
}
UEnum* FindExistingEnumIfHotReloadOrDynamic(UObject* Outer, const TCHAR* EnumName, SIZE_T Size, uint32 Crc, bool bIsDynamic)
{
#if WITH_HOT_RELOAD
UEnum* Result = FindExistingObjectIfHotReload<UEnum>(Outer, EnumName, Size, Crc);
#else
UEnum* Result = nullptr;
#endif
if (!Result && bIsDynamic)
{
Result = Cast<UEnum>(StaticFindObjectFast(UEnum::StaticClass(), Outer, EnumName));
}
return Result;
}
UObject* ConstructDynamicType(FName TypePathName, EConstructDynamicType ConstructionSpecifier)
{
UObject* Result = nullptr;
if (FDynamicClassStaticData* ClassConstructFn = GetDynamicClassMap().Find(TypePathName))
{
if (ConstructionSpecifier == EConstructDynamicType::CallZConstructor)
{
UClass* DynamicClass = ClassConstructFn->ZConstructFn();
check(DynamicClass);
DynamicClass->AssembleReferenceTokenStream();
Result = DynamicClass;
}
else if (ConstructionSpecifier == EConstructDynamicType::OnlyAllocateClassObject)
{
Result = ClassConstructFn->StaticClassFn();
check(Result);
}
}
else if (UScriptStruct * (**StaticStructFNPtr)() = GetDynamicStructMap().Find(TypePathName))
{
Result = (*StaticStructFNPtr)();
}
else if (UEnum * (**StaticEnumFNPtr)() = GetDynamicEnumMap().Find(TypePathName))
{
Result = (*StaticEnumFNPtr)();
}
return Result;
}
FName GetDynamicTypeClassName(FName TypePathName)
{
FName Result = NAME_None;
if (GetDynamicClassMap().Find(TypePathName))
{
Result = UDynamicClass::StaticClass()->GetFName();
}
else if (GetDynamicStructMap().Find(TypePathName))
{
Result = UScriptStruct::StaticClass()->GetFName();
}
else if (GetDynamicEnumMap().Find(TypePathName))
{
Result = UEnum::StaticClass()->GetFName();
}
if (false && Result == NAME_None)
{
UE_LOG(LogUObjectBase, Warning, TEXT("GetDynamicTypeClassName %s not found."), *TypePathName.ToString());
UE_LOG(LogUObjectBase, Warning, TEXT("---- classes"));
for (auto& Pair: GetDynamicClassMap())
{
UE_LOG(LogUObjectBase, Warning, TEXT(" %s"), *Pair.Key.ToString());
}
UE_LOG(LogUObjectBase, Warning, TEXT("---- structs"));
for (auto& Pair: GetDynamicStructMap())
{
UE_LOG(LogUObjectBase, Warning, TEXT(" %s"), *Pair.Key.ToString());
}
UE_LOG(LogUObjectBase, Warning, TEXT("---- enums"));
for (auto& Pair: GetDynamicEnumMap())
{
UE_LOG(LogUObjectBase, Warning, TEXT(" %s"), *Pair.Key.ToString());
}
UE_LOG(LogUObjectBase, Fatal, TEXT("GetDynamicTypeClassName %s not found."), *TypePathName.ToString());
}
UE_CLOG(Result == NAME_None, LogUObjectBase, Warning, TEXT("GetDynamicTypeClassName %s not found."), *TypePathName.ToString());
return Result;
}
UPackage* FindOrConstructDynamicTypePackage(const TCHAR* PackageName)
{
UPackage* Package = Cast<UPackage>(StaticFindObjectFast(UPackage::StaticClass(), nullptr, PackageName));
if (!Package)
{
Package = CreatePackage(PackageName);
if (!GEventDrivenLoaderEnabled)
{
Package->SetPackageFlags(PKG_CompiledIn);
}
}
check(Package);
return Package;
}
TMap<FName, FName>& GetConvertedDynamicPackageNameToTypeName()
{
static TMap<FName, FName> ConvertedDynamicPackageNameToTypeName;
return ConvertedDynamicPackageNameToTypeName;
}