UE4模块启动顺序

前言

本文大致梳理一下UE4编辑器启动后各个模块的加载顺序, 重点看一下ELoadingPhase枚举对应的类型的加载时机

入口

任何可执行程序都会有一个执行入口,在 UE 中,每一个 Target 都会编译出一个可执行程序。引擎启动是从 Launch 模块开始的,main 函数也是定义在其中的

简化代码表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int32 GuardedMain( const TCHAR* CmdLine )
{
EnginePreInit( CmdLine );
//...........
if (GIsEditor)
{
EditorInit(GEngineLoop);
}
else
{
EngineInit();
}
//...........
while( !IsEngineExitRequested() )
{
EngineTick();
}
}

从代码的字面意思就可以理解为启动步骤大概就是 预初始化->初始化, 然后是Tick循环

这里不得不说到一个引擎类 FEngineLoop

FEngineLoop 管理了程序的初始化与主循环

因为整个UE是模块化的架构,引擎的实现就是靠各个模块组合驱动的, 那么就拿模块的启动来看一下引擎的启动顺序

模块启动

先看一下模块加载的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace ELoadingPhase
{
enum Type
{
EarliestPossible,
PostConfigInit,
PostSplashScreen,
PreEarlyLoadingScreen,
PreLoadingScreen,
PreDefault,
Default,
PostDefault,
PostEngineInit,
None,
Max,
}
}

先看一下PreDefault模式的插件到StartupModule的调用栈

image-20210809164503801

先看下面代码

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
bool FEngineLoop::LoadStartupModules()
{
FScopedSlowTask SlowTask(3);

SlowTask.EnterProgressFrame(1);
// Load any modules that want to be loaded before default modules are loaded up.
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PreDefault) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PreDefault))
{
return false;
}

SlowTask.EnterProgressFrame(1);
// Load modules that are configured to load in the default phase
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::Default) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::Default))
{
return false;
}

SlowTask.EnterProgressFrame(1);
// Load any modules that want to be loaded after default modules are loaded up.
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostDefault) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostDefault))
{
return false;
}

return true;
}

上面是几种类型加载模式的模块启动都在编辑器引擎启动之前, 意味着UEditorEngine::InitEditor()还未执行.

那么其他类型比如常见的PostEngineInit是啥时候启动的呢?

FEngineLoop::Init()中, 先执行了GEditor的初始化, 然后再执行PostEngineInit类型的模块的加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
SCOPED_BOOT_TIMING("GEngine->Init");
GEngine->Init(this);
}
//............
{
SCOPED_BOOT_TIMING("IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit)");
// Load all the post-engine init modules
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit))
{
RequestEngineExit(TEXT("One or more modules failed PostEngineInit"));
return 1;
}
}

总结

简单梳理各类模块加载的顺序,以及部分常见的功能的初始化顺序

  • 多种平台调用 GuardedMain() //Launch.cpp
  • GuardedMain 依次调用EnginePreInit, EditorInitEngineInit, 然后循环EngineTick
  • Launch.cpp内有全局变量 FEngineLoop GEngineLoop;, 上述方法通过GEngineLoop执行
  • GEngineLoop
    • FEngineLoop::PreInit 会执行PreInitPreStartupScreen, PreInitPostStartupScreen
      • 前者会执行如命令行, 本地化等等大量初始化和逻辑, 先通过FEngineLoop::AppInit加载*EarliestPossiblePostConfigInit类型的模块, 随后加载*PostSplashScreen类型的模块 (0%-18%), 退出函数时加载至39%**
      • 后者加载*PreEarlyLoadingScreen*类型的模块(39%); 随后播放开场动画等, 然后加载AssetRegistry模块, 然后通过LoadStartupCoreModules(45% - 55%)方法加载核心模块, 随后是*PreLoadingScreen*类型的模块(55% - 59%), 接着开始渲染线程StartRenderingThread (59% - 75%), 然后通过LoadStartupModules(75%)方法加载*PreDefault, Default, PostDefault*类型的模块,
      • 随后创建几个单例并调用Init(%79 - 86%)
        • 创建GEditor并调用GEditorEngine::InitEditor , 该方法会加载编辑器资源
        • 创建GEngine并调用GEngine::Init, 该方法也有大量初始化比如创建FWorldContext, Config的加载, 音效设备的初始化, 加载一些系统模块(MovieScene,LevelSequence等等)
    • 然后通过执行 FEngineLoop::Init加载*PostEngineInit*类型的模块(86% - 90%)

补充

加载核心模块在 45%进度, EarliestPossible, PostConfigInit , PostSplashScreenPreEarlyLoadingScreen会在此之前

核心模块有Core, Networking, UnrealEd, Slate等等

编辑器资源初始化在75%之后, 也就是在PreLoadingScreen, PreDefault, Default, PostDefault之后, 所以如果需要对资源进行操作需要使用最后加载的PostEngineInit

另外, 引擎提供了几个全局代理, 可以来监听比如编辑器初始化完毕等事件 , 比如

1
FCoreDelegates::OnPostEngineInit.AddRaw(this, &FAdvancedFrameworkEditorModule::RegisterPlaceActors);

详情可以参考文件CoreDelegates.h