UE4 运行流程源码浅析(2)

本文章只是我个人在学习虚幻引擎过程中的一些理解,不一定正确,若有说的不对的地方,欢迎指正。

在《UE4 运行流程源码浅析(1)》中我们简略讲了一下虚幻引擎运行流程,相信大家已经对整体框架有了初步的了解。本篇我们来讲一下之前没讲的,在虚幻引擎预初始化(GEngineLoop.PreInit)、初始化(GEngineLoop.Init)、循环(GEngineLoop.Tick)等阶段中调用的一些比较重要的函数。

一.GEngineLoop.PreInit:

还记得吗引擎循环类(GEngineLoop)的预初始化(PreInit)主要调用两个函数:预初始化的预启动函数(PreInitPreStartupScreen)和预初始化的后启动函数(PreInitPostStartupScreen)。其中PreInitPreStartupScreen调用了启动虚幻应用程序函数(AppInit),PreInitPostStartupScreen调用了用来加载需要的核心模块的函数(LoadStartupCoreModules)。

1.AppInit:

AppInit函数启动应用程序,内部做一些和平台有关的初始化操作,包括初始化时间、文件系统、日志系统、相关配置等。下面是简略源码,解释都在注释中:

bool FEngineLoop::AppInit()
{
	//……

	//特定平台的预初始化
	FPlatformMisc::PlatformPreInit();

	//平台应用的预初始化
	FPlatformApplicationMisc::PreInit();

	//记录启动时间
	GSystemStartTime = FDateTime::Now().ToString();

	//切换到执行文件目录
	FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir();

	//在分析命令行完成后初始化文件管理器(file manager)
	IFileManager::Get().ProcessCommandLineOptions();

	//此处省略了一些命令行处理……

	//初始化日志目录树
	IFileManager::Get().MakeDirectory(*FPaths::ProjectLogDir(), true);

	//注释翻译:获取最小文件名,此时我们有足够的信息将其保存日志文件夹名字
	FCString::Strcpy(MiniDumpFilenameW, *IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*FString::Printf(TEXT("%sunreal-v%i-%s.dmp"), *FPaths::ProjectLogDir(), FEngineVersion::Current().GetChangelist(), *FDateTime::Now().ToString())));

	//在磁盘中初始化日志系统
	FPlatformOutputDevices::SetupOutputDevices();

	//……

	//初始化配置系统(config system)
	FConfigCacheIni::InitializeConfigSystem();

	//……

	//加载哪些加载阶段设置为尽早(EarliestPossible)的插件和项目
	ProjectManager.LoadModulesForProject(ELoadingPhase::EarliestPossible)
	PluginManager.LoadModulesForEnabledPlugins(ELoadingPhase::EarliestPossible)

	//此时配置已经初始化完成,开始设置堆栈运行选项
	FPlatformStackWalk::Init();

	//……

	//根据路径检测某些项目和插件是否更新,并尝试编译它们
	ProjectManager.CheckModuleCompatibility(IncompatibleFiles);
	PluginManager.CheckModuleCompatibility(IncompatibleFiles, IncompatibleEngineFiles);

	//……

	//测试若存在需要的模块,他通过IDE加载它
	FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *CompileForbidden, TEXT("Missing Modules"));

	//继续执行之前询问是否编译
	FPlatformMisc::MessageBoxExt(EAppMsgType::YesNo, *CompilePrompt, *FString::Printf(TEXT("Missing %s Modules"), FApp::GetProjectName())

	//……

	//处理命令行和配置信息
	FLogSuppressionInterface::Get().ProcessConfigAndCommandLine();

	//此时配置系统(config system)已经完成初始化,开始初始化音视频系统(audio system)
	[[IOSAppDelegate GetDelegate]InitializeAudioSession] ;

	//加载那些加载阶段设置为预加载(pre-init)的插件和项目
	ProjectManager.LoadModulesForProject(ELoadingPhase::PostConfigInit)
	PluginManager.LoadModulesForEnabledPlugins(ELoadingPhase::PostConfigInit)

	//……

	//初始化头显
	PreInitHMDDevice();

	//……

	//根据命令行参数启动日志打印
	GLogConsole->Show(true);

	//打印所有初始化日志
	FApp::PrintStartupLogMessages();

	//……

	//初始化色彩列表
	GColorList.CreateColorMap();

	//触发代理函数,通知初始化一些其他系统 
	FCoreDelegates::OnInit.Broadcast();

	//……
}

2.LoadStartupCoreModules:

LoadStartupCoreModules函数内部实现基本都是在加载模块,包括运行时模块(Runtime)、平台需要的模块、编辑器相关的模块、UMG/Slate相关模块、音效相关模块、AI相关模块等。可以发现许多模块是我们听说过的。简略源码如下:

bool FEngineLoop::LoadStartupCoreModules()
{
	//……

	//加载所有运行时(Runtime)模块
	FModuleManager::Get().LoadModule(TEXT("Core"));
	FModuleManager::Get().LoadModule(TEXT("Networking"));

	//加载一些平台需要的模块
	FPlatformApplicationMisc::LoadStartupModules();

	//加载Messaging模块
	FModuleManager::LoadModuleChecked<IMessagingModule>("Messaging");

	//加载MRMesh,根据注释来看这个模块用来支持场景重建(Scene Reconstruction)
	FModuleManager::LoadModuleChecked<IMRMeshModule>("MRMesh");

	//加载UnrealEd、EditorStyle、LandscapeEditorUtilities等模块
	FModuleManager::Get().LoadModuleChecked("UnrealEd");
	FModuleManager::LoadModuleChecked<IEditorStyleModule>("EditorStyle");
	FModuleManager::Get().LoadModuleChecked("LandscapeEditorUtilities");

	//加载SlateCore、Slate、SlateReflector模块,这些都是UI类模块,其中SlateReflector用来初始化WidgetSnapshotService
	FModuleManager::Get().LoadModule("SlateCore");
	FModuleManager::Get().LoadModule("Slate");
	FModuleManager::Get().LoadModule("SlateReflector");

	//加载UMG模块,该模块在运行时(runtime)或将游戏项目中的资源进行处理和打包时(cooking)使用;
	//加载UMG/UMGEditor模块时为蓝图(blueprints)的编译做准备
	FModuleManager::Get().LoadModule("UMG");

	/***************************************************************************************
	*	加载所有Development模块
	***************************************************************************************/

	//加载MessageLog和FunctionalTesting模块
	FModuleManager::Get().LoadModule("MessageLog");
	FModuleManager::Get().LoadModule("FunctionalTesting");

	//加载行为树编辑器(BehaviorTreeEditor)模块,打包行为树资源需要该模块
	FModuleManager::Get().LoadModule(TEXT("BehaviorTreeEditor"));

	//加载GameplayTasksEditor模块,能力任务(Ability tasks)就是基于GameplayTasks的
	FModuleManager::Get().LoadModule(TEXT("GameplayTasksEditor"));

	//加载音效模块并注册对应资源
	IAudioEditorModule * AudioEditorModule = &FModuleManager::LoadModuleChecked<IAudioEditorModule>("AudioEditor");
	AudioEditorModule->RegisterAssetActions();

	//加载StringTableEditor模块并注册对应资源
	FModuleManager::Get().LoadModule("StringTableEditor");

	//加载VREditor模块
	FModuleManager::Get().LoadModule(TEXT("VREditor"));

	//加载EnvironmentQueryEditor(EQS)模块,在静态资源初始化和资源打包(cooking)时使用
	FModuleManager::Get().LoadModule(TEXT("EnvironmentQueryEditor"));

	//加载IntroTutorials和Blutility模块
	FModuleManager::Get().LoadModule(TEXT("IntroTutorials"));
	FModuleManager::Get().LoadModule(TEXT("Blutility"));

	/***************************************************************************************
	*	加载运行时客户端需要的模块
	***************************************************************************************/
	
	//加载Overlay模块
	FModuleManager::Get().LoadModule(TEXT("Overlay"));

	//加载MediaAssets模块
	FModuleManager::Get().LoadModule(TEXT("MediaAssets"));

	//加载ClothingSystemRuntimeNv模块
	FModuleManager::Get().LoadModule(TEXT("ClothingSystemRuntimeNv"));

	//加载ClothingSystemEditor模块
	FModuleManager::Get().LoadModule(TEXT("ClothingSystemEditor"));

	//加载PacketHandler和NetworkReplayStreaming模块
	FModuleManager::Get().LoadModule(TEXT("PacketHandler"));
	FModuleManager::Get().LoadModule(TEXT("NetworkReplayStreaming"));

	//……
}

注:虚幻引擎加载模块几乎都是使用FModuleManager

二.GEngineLoop.Init:

这里先提一下UEngine,该类表示引擎实例类型,它有两个子类,分别是编辑器模式下的UEditorEngine和非编辑器模式下的UGameEngine,在这一小节中我们主要聚焦在非编辑器模式下的函数实现。
在非编辑器模式下执行引擎循环类的初始化接口的过程中,会调用引擎实例的初始化接口(UGameEngine::Init)和开始接口(UGameEngine::Start),其中Start会初始化GameInstance,加载地图和初始化世界和世界中的Actor,而UGameEngine::Init会调用父类的实现(UEngine::Init)。

注:由于Start内容较多且比较重要,本章主要讲UGameEngine和UEngine的Init,至于Start会留待下一篇讲

1.UGameEngine::Init:

UGameEngine::Init代码量不多,缩减之后加上注释也就四十行左右。该函数在一开始就调用了父类实现(UEngine::Init),之后加载用户的游戏设置、初始化GameInstance、创建世界管理器(FWorldContext)、初始化UGameViewportClientULocalPlayer
这里说一下UGameViewportClientULocalPlayer的关系。可以把ULocalPlayer看作屏幕前的用户,把UGameViewportClient看作屏幕,本质是处理音效、渲染和输入的接口类。

注:GameInstance在UE4.4开始出现,是从UGameEngine中分离出来的 UGameEngine::Init简略源码如下:

void UGameEngine::Init(IEngineLoop* InEngineLoop)
{
	//……

	//调用基类函数
	UEngine::Init(InEngineLoop);

	//……

	//加载和引用用户的游戏设置
	GetGameUserSettings()->LoadSettings();
	GetGameUserSettings()->ApplyNonResolutionSettings();

	//创建游戏实例(GameInstance)并初始化
	GameInstance = NewObject<UGameInstance>(this, GameInstanceClass);
	GameInstance->InitializeStandalone();

	//创建WorldContext,这部分在UE4.25中是注掉的
	//WorldContext用来管理世界(World),游戏模式下WorldContext只有唯一个,编辑器模式有两个WorldContext,一个管理编辑器的World,一个PIE(Play In Editor)的World
	FWorldContext& InitialWorldContext = CreateNewWorldContext(EWorldType::Game);
	
	//……

	//初始化客户端游戏视口、初始化并赋给WorldContext
	ViewportClient = NewObject<UGameViewportClient>(this, GameViewportClientClass);
	ViewportClient->Init(*GameInstance->GetWorldContext(), GameInstance);
	GameViewport = ViewportClient;
	GameInstance->GetWorldContext()->GameViewport = ViewportClient;

	//下面这部分代码应该在创建UI之前执行
	{
		//把创建的客户端视口绑定到新视口
		CreateGameViewport(ViewportClient);
		
		//初始化本地玩家(LocalPlayer)
		ViewportClient->SetupInitialLocalPlayer(Error)

		//触发视口创建代理函数
		UGameViewportClient::OnViewportCreated().Broadcast();
	}

	//设置引擎实例已初始化标志
	bIsInitialized = true;
}

2.UEngine::Init:

之前我们说子类的初始化接口调用了父类实现,现在我们来说说它的源码。UEngine::Init代码量比子类更多,缩减之后也有大约一百行。该函数做一些通用的初始化,包括初始化子系统、默认地图处理、Slate相关处理、加载一些引擎类、读取配置、创建世界(UWorld)等。老规矩,上菜:

void UEngine::Init(IEngineLoop* InEngineLoop)
{
	//……

	EngineLoop = InEngineLoop;

	//收集并初始化子系统(Subsystems)
	EngineSubsystemCollection.Initialize(this);

	//通过检测命令行,确定用户是否设置默认地图(default map)
	if (FParse::Value(FCommandLine::Get(), TEXT("DEFAULTMAP="), MapName, UE_ARRAY_COUNT(MapName)))
	{
		//……
		UGameMapsSettings::SetGameDefaultMap(MapString);
	}

	//……

	//添加垃圾回收(GC)代理函数
	FCoreUObjectDelegates::GetPreGarbageCollectDelegate().AddStatic(UEngine::PreGarbageCollect);

	//初始化头显设备
	InitializeHMDDevice();

	//初始化眼睛追踪设备
	InitializeEyeTrackingDevice();

	//……

	//当Slate被使用时(不在专属服务器上),初始化Slate的渲染(此时在RHI已经初始)
	FSlateApplication& CurrentSlateApp = FSlateApplication::Get();
	CurrentSlateApp.InitializeSound(TSharedRef<FSlateSoundDevice>(new FSlateSoundDevice()));

	//……

	//加载引擎类
	LoadObject<UClass>(UEngine::StaticClass()->GetOuter(), *UEngine::StaticClass()->GetName(), NULL, LOAD_Quiet | LOAD_NoWarn, NULL);
	
	//读取引擎配置文件(Engine.ini),获取默认材质(DefaultMaterial)和etc
	LoadConfig();

	//……

	//该函数加载所有引擎引用的对象(包括材质、Texture、UConsole、UGameViewportClient等)
	InitializeObjectReferences();

	//……

	//为编辑器创建一个WorldContext并创建一个新的World
	FWorldContext& InitialWorldContext = CreateNewWorldContext(EWorldType::Editor);
	InitialWorldContext.SetCurrentWorld(UWorld::CreateWorld(EWorldType::Editor, true));
	GWorld = InitialWorldContext.World();

	//初始化音效设别管理器
	InitializeAudioDeviceManager();

	//网络校验,确保网络能在该项目版本正常使用
	const UGeneralProjectSettings& ProjectSettings = *GetDefault<UGeneralProjectSettings>();
	FNetworkVersion::SetProjectVersion(*ProjectSettings.ProjectVersion);

	//……

	//添加travel和network失败时的代理函数,可以覆盖该代理以添加自己的处理代码
	OnTravelFailure().AddUObject(this, &UEngine::HandleTravelFailure);
	OnNetworkFailure().AddUObject(this, &UEngine::HandleNetworkFailure);
	OnNetworkLagStateChanged().AddUObject(this, &UEngine::HandleNetworkLagStateChanged);

	//添加在线子系统代理函数
	FOnlineExternalUIChanged OnExternalUIChangeDelegate;
	OnExternalUIChangeDelegate.BindUObject(this, &UEngine::OnExternalUIChange);
	UOnlineEngineInterface::Get()->BindToExternalUIOpening(OnExternalUIChangeDelegate);

	//初始化内存可视化系统的数据
	GetBufferVisualizationData().Initialize();

	//……

	//初始化引擎分析器(engine analytics provider)
	FEngineAnalytics::Initialize();

	//加载引擎运行时模块(engine runtime modules)
	{
		FModuleManager::Get().LoadModule("ImageWriteQueue");
		FModuleManager::Get().LoadModuleChecked("StreamingPauseRendering");
		FModuleManager::Get().LoadModuleChecked("MovieScene");
		FModuleManager::Get().LoadModuleChecked("MovieSceneTracks");
		FModuleManager::Get().LoadModule("LevelSequence");
	}

	//……

	//触发资源管理器加载工作的完成事件
	AssetManager->FinishInitialLoading();

	//……

	//这里省略添加引擎状态的代码……

	//……

	//记录头显设别的分析
	RecordHMDAnalytics();

	//……
}

三.GEngineLoop.Tick:

本小节仍然聚焦在非编辑器模式下,引擎循环类的Tick接口每帧调用引擎实例的TickUGameEngine::Tick)。该接口每帧更新许多模块:日志、刷新窗口、子系统,渲染等,还触发一些模块的Tick,简略源码如下:

void UGameEngine::Tick(float DeltaSeconds, bool bIdleMode)
{
	//……

	//更新Log
	GLog->Flush();

	//清除关闭的窗口
	CleanupGameViewport();

	//根据帧率设置是否降低画面细节
	GameViewport->SetDropDetail(DeltaSeconds);

	//调用一些子系统(subsystems)的Tick
	FEngineAnalytics::Tick(DeltaSeconds);
	FStudioAnalytics::Tick(DeltaSeconds);

	//……

	//根据需要触发无缝地图、客户端、服务端的Tick
	TickWorldTravel(Context, DeltaSeconds);

	//调用World的Tick
	Context.World()->Tick(LEVELTICK_All, DeltaSeconds);

	//更新天光(SkyLight)
	USkyLightComponent::UpdateSkyCaptureContents(Context.World());

	//根据需要生成玩家,提供一个刷新玩家的机会
	GamePlayer->Exec(GamePlayer->GetWorld(), *(FString("CAUSEEVENT ") + InitialExec), *GLog);

	//根据需要阻塞住World的异步加载 Block on async loading if requested.
	BlockTillLevelStreamingCompleted(Context.World());

	//更新关卡流
	Context.World()->UpdateLevelStreaming();

	//……

	//触发游戏对象(GameObject)的Tick
	FTickableGameObject::TickObjects(nullptr, LEVELTICK_All, false, DeltaSeconds);

	//……

	//触发media模块的Tick
	IMediaModule* MediaModule = FModuleManager::LoadModulePtr<IMediaModule>(MediaModuleName);
	MediaModule->TickPostEngine();

	//触发游戏视口(GameViewport)的Tick
	GameViewport->Tick(DeltaSeconds);

	//显示这一帧游戏窗口中的内容
	GameViewportWindow.Pin()->ShowWindow();
	FSlateApplication::Get().RegisterGameViewport(GameViewportWidget.ToSharedRef());
	
	//渲染所有需要渲染的东西 Render everything.
	RedrawViewports();

	//触发需要在所有渲染完成后做的任务
	GetRendererModule().PostRenderAllViewports();

	//触资源流Tick,更新信息
	IStreamingManager::Get().Tick(DeltaSeconds);

	//触发音效Tick,更新音效
	GameAudioDeviceManager->UpdateActiveAudioDevices(bIsAnyNonPreviewWorldUnpaused);

	//更新渲染线程相关命令 rendering thread commands
	//触发GRenderingRealtimeClock、GRenderTargetPool、FRDGBuilder的Tick
	GRenderingRealtimeClock.Tick(DeltaSeconds);
	GRenderTargetPool.TickPoolElements();
	FRDGBuilder::TickPoolElements();

	//广播EngineTick完成事件
	BroadcastPostEditorTick(DeltaSeconds);

	//触资产注册(FAssetRegistryModule)模块Tick, 更新资产注册信息
	FAssetRegistryModule::TickAssetRegistry(DeltaSeconds);
}

四.小结:

本章进一步讲了引擎的预初始化、初始化和循环几个阶段。但初始化还有个Start没讲,这在下篇再来讲,话不多所,下篇见。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值