
AppInit 단계에서는 Unreal Engine 내부의 여러 시스템에 초기화 단계임을 알리기 위해 멀티캐스트 델리게이트가 브로드캐스트됩니다.
bool FEngineLoop::AppInit()
// Init other systems.
{
SCOPED_BOOT_TIMING("FCoreDelegates::OnInit.Broadcast");
FCoreDelegates::OnInit.Broadcast();
}
특히 중요한 점은, 이 브로드캐스트가 CoreUObject의 Startup Module에서 바인딩된 InitUObject 함수를 호출한다는 것입니다.
void InitUObject()
{
LLM_SCOPE(ELLMTag::InitUObject);
FGCCSyncObject::Create();
// 리다이렉트 맵 초기화
FCoreRedirects::Initialize();
for (const FString& Filename : GConfig->GetFilenames())
{
CoreRedirects::ReadRedirectsFromIni(Filename);
FLinkerLoad::CreateActiveRedirectsMap(Filename);
}
FCoreDelegates::OnShutdownAfterError.AddStatic(StaticShutdownAfterError);
FCoreDelegates::OnExit.AddStatic(StaticExit);
#if !USE_PER_MODULE_UOBJECT_BOOTSTRAP && !IS_MONOLITHIC
// USE_PER_MODULE_UOBJECT_BOOTSTRAP==0일 때는 콜백이 다른 곳에서 설치되므로 필요하지 않음
// 모놀리식 빌드에서는 모든 보류 중인 등록자가 앱 시작 시 한 번에 처리되므로
// ProcessNewlyLoadedUObjects는 각 모듈마다 호출될 필요 없이 한 번만 호출됨
FModuleManager::Get().OnProcessLoadedObjectsCallback().AddStatic(ProcessNewlyLoadedUObjects);
#endif
struct Local
{
static bool IsPackageLoaded( FName PackageName )
{
return FindPackage( NULL, *PackageName.ToString() ) != NULL;
}
};
FModuleManager::Get().IsPackageLoadedCallback().BindStatic(Local::IsPackageLoaded);
FCoreDelegates::NewFileAddedDelegate.AddStatic(FLinkerLoad::OnNewFileAdded);
FCoreDelegates::GetOnPakFileMounted2().AddStatic(FLinkerLoad::OnPakFileMounted);
// 오브젝트 초기화
StaticUObjectInit();
}
ProcessNewlyLoadedObjects는 OnProcessLoadedObjectsCallback에 바인딩되어 있습니다.
#if !USE_PER_MODULE_UOBJECT_BOOTSTRAP && !IS_MONOLITHIC
// USE_PER_MODULE_UOBJECT_BOOTSTRAP==0일 때는 콜백이 다른 곳에서 설치되므로 필요하지 않음
// 모놀리식 빌드에서는 모든 보류 중인 등록자가 앱 시작 시 한 번에 처리되므로
// ProcessNewlyLoadedUObjects는 각 모듈마다 호출될 필요 없이 한 번만 호출됨
FModuleManager::Get().OnProcessLoadedObjectsCallback().AddStatic(ProcessNewlyLoadedUObjects);
#endif
이 콜백은 Module Manager 내부에 정의되어 있습니다.
DECLARE_EVENT_TwoParams(FModuleManager, ProcessLoadedObjectsEvent, FName, bool);
ProcessLoadedObjectsEvent& OnProcessLoadedObjectsCallback()
{
return ProcessLoadedObjectsCallback;
}
다이어그램을 따라가다 보면, ProcessLoadedObjects가 왜 두 번 호출되는지 궁금할 수 있습니다. CoreUObject의 경우, 한 번은 StartupModule에서, 두 번째는 ProcessNewlyLoadedObjects에서 호출됩니다. 아래 코드 스니펫은 AppInit()과 ProcessNewlyLoadedObjects 콜백 설정 과정을 보여줍니다. 이 이벤트는 새로운 모듈이 로드된 후 DLL의 네이티브 클래스에 대한 타입 오브젝트를 생성하는 역할을 합니다.
다음 다이어그램 단계에 따라 StaticUObjectInit()에 도달했습니다.
void StaticUObjectInit() {
UObjectBaseInit();
// Allocate special packages.
GObjTransientPkg = NewObject<UPackage>(nullptr, TEXT("/Engine/Transient"), RF_Transient);
GObjTransientPkg->AddToRoot();
if (IConsoleVariable* CVarVerifyGCAssumptions = IConsoleManager::Get().FindConsoleVariable(TEXT("gc.VerifyAssumptions"))) {
if (FParse::Param(FCommandLine::Get(), TEXT("VERIFYGC"))) {
CVarVerifyGCAssumptions->Set(true, ECVF_SetByCommandline);
}
if (FParse::Param(FCommandLine::Get(), TEXT("NOVERIFYGC"))) {
CVarVerifyGCAssumptions->Set(false, ECVF_SetByCommandline);
}
}
UE_LOG(LogInit, Log, TEXT("Object subsystem initialized"));
}
StaticUObjectInit 함수는 UObjectBaseInit을 호출합니다. 초기화가 완료되면, NewObject를 사용해 특수한 임시 패키지(transient package)를 생성할 수 있습니다. GObjTransientPkg는 Outer가 없는 모든 오브젝트를 위한 전역 변수로, 이러한 임시 패키지에 할당됩니다. 이는 모든 UObject가 반드시 하나의 UPackage에 포함되어야 한다는 요구사항을 충족합니다. 또한, 임시 패키지에 오브젝트를 루트에 추가(add to root)하면 해당 오브젝트가 가비지 컬렉션 대상에서 제외되어 삭제되지 않도록 보장합니다.
void UObjectBaseInit(){
int32 MaxObjectsNotConsideredByGC = 0;
int32 SizeOfPermanentObjectPool = 0;
int32 MaxUObjects = 2 * 1024 * 1024; // 기본값: 약 2백만 개의 UObject
bool bPreAllocateUObjectArray = false;
....
GUObjectAllocator.AllocatePermanentObjectPool(SizeOfPermanentObjectPool);
GUObjectArray.AllocateObjectPool(MaxUObjects, MaxObjectsNotConsideredByGC, bPreAllocateUObjectArray);
UObjectProcessRegistrants();
이 코드는 UObject 초기화의 마지막 단계를 나타내며, 자동 등록(auto-registration)된 오브젝트들을 주요 데이터 구조에 포함시킵니다. 이 함수는 퍼머넌트 오브젝트 풀(permanent object pool)에 대한 메모리를 할당할 수 있는데, 이는 쿠킹된 빌드(cooked build)나 GC가 적용되지 않는 UObject에 사용됩니다. 이 시점에서 오브젝트 해시 시스템이 구축되고, GConfig 값을 읽어 가비지 컬렉션(GC) 동작을 설정합니다. 또한, 최대 허용 UObject 개수를 지정하여 UObject 풀의 크기를 결정합니다. 이후 패키지 로딩을 위한 비동기 로딩 스레드가 시작되며, 이는 AsyncPackageLoader.cpp에서 관리됩니다.
static void UObjectProcessRegistrants()
{
SCOPED_BOOT_TIMING("UObjectProcessRegistrants");
check(UObjectInitialized());
// 등록할 모든 오브젝트의 리스트를 만듭니다.
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()); // DeferredRegister에서 설정되어야 함
// Register 과정에서 새로운 pending registrant가 추가될 수 있으므로, 다시 dequeue합니다.
DequeuePendingAutoRegistrants(PendingRegistrants);
}
}
UClassRegisterAllCompiledInClasses는 전역 Pending Registrant 링크 리스트에 등록자를 enqueue합니다. UObjectProcessRegistrants는 이 링크 리스트를 순회하며, DequeuePendingAutoRegistrants를 통해 등록자들을 배열로 추출합니다. 이 dequeue 과정이 두 번 발생하는 이유는, UObject가 등록될 때(CDO 생성이나 패키지 로딩 등) 다른 모듈에서 참조될 수 있기 때문입니다. 이로 인해 또 다른 모듈이 로드되면서 추가적인 pending registrant가 링크 리스트에 추가될 수 있습니다. 이러한 추가 등록자들도 배열로 추출되어 등록이 진행됩니다.
static void DequeuePendingAutoRegistrants(TArray<FPendingRegistrant>& OutPendingRegistrants)
{
// 등록은 enqueue된 순서대로 처리합니다. 각 registrant는 자신의 의존성을 먼저 enqueue한 후 자신을 enqueue합니다.
FPendingRegistrant* NextPendingRegistrant = GFirstPendingRegistrant;
GFirstPendingRegistrant = NULL;
GLastPendingRegistrant = NULL;
while(NextPendingRegistrant)
{
FPendingRegistrant* PendingRegistrant = NextPendingRegistrant;
OutPendingRegistrants.Add(*PendingRegistrant);
NextPendingRegistrant = PendingRegistrant->NextAutoRegister;
delete PendingRegistrant;
};
}
void UObjectForceRegistration(UObjectBase* Object, bool bCheckForModuleRelease)
{
LLM_SCOPE(ELLMTag::UObject);
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);
}
}
함수 UObjectForceRegistration은 등록이 필요한 오브젝트에 대해 지연 등록(Deferred Registration)을 수행하고, 해당 오브젝트를 보류 중인 등록자(Pending Registrant) 목록에서 제거하는 역할을 합니다. 또한, UObjectForceRegistration은 두 가지 추가적인 상황에서 호출될 수 있습니다.
CreateDefaultSubObject
// Class.cpp
if ( ParentClass != NULL )
{
UObjectForceRegistration(ParentClass);
ParentDefaultObject = ParentClass->GetDefaultObject(); // 기본 오브젝트가 아직 생성되지 않았다면 강제로 생성
check(GConfig);
if (GEventDrivenLoaderEnabled && EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME)
{
check(ParentDefaultObject && !ParentDefaultObject->HasAnyFlags(RF_NeedLoad));
}
}
이 코드는 CDO가 자신과 연결된 기반 클래스가 등록되어 있는지 보장하기 위해 호출됩니다.
ConstructUClass
// UObjectGlobals.cpp
if ( ParentClass != NULL )
{
UObjectForceRegistration(ParentClass);
ParentDefaultObject = ParentClass->GetDefaultObject(); // 기본 오브젝트가 아직 생성되지 않았다면 강제로 생성
check(GConfig);
if (GEventDrivenLoaderEnabled && EVENT_DRIVEN_ASYNC_LOAD_ACTIVE_AT_RUNTIME)
{
check(ParentDefaultObject && !ParentDefaultObject->HasAnyFlags(RF_NeedLoad));
}
}
....
NewClass 오브젝트는 UObjectForceRegistration에 전달되어 해당 클래스 타입이 등록되었는지 확인합니다. 본질적으로, UObjectForceRegistration에 오브젝트를 전달하면 그 오브젝트가 아직 pending registrants 리스트에 남아 있는지 검사할 수 있습니다.
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);
}
이 단계는 오브젝트가 실제로 등록되는 시점입니다. 여기서 deferred registration과 UObjectBase::Register()의 차이를 명확히 이해하는 것이 중요합니다. 지연 등록(deferred registration) 단계에서는 타입 시스템을 위한 GUObjectAllocator와 GUObjectArray가 아직 준비되지 않았으므로, 이 시점에서는 NewObject나 Package를 할당하거나 생성할 수 없습니다. 하지만 UObject 시스템에서 정의된 함수들은 사용할 수 있습니다.
타입 시스템이 할당된 이후, 새로 로드된 모듈들은 UObjectBase::Register()를 호출하여 UObject 타입에 대한 새로운 메모리를 할당하고, 이를 전역 UObject 배열 맵에 추가합니다.
지연 등록 단계에서는 필요한 모든 초기화가 완료되어야 하며, 패키지를 생성할 수 있습니다. 패키지가 생성되면 UClass의 Outer로 할당하고, ClassPrivate를 설정하며, 추가적으로 AddObject를 호출해 내부 플래그를 설정하고, 오브젝트를 해싱하며, NamePrivate를 할당합니다.
이 단계에서는 UClass 오브젝트의 프로퍼티와 함수가 아직 설정되지 않았다는 점에 유의해야 합니다.
여기까지의 주요 단계는 다음과 같습니다:
ProcessNewlyLoadedUObjects 콜백이 설정됩니다.UObjectProcessRegistrants에 있는 항목에 대해 패키지를 생성할 수 있으며, 이때 OuterPrivate와 NamePrivate를 할당한 뒤 전역 오브젝트 배열에 추가할 수 있습니다.아직 UClass에 프로퍼티와 함수가 할당되지는 않았지만, OuterPrivate, ClassPrivate, SuperStruct, NamePrivate의 메모리 레이아웃은 아래와 같이 이미 설정되어 있습니다.

SuperStruct는 UClass 객체의 타입 트리에서 상속 관계를 결정하는 핵심 역할을 합니다. 각 타입 간의 관계는 ClassPrivate 속성에 의해 정의됩니다.

UClass::StaticClass()가 호출되면 UClass 객체가 생성되고, 이때 ClassPrivate는 자기 자신을 가리킵니다.UClass를 통해 관리됩니다.UPackage를 Outer로 가지며, 이를 통해 의존성 트리가 형성됩니다. 이는 액터의 소유자(AActor의 Owner)와는 구분해야 합니다.UObjectBase의 메모리 레이아웃에서 관계를 정의하는 네 가지 주요 변수는 다음과 같습니다.
모든 UPackage 객체의 경우, 오브젝트의 Outer는 보통 UPackage이며, 이를 통해 의존성 트리가 형성됩니다. 이는 액터의 소유자(AActor의 Owner)와는 다르다는 점에 유의해야 합니다.
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);
FPackageDeferredRegistry& PackageRegistry = FPackageDeferredRegistry::Get();
FClassDeferredRegistry& ClassRegistry = FClassDeferredRegistry::Get();
FStructDeferredRegistry& StructRegistry = FStructDeferredRegistry::Get();
FEnumDeferredRegistry& EnumRegistry = FEnumDeferredRegistry::Get();
PackageRegistry.ProcessChangedObjects(true);
StructRegistry.ProcessChangedObjects();
EnumRegistry.ProcessChangedObjects();
UClassRegisterAllCompiledInClasses();
bool bNewUObjects = false;
TArray<UClass*> AllNewClasses;
while (GFirstPendingRegistrant ||
ClassRegistry.HasPendingRegistrations() ||
StructRegistry.HasPendingRegistrations() ||
EnumRegistry.HasPendingRegistrations())
{
bNewUObjects = true;
UObjectProcessRegistrants();
UObjectLoadAllCompiledInStructs();
FCoreUObjectDelegates::CompiledInUObjectsRegisteredDelegate.Broadcast(Package);
UObjectLoadAllCompiledInDefaultProperties(AllNewClasses);
}
PackageRegistry.EmptyRegistrations();
EnumRegistry.EmptyRegistrations();
StructRegistry.EmptyRegistrations();
ClassRegistry.EmptyRegistrations();
if (TMap<UObjectBase*, FPendingRegistrantInfo>& PendingRegistrants = FPendingRegistrantInfo::GetMap(); PendingRegistrants.IsEmpty())
{
PendingRegistrants.Empty();
}
if (bNewUObjects && !GIsInitialLoad)
{
for (UClass* Class : AllNewClasses)
{
// Assemble reference token stream for garbage collection/ RTGC.
if (!Class->HasAnyFlags(RF_ClassDefaultObject) && !Class->HasAnyClassFlags(CLASS_TokenStreamAssembled))
{
Class->AssembleReferenceTokenStream();
}
}
}
}
Deferred Registry는 타입 정보의 정적 할당 단계에서 전역 배열로 채워집니다.UClassRegisterAllCompiledInClasses는 컴파일된 각 클래스에 대해 TClass::StaticClass()를 호출하여 UClass* 객체를 생성합니다.UObjectProcessRegistrants 호출을 통해 모든 관련 UClass* 객체가 메모리에 등록된 후 다음 단계로 진행됩니다.UObjectLoadAllCompiledInStructs는 enum과 struct에 대해 각각 UEnum과 UScriptStruct를 생성합니다.UObjectLoadAllCompiledDefaultProperties 단계에서는 UClass*의 CDO(CreateDefaultObject)가 생성됩니다.GIsInitialLoad 값으로 판단하며, GC(Garbage Collection)가 활성화된 이후에만 true가 됩니다. false라면 초기 로딩이 끝난 상태입니다. 이 시점에 UClass의 참조 토큰 스트림(객체 참조 분석에 사용되는 GC용 데이터 구조)도 처리됩니다.ProcessNewlyLoadedUObjects는 이후 모듈이 로드된 뒤에도 호출되므로, 클래스 계층 내 각 클래스에 대해 AssembleReferenceTokenStreams가 한 번만 호출되며, 중복 처리를 방지하는 플래그가 설정됩니다./**
* 각 struct에 대해 StaticStruct를 호출합니다.
* 이 함수는 내부 싱글턴을 설정하며, 핫 리로드(hot reload) 환경에서도 올바르게 동작합니다.
*/
static void UObjectLoadAllCompiledInStructs()
{
SCOPED_BOOT_TIMING("UObjectLoadAllCompiledInStructs");
FEnumDeferredRegistry& EnumRegistry = FEnumDeferredRegistry::Get();
FStructDeferredRegistry& StructRegistry = FStructDeferredRegistry::Get();
{
SCOPED_BOOT_TIMING("UObjectLoadAllCompiledInStructs - CreatePackages (could be optimized!)");
EnumRegistry.DoPendingPackageRegistrations();
StructRegistry.DoPendingPackageRegistrations();
}
// Struct 로드
EnumRegistry.DoPendingOuterRegistrations(true);
StructRegistry.DoPendingOuterRegistrations(true);
}
EnumRegistry와 StructRegistry의 getter가 다시 호출되어 UClass*가 올바르게 설정되었는지 확인합니다. 이후 Outer Package를 생성하는 몇 가지 단계가 진행됩니다.
CreatePackage가 호출됩니다. 이때 패키지 이름이 이미 존재하지 않는지 확인하여 중복 생성을 방지합니다.StaticStruct와 StaticEnum을 호출하여 OuterSingleton을 채웁니다.UScriptStruct를 생성할 때 Enum이 먼저 준비되어 있어야 struct 내부에 Enum이 포함될 수 있습니다.여기서 궁금할 수 있습니다. 만약 struct가 UClass 변수를 포함한다면 어떻게 될까요?
이 경우, 각 클래스에 속하는 UClass 오브젝트들은 이미 UObjectProcessRegistrants 단계에서 등록이 완료된 상태입니다. 즉, 모든 오브젝트 참조 타입은 단순히 클래스 이름을 통해 UClass를 찾기만 하면 됩니다. UClass가 아직 완전히 등록되지 않았더라도, 구조체가 완전히 구성될 필요는 없으며 이름만으로 참조가 가능합니다.
/**
* Load any outstanding compiled in default properties
*/
static void UObjectLoadAllCompiledInDefaultProperties(TArray<UClass*>& OutAllNewClasses)
{
TRACE_LOADTIME_REQUEST_GROUP_SCOPE(TEXT("UObjectLoadAllCompiledInDefaultProperties"));
static FName LongEnginePackageName(TEXT("/Script/Engine"));
FClassDeferredRegistry& ClassRegistry = FClassDeferredRegistry::Get();
if (ClassRegistry.HasPendingRegistrations())
{
SCOPED_BOOT_TIMING("UObjectLoadAllCompiledInDefaultProperties");
TArray<UClass*> NewClasses;
TArray<UClass*> NewClassesInCoreUObject;
TArray<UClass*> NewClassesInEngine;
ClassRegistry.DoPendingOuterRegistrations(true, [&OutAllNewClasses, &NewClasses, &NewClassesInCoreUObject, &NewClassesInEngine](const TCHAR* PackageName, UClass& Class) -> void
{
UE_LOG(LogUObjectBootstrap, Verbose, TEXT("UObjectLoadAllCompiledInDefaultProperties After Registrant %s %s"), PackageName, *Class.GetName());
if (Class.GetOutermost()->GetFName() == GLongCoreUObjectPackageName)
{
NewClassesInCoreUObject.Add(&Class);
}
else if (Class.GetOutermost()->GetFName() == LongEnginePackageName)
{
NewClassesInEngine.Add(&Class);
}
else
{
NewClasses.Add(&Class);
}
OutAllNewClasses.Add(&Class);
});
auto NotifyClassFinishedRegistrationEvents = [](TArray<UClass*>& Classes)
{
for (UClass* Class : Classes)
{
TCHAR PackageName[FName::StringBufferSize];
TCHAR ClassName[FName::StringBufferSize];
Class->GetOutermost()->GetFName().ToString(PackageName);
Class->GetFName().ToString(ClassName);
NotifyRegistrationEvent(PackageName, ClassName, ENotifyRegistrationType::NRT_Class, ENotifyRegistrationPhase::NRP_Finished, nullptr, false, Class);
}
};
// notify async loader of all new classes before creating the class default objects
{
SCOPED_BOOT_TIMING("NotifyClassFinishedRegistrationEvents");
NotifyClassFinishedRegistrationEvents(NewClassesInCoreUObject);
NotifyClassFinishedRegistrationEvents(NewClassesInEngine);
NotifyClassFinishedRegistrationEvents(NewClasses);
}
{
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)));
}
}
}
여기서는 UClass* 객체의 생성이 다음과 같은 단계로 이루어집니다.
ClassRegistry의 getter가 클래스 타입에 대해 정적으로 초기화된 전역 지연 등록 객체 배열에서 요소를 가져옵니다.DoPendingOuterRegistrations가 각 객체를 순회하며 Z_Construct_UClass 함수를 호출합니다. 람다 함수가 TArray 타입을 캡처해 OuterRegistrant 결과를 서브 배열에 분류하고, OutAllNewClasses를 채웁니다.CoreUObject가 가장 먼저, 그 다음 Engine, 마지막으로 기타 모듈 순입니다. CoreUObject는 하위 계층(다른 패키지 참조 없음), Engine은 두 번째(하위 오브젝트 참조 가능), 기타는 참조 관계가 불확실합니다. 이 순서는 의존성 역전 방지와 탐색 호출 최소화를 위해 필요합니다.UClass 타입에 대해 CDO가 생성됩니다.UObjectLoadAllCompiledInDefaultProperties는 UClass 객체 생성 이후 호출되며, 내부에 중첩된 UEnum* 또는 UScriptStruct 객체를 포함할 수 있습니다.