언리얼 프레임워크 구조 1
언리얼 엔진의 WinMain부터 EngineTick까지
언리얼엔진 프레임워크 구조 1
전체 흐름
언리얼엔진 전체 실행 흐름 정리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
WinMain
↓
GuardedMain
↓
EnginePreInit
↓
CoreUObject 로드
↓
UClass 등록
↓
CDO 생성
↓
생성자 호출
↓
EngineInit
↓
GEngine 생성
↓
EngineTick
↓
UWorld Tick
↓
Actor Tick
1. WinMain
언리얼 엔진의 최초 진입점
Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp
호출 구조
1
2
3
WinMain
└ LaunchWindowsStartup
└ GuardedMain
LaunchWindows.cpp
1
2
3
4
5
6
7
int32 WINAPI WinMain(_In_ HINSTANCE hInInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ char* pCmdLine, _In_ int32 nCmdShow)
{
int32 Result = LaunchWindowsStartup(hInInstance, hPrevInstance, pCmdLine, nCmdShow, nullptr);
LaunchWindowsShutdown();
return Result;
}
2. GuardedMain
실질적인 엔진 실행 흐름
Launch.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int32 GuardedMain(const TCHAR* CmdLine)
{
int32 ErrorLevel = EnginePreInit(CmdLine);
#if WITH_EDITOR
if (GIsEditor)
{
ErrorLevel = EditorInit(GEngineLoop);
}
#endif
ErrorLevel = EngineInit();
while (!IsEngineExitRequested())
{
EngineTick();
}
EditorExit();
return ErrorLevel;
}
호출 흐름
1
2
3
4
5
GuardedMain
├ EnginePreInit
├ EditorInit
├ EngineInit
└ EngineTick
3. EnginePreInit
엔진 실행 준비 단계
LaunchEngineLoop.cpp
1
2
3
4
5
6
7
8
9
10
11
12
int32 FEngineLoop::PreInit(const TCHAR* CmdLine)
{
// Config 초기화
// CommandLine 파싱
// Platform 초기화
// ModuleManager 초기화
// PluginManager 초기화
// Trace 초기화
// CrashReporter 준비
// Core Module 로딩
// Startup Module 로딩
}
모듈을 로딩 및 실행할 곳은 LoadingPhase 형태로 관리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
namespace ELoadingPhase
{
enum Type
{
/** As soon as possible - in other words, uplugin files are loadable from a pak file (as well as right after PlatformFile is set up in case pak files aren't used) Used for plugins needed to read files (compression formats, etc) */
EarliestPossible,
/** Loaded before the engine is fully initialized, immediately after the config system has been initialized. Necessary only for very low-level hooks */
PostConfigInit,
/** The first screen to be rendered after system splash screen */
PostSplashScreen,
/** Loaded before coreUObject for setting up manual loading screens, used for our chunk patching system */
PreEarlyLoadingScreen,
/** Loaded before the engine is fully initialized for modules that need to hook into the loading screen before it triggers */
PreLoadingScreen,
/** Right before the default phase */
PreDefault,
/** Loaded at the default loading point during startup (during engine init, after game modules are loaded.) */
Default,
/** Right after the default phase */
PostDefault,
/** After the engine has been initialized */
PostEngineInit,
/** Do not automatically load this module */
None,
// NOTE: If you add a new value, make sure to update the ToString() method below!
Max
};
/**
* Converts a string to a ELoadingPhase::Type value
*
* @param The string to convert to a value
* @return The corresponding value, or 'Max' if the string is not valid.
*/
PROJECTS_API ELoadingPhase::Type FromString( const TCHAR *Text );
/**
* Returns the name of a module load phase.
*
* @param The value to convert to a string
* @return The string representation of this enum value
*/
PROJECTS_API const TCHAR* ToString( const ELoadingPhase::Type Value );
}
}
어떤 모듈은 언제, 어떤 플랫폼에서 로딩이 되는지, 시작이 되는지를 위 enum type으로 구분하여 동작시킨다
PreInit 에서는 GEngine에 필요한 대부분의 필수 모듈들이 탑재가 된다
Ex)
1
IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreLoadingScreen)
4. CoreUObject 로드
PreInit 과정에서 CoreUObject 모듈이 로드된다.
LaunchEngineLoop.cpp
1
2
3
4
5
6
7
8
9
10
int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine)
{
LoadCoreModules();
LoadPreInitModules();
ProcessNewlyLoadedUObjects();
LoadStartupCoreModules();
LoadStartupModules();
}
모듈 로딩
LaunchEngineLoop.cpp
1
2
3
4
bool FEngineLoop::LoadCoreModules()
{
return FModuleManager::Get().LoadModule(TEXT("CoreUObject")) != nullptr;
}
ModuleManager.cpp
1
2
3
4
5
6
7
8
IModuleInterface* FModuleManager::LoadModuleWithFailureReason(...)
{
...
ModuleInfo->Module->StartupModule();
...
}
모듈 로드 과정
1
2
3
4
5
6
7
8
9
LoadModule
↓
DLL 로드
↓
모듈 객체 생성
↓
StartupModule()
↓
내부 자료구조 초기화
Loading Phase
언리얼은 모듈을 단계별로 로드한다.
주요 단계
1
2
3
4
5
PreEarlyLoadingScreen
PreLoadingScreen
Default
PostDefault
PostEngineInit
부팅 시간 측정
1
2
3
SCOPED_BOOT_TIMING("IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreLoadingScreen)");
IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreLoadingScreen);
언리얼은 모듈 로딩 과정에서
- 현재 로딩 단계
- 모듈 로딩 시간
을 함께 추적한다.
정리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
WinMain
↓
GuardedMain
↓
EnginePreInit
↓
CoreUObject 로드
↓
UClass 등록
↓
CDO 생성
↓
생성자 호출
↓
EngineInit
↓
GEngine 생성
↓
EngineTick
↓
UWorld Tick
↓
Actor Tick
생성자가 BeginPlay보다 먼저 호출되는 이유와, 생성자에서 GetWorld()를 사용하면 안 되는 이유는 CoreUObject의 CDO 생성 과정에서 찾을 수 있다.
5. CoreUObject
CoreUObject는 언리얼 객체 시스템의 핵심 모듈이다.
1
2
3
4
5
6
7
8
9
10
11
Core
└ CoreUObject
├ UObject
├ UClass
├ Reflection
├ CDO
├ Package
├ Serialization
├ GC
├ Asset Reference
└ Object Loading
포함 핵심 기능
- UObject
- UClass
- Reflection
- Garbage Collection
- Package
- Serialization
6. UClass 등록과 CDO 생성
모듈 로드 과정 중
1
ProcessNewlyLoadedUObjects();
가 호출된다.
이 과정에서
1
2
3
4
5
6
7
새로운 UClass 등록
↓
Reflection 정보 구축
↓
CDO 생성
↓
생성자 호출
이 수행된다.
7. 생성자에서 주의할 점
생성자는 실제 게임 시작 시에만 호출되는 것이 아니다.
CDO 생성 과정에서도 호출된다.
1
2
3
4
5
6
7
CoreUObject 로드
↓
UClass 등록
↓
CDO 생성
↓
생성자 호출
따라서 생성자 실행 시점에는
1
2
3
4
5
GEngine
UWorld
GameMode
Actor
PlayerController
등이 존재하지 않을 수 있다.
UCLASS() 를 사용해서 정의된 클래스의 생성자에 게임 플레이 관련 코드가 포함되어서는 안되는 이유중 하나이다.
7-1. 생성자에서 수행하는 작업
- 기본값 초기화
- Tick 설정
- Replication 설정
- CreateDefaultSubobject
- 컴포넌트 구조 구성
7-2. 생성자에서 CreateDefaultSubobject를 사용하는 이유
1
CreateDefaultSubobject()
는 단순히 컴포넌트를 생성하는 함수가 아니다.
실제로는 CDO에 액터 기본 구조 정의를 기록하는 것에 가깝다.
1
2
3
액터 기본 구조 정의
↓
CDO에 기록
예시)
생성자에서 호출
1
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
액터 구조 CDO에 기록
1
2
AMyActor_CDO
└ Mesh_CDO
SpawnActor 시 복사, 생성이 실행
1
2
3
4
5
CDO
↓
복사
↓
실제 Actor 생성
8. Engine Init
PreInit이 끝나면 Init()을 통해 실제 엔진 객체 생성 후 실행
LaunchEngineLoop.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int32 FEngineLoop::Init()
{
// GEngine 객체 생성
GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass);
//GEngine
// Editor = UUnrealEdEngine : public UEditorEngine, public FNotifyHook
// UEditorEngine : UEngine
#IF WITH_EDITOR : GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass);
// Game = UEngine
#IF !WITH_EDITOR : GEngine = GEditor = GUnrealEd = NewObject<UUnrealEdEngine>(GetTransientPackage(), EngineClass);
// 객체 초기화
// 게임 인스턴스 초기화
GEngine->Init(this);
SetEngineStartupModuleLoadingComplete();
// 엔진 시작
// 게임 인스턴스 실행
GEngine->Start();
}
9. 엔진 시작 전, 게임 인스턴스 생성 및 맵 초기화
엔진 초기화에서 게임 인스턴스 생성 게임 시작으로 StartGameInstace() 호출
GameEngine.cpp
1
2
3
4
5
6
7
8
9
void UGameEngine::Init(IEngineLoop* InEngineLoop)
{
...
GameInstance = NewObject<UGameInstance>(this, GameInstanceClass);
GameInstance->InitializeStandalone();
}
10. Engine Start
이 시점부터 실제 엔진이 동작하기 시작한다.
GameEngine.cpp
1
2
3
4
5
6
7
8
9
void UGameEngine::Start()
{
TRACE_CPUPROFILER_EVENT_SCOPE(UGameEngine::Start);
UE_LOG(LogInit, Display, TEXT("Starting Game."));
GameInstance->StartGameInstance();
}
1
2
3
4
EngineInit
├ Create GEngine
├ GEngine->Init()
└ GEngine->Start()
11. EngineTick
Launch.cpp
1
2
3
4
LAUNCH_API void EngineTick()
{
GEngineLoop.Tick();
}
Tick 구조
LaunchEngineLoop.cpp
1
2
3
4
5
6
7
8
void FEngineLoop::Tick()
{
BeginExitIfRequested();
GEngine->Tick(FApp::GetDeltaTime(), bIdleMode);
FFrameEndSync::Sync(FFrameEndSync::EFlushMode::EndFrame);
}
호출 흐름
1
2
3
4
5
6
EngineTick
└ FEngineLoop::Tick
└ UEngine::Tick
└ UWorld::Tick
├ Actor Tick
└ Component Tick
스레드 동기화
프레임 종료 시점에 동기화 수행
- Game Thread
- Render Thread