// 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& GetMap() { static TMap 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>& GetPerModuleBootstrapMap() { static TMap> 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((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((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>& PerModuleMap = GetPerModuleBootstrapMap(); FName Package = IPluginManager::Get().PackageNameFromModuleName(Module); FName ScriptName = *(FString(TEXT("/Script/")) + Package.ToString()); TArray* 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>& 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& Args) { TMap>& 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& 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>& 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& 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 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& 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& GetDeferredCompiledInStructRegistration() { static TArray DeferredCompiledInRegistration; return DeferredCompiledInRegistration; } TMap& GetDynamicStructMap() { static TMap 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& GetDeferredCompiledInEnumRegistration() { static TArray DeferredCompiledInRegistration; return DeferredCompiledInRegistration; } TMap& GetDynamicEnumMap() { static TMap 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& GetDeferredCompiledInRegistration() { static TArray DeferredCompiledInRegistration; return DeferredCompiledInRegistration; } /** Classes loaded with a module, deferred until we register them all in one go */ static TArray& GetDeferredClassRegistration() { static TArray DeferredClassRegistration; return DeferredClassRegistration; } #if WITH_HOT_RELOAD /** Map of deferred class registration info (including size and reflection info) */ static TMap& GetDeferRegisterClassMap() { static TMap DeferRegisterClassMap; return DeferRegisterClassMap; } /** Classes that changed during hot-reload and need to be re-instanced */ static TArray& GetHotReloadClasses() { static TArray 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& 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(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 ClassSubobjects; GetObjectsWithOuter(ExistingClass, ClassSubobjects); for (auto ClassSubobject: ClassSubobjects) { if (auto Enum = dynamic_cast(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& GetDynamicClassMap() { static TMap DynamicClassMap; return DynamicClassMap; } void UObjectCompiledInDefer(UClass* (*InRegister)(), UClass* (*InStaticClass)(), const TCHAR* Name, const TCHAR* PackageName, bool bDynamic, const TCHAR* DynamicPathName, void (*InInitSearchableValues)(TMap&)) { if (!bDynamic) { #if WITH_HOT_RELOAD // Either add all classes if not hot-reloading, or those which have changed TMap& 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& 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 AddedClasses; #endif SCOPED_BOOT_TIMING("UClassRegisterAllCompiledInClasses"); TArray& 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& 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& DeferredCompiledInRegistration = GetDeferredCompiledInRegistration(); const bool bHaveRegistrants = DeferredCompiledInRegistration.Num() != 0; if (bHaveRegistrants) { SCOPED_BOOT_TIMING("UObjectLoadAllCompiledInDefaultProperties"); TArray NewClasses; TArray NewClassesInCoreUObject; TArray NewClassesInEngine; TArray 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 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 PendingEnumRegistrants = MoveTemp(GetDeferredCompiledInEnumRegistration()); TArray 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& DeferredCompiledInRegistration = GetDeferredCompiledInRegistration(); const TArray& DeferredCompiledInStructRegistration = GetDeferredCompiledInStructRegistration(); const TArray& 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, FObjectCompiledInfo>& GetRegisteredInfo() { static TMap, FObjectCompiledInfo> StructOrEnumCompiledInfoMap; return StructOrEnumCompiledInfoMap; } FObjectCompiledInfo(SIZE_T InClassSize, uint32 InCrc) : Size(InClassSize), Crc(InCrc) { } SIZE_T Size; uint32 Crc; }; template TType* FindExistingObjectIfHotReload(UObject* Outer, const TCHAR* Name, SIZE_T Size, uint32 Crc) { TTuple 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(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(Outer, StructName, Size, Crc); #else UScriptStruct* Result = nullptr; #endif if (!Result && bIsDynamic) { Result = Cast(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(Outer, EnumName, Size, Crc); #else UEnum* Result = nullptr; #endif if (!Result && bIsDynamic) { Result = Cast(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(StaticFindObjectFast(UPackage::StaticClass(), nullptr, PackageName)); if (!Package) { Package = CreatePackage(PackageName); if (!GEventDrivenLoaderEnabled) { Package->SetPackageFlags(PKG_CompiledIn); } } check(Package); return Package; } TMap& GetConvertedDynamicPackageNameToTypeName() { static TMap ConvertedDynamicPackageNameToTypeName; return ConvertedDynamicPackageNameToTypeName; }