虚幻4引擎源码学习笔记(二):主循环LaunchEngineLoop

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wolf96/article/details/82828435

点此查看大图

虚幻引擎主循环为LaunchEngineLoop.cpp,LaunchEngineLoop.h

LaunchEngineLoop.cpp里面有3000+行代码,包含了整个虚幻引擎生命周期的流程

依.cpp顺序依次看一下

引用库和变量定义

大部分功能的库都被引用

#include "LaunchEngineLoop.h"

#include "HAL/PlatformStackWalk.h"
#include "HAL/PlatformOutputDevices.h"
#include "HAL/LowLevelMemTracker.h"
#include "Misc/MessageDialog.h"
#include "Misc/ScopedSlowTask.h"
#include "Misc/QueuedThreadPool.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformAffinity.h"
#include "Misc/FileHelper.h"
#include "Internationalization/TextLocalizationManagerGlobals.h"
#include "Logging/LogSuppressionInterface.h"
#include "Async/TaskGraphInterfaces.h"
#include "Misc/TimeGuard.h"
#include "Misc/Paths.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/OutputDeviceHelper.h"
#include "Misc/OutputDeviceRedirector.h"
#include "Misc/AutomationTest.h"
#include "Misc/CommandLine.h"
#include "Misc/App.h"
#include "Misc/OutputDeviceConsole.h"
#include "HAL/PlatformFilemanager.h"
#include "Templates/ScopedPointer.h"
#include "HAL/FileManagerGeneric.h"
#include "HAL/ExceptionHandling.h"
#include "Stats/StatsMallocProfilerProxy.h"
#include "HAL/PlatformSplash.h"
#include "HAL/PlatformApplicationMisc.h"
#include "HAL/ThreadManager.h"
#include "ProfilingDebugging/ExternalProfiler.h"
#include "Containers/Ticker.h"

#include "Interfaces/IPluginManager.h"
#include "ProjectDescriptor.h"
#include "Interfaces/IProjectManager.h"
#include "Misc/UProjectInfo.h"
#include "Misc/EngineVersion.h"

#include "Misc/CoreDelegates.h"
#include "Modules/ModuleManager.h"
#include "Runtime/Launch/Resources/Version.h"
#include "Modules/BuildVersion.h"
#include "UObject/DevObjectVersion.h"
#include "HAL/ThreadHeartBeat.h"

#include "Misc/NetworkVersion.h"
#include "Templates/UniquePtr.h"

#if !(IS_PROGRAM || WITH_EDITOR)
#include "IPlatformFilePak.h"
#endif

#if WITH_COREUOBJECT
#include "Internationalization/PackageLocalizationManager.h"
#include "Misc/PackageName.h"
#include "UObject/UObjectHash.h"
#include "UObject/Package.h"
#include "UObject/Linker.h"
#include "UObject/LinkerLoad.h"
#endif

#if WITH_EDITOR
#include "Blueprint/BlueprintSupport.h"
#include "EditorStyleSet.h"
#include "Misc/RemoteConfigIni.h"
#include "EditorCommandLineUtils.h"
#include "Input/Reply.h"
#include "Styling/CoreStyle.h"
#include "RenderingThread.h"
#include "Editor/EditorEngine.h"
#include "UnrealEdMisc.h"
#include "UnrealEdGlobals.h"
#include "Editor/UnrealEdEngine.h"
#include "Settings/EditorExperimentalSettings.h"
#include "Interfaces/IEditorStyleModule.h"
#include "PIEPreviewDeviceProfileSelectorModule.h"

#if PLATFORM_WINDOWS
#include "Windows/AllowWindowsPlatformTypes.h"
#include <objbase.h>
#include "Windows/HideWindowsPlatformTypes.h"
#endif
#endif //WITH_EDITOR
//引擎相关
#if WITH_ENGINE
#include "Engine/GameEngine.h"
#include "UnrealClient.h"
#include "Engine/LocalPlayer.h"
#include "GameFramework/PlayerController.h"
#include "GameFramework/GameUserSettings.h"
#include "Features/IModularFeatures.h"
#include "GameFramework/WorldSettings.h"
#include "SystemSettings.h"
#include "EngineStats.h"
#include "EngineGlobals.h"
#include "AudioThread.h"
#if WITH_ENGINE && !UE_BUILD_SHIPPING
#include "IAutomationControllerModule.h"
#endif // WITH_ENGINE && !UE_BUILD_SHIPPING
#include "Database.h"
#include "DerivedDataCacheInterface.h"
#include "ShaderCompiler.h"
#include "DistanceFieldAtlas.h"
#include "GlobalShader.h"
#include "ShaderCodeLibrary.h"
#include "Materials/MaterialInterface.h"
#include "TextureResource.h"
#include "Engine/Texture2D.h"
#include "Internationalization/StringTable.h"
#include "SceneUtils.h"
#include "ParticleHelper.h"
#include "PhysicsPublic.h"
#include "PlatformFeatures.h"
#include "DeviceProfiles/DeviceProfileManager.h"
#include "Commandlets/Commandlet.h"
#include "EngineService.h"
#include "ContentStreaming.h"
#include "HighResScreenshot.h"
#include "Misc/HotReloadInterface.h"
#include "ISessionServicesModule.h"
#include "Net/OnlineEngineInterface.h"
#include "Internationalization/EnginePackageLocalizationCache.h"
#include "Rendering/SlateRenderer.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/SlateApplication.h"
#include "IMessagingModule.h"
#include "Engine/DemoNetDriver.h"
#include "LongGPUTask.h"
#include "RenderUtils.h"
#include "DynamicResolutionState.h"
#include "EngineModule.h"

#if !UE_SERVER
#include "AppMediaTimeSource.h"
#include "IHeadMountedDisplayModule.h"
#include "IMediaModule.h"
#include "HeadMountedDisplay.h"
#include "MRMeshModule.h"
#include "Interfaces/ISlateRHIRendererModule.h"
#include "Interfaces/ISlateNullRendererModule.h"
#include "EngineFontServices.h"
#endif

#include "MoviePlayer.h"

#include "ShaderCodeLibrary.h"
#include "ShaderCache.h"
#include "ShaderPipelineCache.h"

#if !UE_BUILD_SHIPPING
#include "STaskGraph.h"
#include "IProfilerServiceModule.h"
#endif

#if WITH_AUTOMATION_WORKER
#include "IAutomationWorkerModule.h"
#endif
#endif //WITH_ENGINE
//渲染器
class FSlateRenderer;
//视口
class SViewport;
//各个平台相关文件
class IPlatformFile;
//外部分析器
class FExternalProfiler;
//反馈上下文
class FFeedbackContext;

#if WITH_EDITO
#include "FeedbackContextEditor.h"
static FFeedbackContextEditor UnrealEdWarn;
#include "AudioEditorModule.h"
#endif // WITH_EDITOR

#if UE_EDITOR
#include "DesktopPlatformModule.h"
#endif

#define LOCTEXT_NAMESPACE "LaunchEngineLoop"

#if PLATFORM_WINDOWS
#include "Windows/AllowWindowsPlatformTypes.h"
#include <ObjBase.h>
#include "Windows/HideWindowsPlatformTypes.h"
#endif

#if WITH_ENGINE
#include "EngineDefines.h"
#if ENABLE_VISUAL_LOG
#include "VisualLogger/VisualLogger.h"
#endif
#include "ProfilingDebugging/CsvProfiler.h"
#include "ProfilingDebugging/TracingProfiler.h"
#endif

#if defined(WITH_LAUNCHERCHECK) && WITH_LAUNCHERCHECK
#include "ILauncherCheckModule.h"
#endif

#if WITH_COREUOBJECT
#ifndef USE_LOCALIZED_PACKAGE_CACHE
#define USE_LOCALIZED_PACKAGE_CACHE 1
#endif
#else
#define USE_LOCALIZED_PACKAGE_CACHE 0
#endif

#ifndef RHI_COMMAND_LIST_DEBUG_TRACES
#define RHI_COMMAND_LIST_DEBUG_TRACES 0
#endif

#ifndef REAPPLY_INI_SETTINGS_AFTER_EARLY_LOADING_SCREEN
#define REAPPLY_INI_SETTINGS_AFTER_EARLY_LOADING_SCREEN 0
#endif

#if WITH_ENGINE
CSV_DECLARE_CATEGORY_MODULE_EXTERN(CORE_API, Basic);
#endif
//在专用服务器上使用GC
int32 GUseDisregardForGCOnDedicatedServers = 1;
static FAutoConsoleVariableRef CVarUseDisregardForGCOnDedicatedServers(
TEXT("gc.UseDisregardForGCOnDedicatedServers"),
GUseDisregardForGCOnDedicatedServers,
TEXT("If false, DisregardForGC will be disabled for dedicated servers."),
ECVF_Default
);
//增加随机的sleep到tick时钟,DoAsyncEndOfFrameTasks 在任意线程上shake loose bugs 。从游戏线程中刷新随机渲染线程
static TAutoConsoleVariable<int32> CVarDoAsyncEndOfFrameTasksRandomize(
TEXT("tick.DoAsyncEndOfFrameTasks.Randomize"),
0,
TEXT("Used to add random sleeps to tick.DoAsyncEndOfFrameTasks to shake loose bugs on either thread. Also does random render thread flushes from the game thread.")
);
//验证 Slate tick中复制的属性没有改变
static TAutoConsoleVariable<int32> CVarDoAsyncEndOfFrameTasksValidateReplicatedProperties(
TEXT("tick.DoAsyncEndOfFrameTasks.ValidateReplicatedProperties"),
0,
TEXT("If true, validates that replicated properties haven't changed during the Slate tick. Results will not be valid if demo.ClientRecordAsyncEndOfFrame is also enabled.")
);
//任务和进程的优先级,帧任务后的异步 experiemntal???
static FAutoConsoleTaskPriority CPrio_AsyncEndOfFrameGameTasks(
TEXT("TaskGraph.TaskPriorities.AsyncEndOfFrameGameTasks"),
TEXT("Task and thread priority for the experiemntal async end of frame tasks."),
ENamedThreads::HighThreadPriority,
ENamedThreads::NormalTaskPriority,
ENamedThreads::HighTaskPriority
);

I/O部分方法

序列化和GLog相关

//管道输出到std输出
//使得UBT(UnrealBuildTool)回收他自己使用的输出
// Pipe output to std output
// This enables UBT to collect the output for it's own use

//std输出设备? (I/O部分?)
class FOutputDeviceStdOutput : public FOutputDevice
{
public:

FOutputDeviceStdOutput()
: AllowedLogVerbosity(ELogVerbosity::Display)
{
if (FParse::Param(FCommandLine::Get(), TEXT("AllowStdOutLogVerbosity")))
{
AllowedLogVerbosity = ELogVerbosity::Log;
}

if (FParse::Param(FCommandLine::Get(), TEXT("FullStdOutLogOutput")))
{
AllowedLogVerbosity = ELogVerbosity::All;
}
}

virtual ~FOutputDeviceStdOutput()
{
}
//是否可以在任何线程上使用
virtual bool CanBeUsedOnAnyThread() const override
{
return true;
}
//序列化
virtual void Serialize( const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category ) override
{
if (Verbosity <= AllowedLogVerbosity)
{
#if PLATFORM_TCHAR_IS_CHAR16
printf("%s\n", TCHAR_TO_UTF8(*FOutputDeviceHelper::FormatLogLine(Verbosity, Category, V, GPrintLogTimes)));
#elif PLATFORM_USE_LS_SPEC_FOR_WIDECHAR
// printf prints wchar_t strings just fine with %ls, while mixing printf()/wprintf() is not recommended (see https://stackoverflow.com/questions/8681623/printf-and-wprintf-in-single-c-code)
printf("%ls\n", *FOutputDeviceHelper::FormatLogLine(Verbosity, Category, V, GPrintLogTimes));
#else
wprintf(TEXT("%s\n"), *FOutputDeviceHelper::FormatLogLine(Verbosity, Category, V, GPrintLogTimes));
#endif
fflush(stdout);
}
}

private:
ELogVerbosity::Type AllowedLogVerbosity;
};

//如果在日志输出中出现任何指定的短语,就退出游戏/编辑器
// Exits the game/editor if any of the specified phrases appears in the log output

//输出设备测试退出
class FOutputDeviceTestExit : public FOutputDevice
{
TArray<FString> ExitPhrases;
public:
FOutputDeviceTestExit(const TArray<FString>& InExitPhrases)
: ExitPhrases(InExitPhrases)
{
}
virtual ~FOutputDeviceTestExit()
{
}
//序列化
virtual void Serialize(const TCHAR* V, ELogVerbosity::Type Verbosity, const class FName& Category) override
{
if (!GIsRequestingExit)
{
for (auto& Phrase : ExitPhrases)
{
if (FCString::Stristr(V, *Phrase) && !FCString::Stristr(V, TEXT("-testexit=")))
{
#if WITH_ENGINE
if (GEngine != nullptr)
{
if (GIsEditor)
{
GEngine->DeferredCommands.Add(TEXT("CLOSE_SLATE_MAINFRAME"));
}
else
{
GEngine->Exec(nullptr, TEXT("QUIT"));
}
}
#else
FPlatformMisc::RequestExit(true);
#endif
break;
}
}
}
}
};

//Scoped输出部分变量
static TUniquePtr<FOutputDeviceConsole> GScopedLogConsole;
static TUniquePtr<FOutputDeviceStdOutput> GScopedStdOut;
static TUniquePtr<FOutputDeviceTestExit> GScopedTestExit;
//初始化std输出设备并把它加到GLog
/**
* Initializes std out device and adds it to GLog
**/
void InitializeStdOutDevice()
{
// Check if something is trying to initialize std out device twice.
check(!GScopedStdOut);

GScopedStdOut = MakeUnique<FOutputDeviceStdOutput>();
//把std加到GLog
GLog->AddOutputDevice(GScopedStdOut.Get());
}

Slate部分

关于Slate

Slate是一种用户界面架构

Slate 是一种完全自定义的、平台无关的用户界面架构,其设计目的是使得构建工具及应用程序(比如虚幻编辑器)

它结合了一种可以轻松设计、布局及风格化组件的声明式语法,使得可以轻松地创建用户界面并进行迭代开发。

https://blog.csdn.net/pizi0475/article/details/50471207?utm_source=copy

Slate控件可以用于在游戏中创建平头显示信息(HUD)或其他用户界面(UI)元素, 比如菜单。您一般可以创建一个或多个 容器 控件,

每个容器可以包含几个其他类型的控件, 这些控件负责用户界面的特定方面。

https://blog.csdn.net/pizi0475/article/details/50471198?utm_source=copy

//关于Slate
//Slate是一种用户界面架构
//Slate 是一种完全自定义的、平台无关的用户界面架构,其设计目的是使得构建工具及应用程序(比如虚幻编辑器)
//它结合了一种可以轻松设计、布局及风格化组件的声明式语法,使得可以轻松地创建用户界面并进行迭代开发。
//https://blog.csdn.net/pizi0475/article/details/50471207?utm_source=copy
//Slate控件可以用于在游戏中创建平头显示信息(HUD)或其他用户界面(UI)元素, 比如菜单。您一般可以创建一个或多个 容器 控件,
//每个容器可以包含几个其他类型的控件, 这些控件负责用户界面的特定方面。
//https://blog.csdn.net/pizi0475/article/details/50471198?utm_source=copy

//在tick时钟滴答时,任务与Slate同时执行。tick.DoAsyncEndOfFrameTasks为true
/** Task that executes concurrently with Slate when tick.DoAsyncEndOfFrameTasks is true. */
class FExecuteConcurrentWithSlateTickTask
{
TFunctionRef<void()> TickWithSlate;

public:

FExecuteConcurrentWithSlateTickTask(TFunctionRef<void()> InTickWithSlate)
: TickWithSlate(InTickWithSlate)
{
}
//获取stat id
static FORCEINLINE TStatId GetStatId()
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FExecuteConcurrentWithSlateTickTask, STATGROUP_TaskGraphTasks);
}
//获取目标线程
static FORCEINLINE ENamedThreads::Type GetDesiredThread()
{
return CPrio_AsyncEndOfFrameGameTasks.Get();
}
//获取后序的模式??
static FORCEINLINE ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }
//执行任务
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
TickWithSlate();
}
};

 RHI部分

关于RHI

RHI: Render hardware interface 渲染硬件层接口

就是包了一层图形API的图形接口,每个平台的图形API是固定的,比如PC是DX,手机是OpenGL es,他们相同功能的

这层图形接口是一样的,引擎只需要关注这层抽象出来的图形接口就可以,

这层图形接口里面有各个平台的不同图形API的相同功能的实现

https://blog.csdn.net/tuanxuan123/article/details/52914553

http://www.manew.com/thread-100777-1-1.html

//关于RHI
//RHI: Render hardware interface 渲染硬件层接口
//就是包了一层图形API的图形接口,每个平台的图形API是固定的,比如PC是DX,手机是OpenGL es,他们相同功能的
//这层图形接口是一样的,引擎只需要关注这层抽象出来的图形接口就可以,
//这层图形接口里面有各个平台的不同图形API的相同功能的实现
//https://blog.csdn.net/tuanxuan123/article/details/52914553
//http://www.manew.com/thread-100777-1-1.html

#if WITH_ENGINE
//退出并且停止RHI线程
static void RHIExitAndStopRHIThread()
{
#if HAS_GPU_STATS
FRealtimeGPUProfiler::Get()->Release();
#endif
FShaderPipelineCache::Shutdown();

// Stop the RHI Thread (using GRHIThread_InternalUseOnly is unreliable since RT may be stopped)
// //图形接口正在运行&线程正在处理任务
if (FTaskGraphInterface::IsRunning() && FTaskGraphInterface::Get().IsThreadProcessingTasks(ENamedThreads::RHIThread))
{
//获取RHI线程
DECLARE_CYCLE_STAT(TEXT("Wait For RHIThread Finish"), STAT_WaitForRHIThreadFinish, STATGROUP_TaskGraphTasks);
FGraphEventRef QuitTask = TGraphTask<FReturnGraphTask>::CreateTask(nullptr, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(ENamedThreads::RHIThread);
//等到RHI任务结束
FTaskGraphInterface::Get().WaitUntilTaskCompletes(QuitTask, ENamedThreads::GameThread_Local);
}
//退出停止RHI
RHIExit();
}
#endif

 启动部分

 打开项目工程相关的实现,设置&检测游戏名称等等

//从命令行解析游戏工程
bool ParseGameProjectFromCommandLine(const TCHAR* InCmdLine, FString& OutProjectFilePath, FString& OutGameName)
{
const TCHAR *CmdLine = InCmdLine;
FString FirstCommandLineToken = FParse::Token(CmdLine, 0);

// trim any whitespace at edges of string - this can happen if the token was quoted with leading or trailing whitespace
// VC++ tends to do this in its "external tools" config
FirstCommandLineToken.TrimStartInline();

//输出工程路径
OutProjectFilePath = TEXT("");
//输出工程名
OutGameName = TEXT("");

if ( FirstCommandLineToken.Len() && !FirstCommandLineToken.StartsWith(TEXT("-")) )
{//如果项目文件存在的话,第一个命令行参数是项目文件.或者如果不是用一个项目文件启动的话,第一个命令行参数是游戏名
// The first command line argument could be the project file if it exists or the game name if not launching with a project file
const FString ProjectFilePath = FString(FirstCommandLineToken);
if ( FPaths::GetExtension(ProjectFilePath) == FProjectDescriptor::GetExtension() )
{
OutProjectFilePath = FirstCommandLineToken;
// Here we derive the game name from the project file
// 从项目文件中推导出游戏名称
OutGameName = FPaths::GetBaseFilename(OutProjectFilePath);
return true;
}
else if (FPaths::IsRelative(FirstCommandLineToken) && FPlatformProperties::IsMonolithicBuild() == false)
{
// Full game name is assumed to be the first token
// 完整的游戏名为第一个token
OutGameName = MoveTemp(FirstCommandLineToken);
//从游戏名称中派生出项目路径。所有的游戏都必须有一个uproject文件,即使它们在根文件夹中。
// Derive the project path from the game name. All games must have a uproject file, even if they are in the root folder.
OutProjectFilePath = FPaths::Combine(*FPaths::RootDir(), *OutGameName, *FString(OutGameName + TEXT(".") + FProjectDescriptor::GetExtension()));
return true;
}
}

#if WITH_EDITOR
//解析游戏工程路径
if (FEditorCommandLineUtils::ParseGameProjectPath(InCmdLine, OutProjectFilePath, OutGameName))
{
return true;
}
#endif
return false;
}

//启动部分:设置游戏名
bool LaunchSetGameName(const TCHAR *InCmdLine, FString& OutGameProjectFilePathUnnormalized)
{
if (GIsGameAgnosticExe)
{
// Initialize GameName to an empty string. Populate it below.
FApp::SetProjectName(TEXT(""));

FString ProjFilePath;
FString LocalGameName;
if (ParseGameProjectFromCommandLine(InCmdLine, ProjFilePath, LocalGameName) == true)
{
// Only set the game name if this is NOT a program...
if (FPlatformProperties::IsProgram() == false)
{
FApp::SetProjectName(*LocalGameName);
}
OutGameProjectFilePathUnnormalized = ProjFilePath;
FPaths::SetProjectFilePath(ProjFilePath);
}
#if UE_GAME
else
{
// Try to use the executable name as the game name.
LocalGameName = FPlatformProcess::ExecutableName();
int32 FirstCharToRemove = INDEX_NONE;
if (LocalGameName.FindChar(TCHAR('-'), FirstCharToRemove))
{
LocalGameName = LocalGameName.Left(FirstCharToRemove);
}
FApp::SetProjectName(*LocalGameName);

// Check it's not UE4Game, otherwise assume a uproject file relative to the game project directory
if (LocalGameName != TEXT("UE4Game"))
{
ProjFilePath = FPaths::Combine(TEXT(".."), TEXT(".."), TEXT(".."), *LocalGameName, *FString(LocalGameName + TEXT(".") + FProjectDescriptor::GetExtension()));
OutGameProjectFilePathUnnormalized = ProjFilePath;
FPaths::SetProjectFilePath(ProjFilePath);
}
}
#endif

static bool bPrinted = false;
if (!bPrinted)
{
bPrinted = true;
if (FApp::HasProjectName())
{
UE_LOG(LogInit, Display, TEXT("Running engine for game: %s"), FApp::GetProjectName());
}
else
{
if (FPlatformProperties::RequiresCookedData())
{
UE_LOG(LogInit, Fatal, TEXT("Non-agnostic games on cooked platforms require a uproject file be specified."));
}
else
{
UE_LOG(LogInit, Display, TEXT("Running engine without a game"));
}
}
}
}
else
{
FString ProjFilePath;
FString LocalGameName;
if (ParseGameProjectFromCommandLine(InCmdLine, ProjFilePath, LocalGameName) == true)
{
if (FPlatformProperties::RequiresCookedData())
{
// Non-agnostic exes that require cooked data cannot load projects, so make sure that the LocalGameName is the GameName
if (LocalGameName != FApp::GetProjectName())
{
UE_LOG(LogInit, Fatal, TEXT("Non-agnostic games cannot load projects on cooked platforms - try running UE4Game."));
}
}
// Only set the game name if this is NOT a program...
if (FPlatformProperties::IsProgram() == false)
{
FApp::SetProjectName(*LocalGameName);
}
OutGameProjectFilePathUnnormalized = ProjFilePath;
FPaths::SetProjectFilePath(ProjFilePath);
}

// In a non-game agnostic exe, the game name should already be assigned by now.
if (!FApp::HasProjectName())
{
UE_LOG(LogInit, Fatal,TEXT("Could not set game name!"));
}
}

return true;
}

//启动部分:修正游戏名
void LaunchFixGameNameCase()
{
#if PLATFORM_DESKTOP && !IS_PROGRAM
// This is to make sure this function is not misused and is only called when the game name is set
check(FApp::HasProjectName());

// correct the case of the game name, if possible (unless we're running a program and the game name is already set)
if (FPaths::IsProjectFilePathSet())
{
const FString GameName(FPaths::GetBaseFilename(IFileManager::Get().GetFilenameOnDisk(*FPaths::GetProjectFilePath())));

const bool bGameNameMatchesProjectCaseSensitive = (FCString::Strcmp(*GameName, FApp::GetProjectName()) == 0);
if (!bGameNameMatchesProjectCaseSensitive && (FApp::IsProjectNameEmpty() || GIsGameAgnosticExe || (GameName.Len() > 0 && GIsGameAgnosticExe)))
{
if (GameName == FApp::GetProjectName()) // case insensitive compare
{
FApp::SetProjectName(*GameName);
}
else
{
const FText Message = FText::Format(
NSLOCTEXT("Core", "MismatchedGameNames", "The name of the .uproject file ('{0}') must match the name of the project passed in the command line ('{1}')."),
FText::FromString(*GameName),
FText::FromString(FApp::GetProjectName()));
if (!GIsBuildMachine)
{
UE_LOG(LogInit, Warning, TEXT("%s"), *Message.ToString());
FMessageDialog::Open(EAppMsgType::Ok, Message);
}
FApp::SetProjectName(TEXT("")); // this disables part of the crash reporter to avoid writing log files to a bogus directory
if (!GIsBuildMachine)
{
exit(1);
}
UE_LOG(LogInit, Fatal, TEXT("%s"), *Message.ToString());
}
}
}
#endif //PLATFORM_DESKTOP
}

//有条件地创建File Wrapper
static IPlatformFile* ConditionallyCreateFileWrapper(const TCHAR* Name, IPlatformFile* CurrentPlatformFile, const TCHAR* CommandLine, bool* OutFailedToInitialize = nullptr, bool* bOutShouldBeUsed = nullptr )
{
if (OutFailedToInitialize)
{
*OutFailedToInitialize = false;
}
if ( bOutShouldBeUsed )
{
*bOutShouldBeUsed = false;
}
//创建File Wrapper
IPlatformFile* WrapperFile = FPlatformFileManager::Get().GetPlatformFile(Name);
if (WrapperFile != nullptr && WrapperFile->ShouldBeUsed(CurrentPlatformFile, CommandLine))
{
if ( bOutShouldBeUsed )
{
*bOutShouldBeUsed = true;
}
if (WrapperFile->Initialize(CurrentPlatformFile, CommandLine) == false)
{
if (OutFailedToInitialize)
{
*OutFailedToInitialize = true;
}
// Don't delete the platform file. It will be automatically deleted by its module.
WrapperFile = nullptr;
}
}
else
{
// Make sure it won't be used.
WrapperFile = nullptr;
}
return WrapperFile;
}

//启动部分:检测文件覆盖
//寻找命令行上的任何的文件覆盖(例如网络连接文件处理程序)
/**
* Look for any file overrides on the command line (i.e. network connection file handler)
*/
bool LaunchCheckForFileOverride(const TCHAR* CmdLine, bool& OutFileOverrideFound)
{
//输出结果
OutFileOverrideFound = false;

// Get the physical platform file.
IPlatformFile* CurrentPlatformFile = &FPlatformFileManager::Get().GetPlatformFile();

// Try to create pak file wrapper
{
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("PakFile"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
{
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
}
PlatformFile = ConditionallyCreateFileWrapper(TEXT("CachedReadFile"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
{
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
}
}

// Try to create sandbox wrapper
{
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("SandboxFile"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
{
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
}
}

#if !UE_BUILD_SHIPPING // UFS clients are not available in shipping builds.
// Streaming network wrapper (it has a priority over normal network wrapper)
bool bNetworkFailedToInitialize = false;
do
{
bool bShouldUseStreamingFile = false;
IPlatformFile* NetworkPlatformFile = ConditionallyCreateFileWrapper(TEXT("StreamingFile"), CurrentPlatformFile, CmdLine, &bNetworkFailedToInitialize, &bShouldUseStreamingFile);
if (NetworkPlatformFile)
{
CurrentPlatformFile = NetworkPlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
}

bool bShouldUseCookedIterativeFile = false;
if ( !bShouldUseStreamingFile && !NetworkPlatformFile )
{
NetworkPlatformFile = ConditionallyCreateFileWrapper(TEXT("CookedIterativeFile"), CurrentPlatformFile, CmdLine, &bNetworkFailedToInitialize, &bShouldUseCookedIterativeFile);
if (NetworkPlatformFile)
{
CurrentPlatformFile = NetworkPlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
}
}

// if streaming network platform file was tried this loop don't try this one
// Network file wrapper (only create if the streaming wrapper hasn't been created)
if ( !bShouldUseStreamingFile && !bShouldUseCookedIterativeFile && !NetworkPlatformFile)
{
NetworkPlatformFile = ConditionallyCreateFileWrapper(TEXT("NetworkFile"), CurrentPlatformFile, CmdLine, &bNetworkFailedToInitialize);
if (NetworkPlatformFile)
{
CurrentPlatformFile = NetworkPlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
}
}

if (bNetworkFailedToInitialize)
{
FString HostIpString;
FParse::Value(CmdLine, TEXT("-FileHostIP="), HostIpString);
#if PLATFORM_REQUIRES_FILESERVER
FPlatformMisc::LowLevelOutputDebugStringf(TEXT("Failed to connect to file server at %s. RETRYING in 5s.\n"), *HostIpString);
FPlatformProcess::Sleep(5.0f);
uint32 Result = 2;
#else //PLATFORM_REQUIRES_FILESERVER
// note that this can't be localized because it happens before we connect to a filserver - localizing would cause ICU to try to load.... from over the file server connection!
FString Error = FString::Printf(TEXT("Failed to connect to any of the following file servers:\n\n %s\n\nWould you like to try again? No will fallback to local disk files, Cancel will quit."), *HostIpString.Replace( TEXT("+"), TEXT("\n ")));
uint32 Result = FMessageDialog::Open( EAppMsgType::YesNoCancel, FText::FromString( Error ) );
#endif //PLATFORM_REQUIRES_FILESERVER

if (Result == EAppReturnType::No)
{
break;
}
else if (Result == EAppReturnType::Cancel)
{
// Cancel - return a failure, and quit
return false;
}
}
}
while (bNetworkFailedToInitialize);
#endif

#if !UE_BUILD_SHIPPING
// Try to create file profiling wrapper
{
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("ProfileFile"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
{
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
}
}
{
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("SimpleProfileFile"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
{
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
}
}
// Try and create file timings stats wrapper
{
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("FileReadStats"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
{
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
}
}
// Try and create file open log wrapper (lists the order files are first opened)
{
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("FileOpenLog"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
{
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
}
}
#endif //#if !UE_BUILD_SHIPPING

// Wrap the above in a file logging singleton if requested
{
IPlatformFile* PlatformFile = ConditionallyCreateFileWrapper(TEXT("LogFile"), CurrentPlatformFile, CmdLine);
if (PlatformFile)
{
CurrentPlatformFile = PlatformFile;
FPlatformFileManager::Get().SetPlatformFile(*CurrentPlatformFile);
}
}

// If our platform file is different than it was when we started, then an override was used
OutFileOverrideFound = (CurrentPlatformFile != &FPlatformFileManager::Get().GetPlatformFile());

return true;
}

//启动部分:验证游戏名称是否合法
bool LaunchHasIncompleteGameName()
{
if ( FApp::HasProjectName() && !FPaths::IsProjectFilePathSet() )
{
// Verify this is a legitimate game name
// Launched with a game name. See if the <GameName> folder exists. If it doesn't, it could instead be <GameName>Game
const FString NonSuffixedGameFolder = FPaths::RootDir() / FApp::GetProjectName();
if (FPlatformFileManager::Get().GetPlatformFile().DirectoryExists(*NonSuffixedGameFolder) == false)
{
const FString SuffixedGameFolder = NonSuffixedGameFolder + TEXT("Game");
if (FPlatformFileManager::Get().GetPlatformFile().DirectoryExists(*SuffixedGameFolder))
{
return true;
}
}
}

return false;
}

//启动部分:更新最近的工程文件
void LaunchUpdateMostRecentProjectFile()
{
// If we are launching without a game name or project file, we should use the last used project file, if it exists
// 如果在没有游戏名称或项目文件的情况下启动,如果存在最后使用的文件。那么使用最后一个使用的项目文件
// 该方法获取最后使用的文件
const FString& AutoLoadProjectFileName = IProjectManager::Get().GetAutoLoadProjectFileName();
FString RecentProjectFileContents;
if ( FFileHelper::LoadFileToString(RecentProjectFileContents, *AutoLoadProjectFileName) )
{
if ( RecentProjectFileContents.Len() )
{
const FString AutoLoadInProgressFilename = AutoLoadProjectFileName + TEXT(".InProgress");
if ( FPlatformFileManager::Get().GetPlatformFile().FileExists(*AutoLoadInProgressFilename) )
{
// We attempted to auto-load a project but the last run did not make it to UEditorEngine::InitEditor.
// This indicates that there was a problem loading the project.
// Do not auto-load the project, instead load normally until the next time the editor starts successfully.
UE_LOG(LogInit, Display, TEXT("There was a problem auto-loading %s. Auto-load will be disabled until the editor successfully starts up with a project."), *RecentProjectFileContents);
}
else if ( FPlatformFileManager::Get().GetPlatformFile().FileExists(*RecentProjectFileContents) )
{
// The previously loaded project file was found. Change the game name here and update the project file path
FApp::SetProjectName(*FPaths::GetBaseFilename(RecentProjectFileContents));
FPaths::SetProjectFilePath(RecentProjectFileContents);
UE_LOG(LogInit, Display, TEXT("Loading recent project file: %s"), *RecentProjectFileContents);

// Write a file indicating that we are trying to auto-load a project.
// This file prevents auto-loading of projects for as long as it exists. It is a detection system for failed auto-loads.
// The file is deleted in UEditorEngine::InitEditor, thus if the load does not make it that far then the project will not be loaded again.
FFileHelper::SaveStringToFile(TEXT(""), *AutoLoadInProgressFilename);
}
}
}
}

初始化时钟

初始化一些变量

//初始化时间,
//初始化一些变量
void FEngineLoop::InitTime()
{
// Init variables used for benchmarking and ticking.
// 用于基准测试和计时的初始变量。
FApp::SetCurrentTime(FPlatformTime::Seconds());
MaxFrameCounter = 0;//最大帧计数器
MaxTickTime = 0;//最大时钟Tick时间
TotalTickTime = 0;//总共时钟Tick时间
LastFrameCycles = FPlatformTime::Cycles();//最后的帧周期

float FloatMaxTickTime = 0;//浮点数最大时钟Tick时间
#if (!UE_BUILD_SHIPPING || ENABLE_PGO_PROFILE)
FParse::Value(FCommandLine::Get(),TEXT("SECONDS="),FloatMaxTickTime);
MaxTickTime = FloatMaxTickTime;

// look of a version of seconds that only is applied if FApp::IsBenchmarking() is set. This makes it easier on
// say, iOS, where we have a toggle setting to enable benchmarking, but don't want to have to make user
// also disable the seconds setting as well. -seconds= will exit the app after time even if benchmarking
// is not enabled
// NOTE: This will override -seconds= if it's specified
// 是否以秒为基准,是的话重新获取赋值最大时钟Tick时间
if (FApp::IsBenchmarking())
{
if (FParse::Value(FCommandLine::Get(),TEXT("BENCHMARKSECONDS="),FloatMaxTickTime) && FloatMaxTickTime)
{
MaxTickTime = FloatMaxTickTime;
}
}

// Use -FPS=X to override fixed tick rate if e.g. -BENCHMARK is used.
// 使用-FPS=X来覆盖固定的tick率,例如使用了 -BENCHMARK
float FixedFPS = 0;
FParse::Value(FCommandLine::Get(),TEXT("FPS="),FixedFPS);
if( FixedFPS > 0 )
{
FApp::SetFixedDeltaTime(1 / FixedFPS);
}

#endif // !UE_BUILD_SHIPPING

// convert FloatMaxTickTime into number of frames (using 1 / FApp::GetFixedDeltaTime() to convert fps to seconds )
// 将浮点maxticktime转换成帧数(使用1/FApp:GetFixedDeltaTime()将fps转换为秒)
MaxFrameCounter = FMath::TruncToInt(MaxTickTime / FApp::GetFixedDeltaTime());
}

EngineLoop生命周期流程

引擎的整个生命周期,类似unity

初始化->循环->退出

预初始化

这部分代码1500+行,暂时不放出来代码

//预初始化
int32 FEngineLoop::PreInit(int32 ArgC, TCHAR* ArgV[], const TCHAR* AdditionalCommandline)
{
FString CmdLine;

// loop over the parameters, skipping the first one (which is the executable name)
// 循环参数,输出到CmdLine中
for (int32 Arg = 1; Arg < ArgC; Arg++)
{
FString ThisArg = ArgV[Arg];
if (ThisArg.Contains(TEXT(" ")) && !ThisArg.Contains(TEXT("\"")))
{
int32 EqualsAt = ThisArg.Find(TEXT("="));
if (EqualsAt > 0 && ThisArg.Find(TEXT(" ")) > EqualsAt)
{
ThisArg = ThisArg.Left(EqualsAt + 1) + FString("\"") + ThisArg.RightChop(EqualsAt + 1) + FString("\"");

}
else
{
ThisArg = FString("\"") + ThisArg + FString("\"");
}
}

CmdLine += ThisArg;
// put a space between each argument (not needed after the end)
if (Arg + 1 < ArgC)
{
CmdLine += TEXT(" ");
}
}

// append the additional extra command line
if (AdditionalCommandline)
{
CmdLine += TEXT(" ");
CmdLine += AdditionalCommandline;
}

// send the command line without the exe name
// 将有起动参数的CmdLine传入初始化方法,执行初始化方法
return GEngineLoop.PreInit(*CmdLine);
}

初始化

//初始化
int32 FEngineLoop::Init()
{
LLM_SCOPE(ELLMTag::EngineInitMemory);

DECLARE_SCOPE_CYCLE_COUNTER( TEXT( "FEngineLoop::Init" ), STAT_FEngineLoop_Init, STATGROUP_LoadTime );
//关于FScopedSlowTask:A scope block representing an amount of work divided up into sections. Use one scope at the top of each function to give accurate feedback to the user of a slow operation's progress.
//https://api.unrealengine.com/INT/API/Runtime/Core/Misc/FScopedSlowTask/index.html
FScopedSlowTask SlowTask(100);
//设置进入程序后帧率为10
SlowTask.EnterProgressFrame(10);

// Figure out which UEngine variant to use.
// 为编辑器/引擎生成不同的Engine Class
UClass* EngineClass = nullptr;
if( !GIsEditor )//如果不是编辑器是引擎
{
// We're the game.
FString GameEngineClassName;
GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), GameEngineClassName, GEngineIni);
//游戏引擎类名
EngineClass = StaticLoadClass( UGameEngine::StaticClass(), nullptr, *GameEngineClassName);
if (EngineClass == nullptr)
{
UE_LOG(LogInit, Fatal, TEXT("Failed to load UnrealEd Engine class '%s'."), *GameEngineClassName);
}
GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass);
}
else//如果是编辑器
{
#if WITH_EDITOR
// We're UnrealEd.
FString UnrealEdEngineClassName;
GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("UnrealEdEngine"), UnrealEdEngineClassName, GEngineIni);
//引擎编辑器类名
EngineClass = StaticLoadClass(UUnrealEdEngine::StaticClass(), nullptr, *UnrealEdEngineClassName);
if (EngineClass == nullptr)
{
UE_LOG(LogInit, Fatal, TEXT("Failed to load UnrealEd Engine class '%s'."), *UnrealEdEngineClassName);
}
GEngine = GEditor = GUnrealEd = NewObject<UUnrealEdEngine>(GetTransientPackage(), EngineClass);
#else
check(0);
#endif
}

check( GEngine );

GetMoviePlayer()->PassLoadingScreenWindowBackToGame();
//解析命令行
GEngine->ParseCommandline();
//初始化时间,初始化时间相关变量
InitTime();
//设置进入程序后帧率为60
SlowTask.EnterProgressFrame(60);
//初始化引擎
GEngine->Init(this);

// Call init callbacks
// 调用初始化回调
// 初始化后做的事情,,
// ??做了什么事情??
PRAGMA_DISABLE_DEPRECATION_WARNINGS
UEngine::OnPostEngineInit.Broadcast();
PRAGMA_ENABLE_DEPRECATION_WARNINGS
FCoreDelegates::OnPostEngineInit.Broadcast();
//设置进入程序后帧率为30
SlowTask.EnterProgressFrame(30);

// initialize engine instance discovery
// 初始化引擎实例discovery

//如果支持多线程
if (FPlatformProcess::SupportsMultithreading())
{
if (!IsRunningCommandlet())
{
//加载会话服务模块
SessionService = FModuleManager::LoadModuleChecked<ISessionServicesModule>("SessionServices").GetSessionService();
//开启会话服务
if (SessionService.IsValid())
{
SessionService->Start();
}
}
//??引擎服务??
EngineService = new FEngineService();
}

// Load all the post-engine init modules
// 载所有引擎初始化后的模块
if (!IProjectManager::Get().LoadModulesForProject(ELoadingPhase::PostEngineInit) || !IPluginManager::Get().LoadModulesForEnabledPlugins(ELoadingPhase::PostEngineInit))
{
GIsRequestingExit = true;
return 1;
}
//开始引擎
GEngine->Start();

GetMoviePlayer()->WaitForMovieToFinish();

#if !UE_SERVER
// initialize media framework
// 初始化媒体架构
IMediaModule* MediaModule = FModuleManager::LoadModulePtr<IMediaModule>("Media");

if (MediaModule != nullptr)
{
MediaModule->SetTimeSource(MakeShareable(new FAppMediaTimeSource));
}
#endif

// initialize automation worker
#if WITH_AUTOMATION_WORKER
//加载自动化工作模块
FModuleManager::Get().LoadModule("AutomationWorker");
#endif

// Automation tests can be invoked locally in non-editor builds configuration (e.g. performance profiling in Test configuration)
#if WITH_ENGINE && !UE_BUILD_SHIPPING
//加载自动化控制器模块
FModuleManager::Get().LoadModule("AutomationController");
//初始化自动化控制器模块
FModuleManager::GetModuleChecked<IAutomationControllerModule>("AutomationController").Init();
#endif

#if WITH_EDITOR
if (GIsEditor)
{
//加载 ProfilerClient模块
FModuleManager::Get().LoadModule(TEXT("ProfilerClient"));
}
//加载序列录制模块
//https://api.unrealengine.com/CHN/Engine/Sequencer/HowTo/SequenceRecorder/index.html
FModuleManager::Get().LoadModule(TEXT("SequenceRecorder"));
//加载序列录制节段模块
FModuleManager::Get().LoadModule(TEXT("SequenceRecorderSections"));
#endif
//标记为正在运行中
GIsRunning = true;
//非编辑器,设置启用渲染
if (!GIsEditor)
{
// hide a couple frames worth of rendering
// 隐藏一些值得渲染的帧
FViewport::SetGameRenderingEnabled(true, 3);
}

FCoreDelegates::StarvedGameLoop.BindStatic(&GameLoopIsStarved);

// Ready to measure thread heartbeat
// 准备测量线程心跳
FThreadHeartBeat::Get().Start();

#if defined(WITH_CODE_GUARD_HANDLER) && WITH_CODE_GUARD_HANDLER
void CheckImageIntegrity();
CheckImageIntegrity();
#endif
//发送引擎初始化完毕事件,观察者模式,广播事件
FCoreDelegates::OnFEngineLoopInitComplete.Broadcast();
return 0;
}

帧循环

//时钟(400+行代码)
//每帧循环执行的部分
void FEngineLoop::Tick()
{
#if !UE_BUILD_SHIPPING && !UE_BUILD_TEST && MALLOC_GT_HOOKS
FScopedSampleMallocChurn ChurnTracker;
#endif
// let the low level mem tracker pump once a frame to update states
// 更新Stats
LLM(FLowLevelMemTracker::Get().UpdateStatsPerFrame());

LLM_SCOPE(ELLMTag::EngineMisc);

// Send a heartbeat for the diagnostics thread
// 发送心跳线程一个心跳
FThreadHeartBeat::Get().HeartBeat(true);
FGameThreadHitchHeartBeat::Get().FrameStart();

// Make sure something is ticking the rendering tickables in -onethread mode to avoid leaks/bugs.
if (!GUseThreadedRendering && !GIsRenderingThreadSuspended.Load(EMemoryOrder::Relaxed))
{
//渲染部分的Tick
TickRenderingTickables();
}

// Ensure we aren't starting a frame while loading or playing a loading movie
// 确保在加载或播放正在加载的影片时没有启动框架
ensure(GetMoviePlayer()->IsLoadingFinished() && !GetMoviePlayer()->IsMovieCurrentlyPlaying());

#if UE_EXTERNAL_PROFILING_ENABLED
//同步分析器
FExternalProfiler* ActiveProfiler = FActiveExternalProfilerBase::GetActiveProfiler();
if (ActiveProfiler)
{
ActiveProfiler->FrameSync();
}
#endif // UE_EXTERNAL_PROFILING_ENABLED

FPlatformMisc::BeginNamedEventFrame();

uint64 CurrentFrameCounter = GFrameCounter;
SCOPED_NAMED_EVENT_F(TEXT("Frame %d"), FColor::Red, CurrentFrameCounter);

// execute callbacks for cvar changes
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_CallAllConsoleVariableSinks);
IConsoleManager::Get().CallAllConsoleVariableSinks();
}

{
SCOPE_CYCLE_COUNTER(STAT_FrameTime);

#if WITH_PROFILEGPU && !UE_BUILD_SHIPPING
// Issue the measurement of the execution time of a basic LongGPUTask unit on the very first frame
// The results will be retrived on the first call of IssueScalableLongGPUTask
if (GFrameCounter == 0 && IsFeatureLevelSupported(GMaxRHIShaderPlatform, ERHIFeatureLevel::SM4) && FApp::CanEverRender())
{
//刷新渲染指令
FlushRenderingCommands();
//入队出队渲染指令,,渲染指令在虚幻内部是一个队列数据结构
ENQUEUE_UNIQUE_RENDER_COMMAND(
MeasureLongGPUTaskExecutionTimeCmd,
{
//估计GPU任务执行时间
MeasureLongGPUTaskExecutionTime(RHICmdList);
});
}
#endif
//发送广播消息“开始帧”
FCoreDelegates::OnBeginFrame.Broadcast();

// flush debug output which has been buffered by other threads
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_FlushThreadedLogs);
GLog->FlushThreadedLogs();
}

// exit if frame limit is reached in benchmark mode, or if time limit is reached
if ((FApp::IsBenchmarking() && MaxFrameCounter && (GFrameCounter > MaxFrameCounter)) ||
(MaxTickTime && (TotalTickTime > MaxTickTime)))
{
FPlatformMisc::RequestExit(0);
}

// set FApp::CurrentTime, FApp::DeltaTime and potentially wait to enforce max tick rate
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_UpdateTimeAndHandleMaxTickRate);
GEngine->UpdateTimeAndHandleMaxTickRate();
}

// beginning of RHI frame
ENQUEUE_RENDER_COMMAND(BeginFrame)([CurrentFrameCounter](FRHICommandListImmediate& RHICmdList)
{
GRHICommandList.LatchBypass();
GFrameNumberRenderThread++;

// If we are profiling, kick off a long GPU task to make the GPU always behind the CPU so that we
// won't get GPU idle time measured in profiling results
IssueLongGPUTaskHelper();

FString FrameString = FString::Printf(TEXT("Frame %d"), CurrentFrameCounter);
FPlatformMisc::BeginNamedEvent(FColor::Yellow, *FrameString);
RHICmdList.PushEvent(*FrameString, FColor::Green);

GPU_STATS_BEGINFRAME(RHICmdList);
RHICmdList.BeginFrame();
FCoreDelegates::OnBeginFrameRT.Broadcast();
});

#if !UE_SERVER && WITH_ENGINE
if (!GIsEditor && GEngine->GameViewport && GEngine->GameViewport->GetWorld() && GEngine->GameViewport->GetWorld()->IsCameraMoveable())
{
// When not in editor, we emit dynamic resolution's begin frame right after RHI's.
GEngine->EmitDynamicResolutionEvent(EDynamicResolutionStateEvent::BeginFrame);
}
#endif

// tick performance monitoring
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_TickFPSChart);
GEngine->TickPerformanceMonitoring( FApp::GetDeltaTime() );

extern COREUOBJECT_API void ResetAsyncLoadingStats();
ResetAsyncLoadingStats();
}

// update memory allocator stats
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Malloc_UpdateStats);
GMalloc->UpdateStats();
}
}

FStats::AdvanceFrame( false, FStats::FOnAdvanceRenderingThreadStats::CreateStatic( &AdvanceRenderingThreadStatsGT ) );

{
SCOPE_CYCLE_COUNTER( STAT_FrameTime );

// Calculates average FPS/MS (outside STATS on purpose)
CalculateFPSTimings();

// Note the start of a new frame
MALLOC_PROFILER(GMalloc->Exec(nullptr, *FString::Printf(TEXT("SNAPSHOTMEMORYFRAME")),*GLog));

// handle some per-frame tasks on the rendering thread
ENQUEUE_UNIQUE_RENDER_COMMAND(
ResetDeferredUpdates,
{
FDeferredUpdateResource::ResetNeedsUpdate();
FlushPendingDeleteRHIResources_RenderThread();
});

{
SCOPE_CYCLE_COUNTER(STAT_PumpMessages);
FPlatformApplicationMisc::PumpMessages(true);
}

bool bIdleMode;
{

QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Idle);

// Idle mode prevents ticking and rendering completely
bIdleMode = ShouldUseIdleMode();
if (bIdleMode)
{
// Yield CPU time
FPlatformProcess::Sleep(.1f);
}
}

// @todo vreditor urgent: Temporary hack to allow world-to-meters to be set before
// input is polled for motion controller devices each frame.
extern ENGINE_API float GNewWorldToMetersScale;
if( GNewWorldToMetersScale != 0.0f )
{
#if WITH_ENGINE
UWorld* WorldToScale = GWorld;

#if WITH_EDITOR
if( GIsEditor && GEditor->PlayWorld != nullptr && GEditor->bIsSimulatingInEditor )
{
WorldToScale = GEditor->PlayWorld;
}
#endif //WITH_EDITOR

if( WorldToScale != nullptr )
{
if( GNewWorldToMetersScale != WorldToScale->GetWorldSettings()->WorldToMeters )
{
WorldToScale->GetWorldSettings()->WorldToMeters = GNewWorldToMetersScale;
}
}

GNewWorldToMetersScale = 0.0f;
}
#endif //WITH_ENGINE

// tick active platform files
FPlatformFileManager::Get().TickActivePlatformFile();

// process accumulated Slate input
if (FSlateApplication::IsInitialized() && !bIdleMode)
{
SCOPE_TIME_GUARD(TEXT("SlateInput"));
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_SlateInput);
LLM_SCOPE(ELLMTag::UI);

FSlateApplication& SlateApp = FSlateApplication::Get();
SlateApp.PollGameDeviceState();
// Gives widgets a chance to process any accumulated input
SlateApp.FinishedInputThisFrame();
}

#if !UE_SERVER
// tick media framework
static const FName MediaModuleName(TEXT("Media"));
IMediaModule* MediaModule = FModuleManager::LoadModulePtr<IMediaModule>(MediaModuleName);

if (MediaModule != nullptr)
{
MediaModule->TickPreEngine();
}
#endif

// main game engine tick (world, game objects, etc.)
GEngine->Tick(FApp::GetDeltaTime(), bIdleMode);

// If a movie that is blocking the game thread has been playing,
// wait for it to finish before we continue to tick or tick again
// We do this right after GEngine->Tick() because that is where user code would initiate a load / movie.
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_WaitForMovieToFinish);
GetMoviePlayer()->WaitForMovieToFinish(true);
}

if (GShaderCompilingManager)
{
// Process any asynchronous shader compile results that are ready, limit execution time
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_GShaderCompilingManager);
GShaderCompilingManager->ProcessAsyncResults(true, false);
}

if (GDistanceFieldAsyncQueue)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_GDistanceFieldAsyncQueue);
GDistanceFieldAsyncQueue->ProcessAsyncTasks();
}

#if !UE_SERVER
// tick media framework
if (MediaModule != nullptr)
{
MediaModule->TickPreSlate();
}
#endif

#if WITH_ENGINE
// process concurrent Slate tasks
FGraphEventRef ConcurrentTask;
const bool bDoConcurrentSlateTick = GEngine->ShouldDoAsyncEndOfFrameTasks();

const UGameViewportClient* const GameViewport = GEngine->GameViewport;
const UWorld* const GameViewportWorld = GameViewport ? GameViewport->GetWorld() : nullptr;
UDemoNetDriver* const CurrentDemoNetDriver = GameViewportWorld ? GameViewportWorld->DemoNetDriver : nullptr;

// Optionally validate that Slate has not modified any replicated properties for client replay recording.
FDemoSavedPropertyState PreSlateObjectStates;
const bool bValidateReplicatedProperties = CurrentDemoNetDriver && CVarDoAsyncEndOfFrameTasksValidateReplicatedProperties.GetValueOnGameThread() != 0;
if (bValidateReplicatedProperties)
{
PreSlateObjectStates = CurrentDemoNetDriver->SavePropertyState();
}

if (bDoConcurrentSlateTick)
{
const float DeltaSeconds = FApp::GetDeltaTime();

if (CurrentDemoNetDriver && CurrentDemoNetDriver->ShouldTickFlushAsyncEndOfFrame())
{
ConcurrentTask = TGraphTask<FExecuteConcurrentWithSlateTickTask>::CreateTask(nullptr, ENamedThreads::GameThread).ConstructAndDispatchWhenReady(
[CurrentDemoNetDriver, DeltaSeconds]() {
if (CVarDoAsyncEndOfFrameTasksRandomize.GetValueOnAnyThread(true) > 0)
{
FPlatformProcess::Sleep(FMath::RandRange(0.0f, .003f)); // this shakes up the threading to find race conditions
}

if (CurrentDemoNetDriver != nullptr)
{
CurrentDemoNetDriver->TickFlushAsyncEndOfFrame(DeltaSeconds);
}
});
}
}
#endif

// tick Slate application
if (FSlateApplication::IsInitialized() && !bIdleMode)
{
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_ProcessPlayerControllersSlateOperations);
check(!IsRunningDedicatedServer());

// Process slate operations accumulated in the world ticks.
ProcessLocalPlayerSlateOperations();
}

FSlateApplication::Get().Tick();
}

#if WITH_ENGINE
if (bValidateReplicatedProperties)
{
const bool bReplicatedPropertiesDifferent = CurrentDemoNetDriver->ComparePropertyState(PreSlateObjectStates);
if (bReplicatedPropertiesDifferent)
{
UE_LOG(LogInit, Log, TEXT("Replicated properties changed during Slate tick!"));
}
}

if (ConcurrentTask.GetReference())
{
CSV_SCOPED_TIMING_STAT(Basic, ConcurrentWithSlateTickTasks_Wait);

QUICK_SCOPE_CYCLE_COUNTER(STAT_ConcurrentWithSlateTickTasks_Wait);
FTaskGraphInterface::Get().WaitUntilTaskCompletes(ConcurrentTask);
ConcurrentTask = nullptr;
}
{
ENQUEUE_UNIQUE_RENDER_COMMAND(WaitForOutstandingTasksOnly_for_DelaySceneRenderCompletion,
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DelaySceneRenderCompletion_TaskWait);
FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::WaitForOutstandingTasksOnly);
});
}
#endif

#if STATS
// Clear any stat group notifications we have pending just in case they weren't claimed during FSlateApplication::Get().Tick
extern CORE_API void ClearPendingStatGroups();
ClearPendingStatGroups();
#endif

#if WITH_EDITOR && !UE_BUILD_SHIPPING
// tick automation controller (Editor only)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_AutomationController);
static FName AutomationController("AutomationController");
if (FModuleManager::Get().IsModuleLoaded(AutomationController))
{
FModuleManager::GetModuleChecked<IAutomationControllerModule>(AutomationController).Tick();
}
}
#endif

#if WITH_ENGINE && WITH_AUTOMATION_WORKER
// tick automation worker
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_AutomationWorker);
static const FName AutomationWorkerModuleName = TEXT("AutomationWorker");
if (FModuleManager::Get().IsModuleLoaded(AutomationWorkerModuleName))
{
FModuleManager::GetModuleChecked<IAutomationWorkerModule>(AutomationWorkerModuleName).Tick();
}
}
#endif

// tick render hardware interface
{
SCOPE_CYCLE_COUNTER(STAT_RHITickTime);
RHITick( FApp::GetDeltaTime() ); // Update RHI.
}

// Increment global frame counter. Once for each engine tick.
GFrameCounter++;

// Disregard first few ticks for total tick time as it includes loading and such.
if (GFrameCounter > 6)
{
TotalTickTime += FApp::GetDeltaTime();
}

// Find the objects which need to be cleaned up the next frame.
FPendingCleanupObjects* PreviousPendingCleanupObjects = PendingCleanupObjects;
PendingCleanupObjects = GetPendingCleanupObjects();

{
SCOPE_CYCLE_COUNTER(STAT_FrameSyncTime);
// this could be perhaps moved down to get greater parallelism
// Sync game and render thread. Either total sync or allowing one frame lag.
static FFrameEndSync FrameEndSync;
static auto CVarAllowOneFrameThreadLag = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.OneFrameThreadLag"));
FrameEndSync.Sync( CVarAllowOneFrameThreadLag->GetValueOnGameThread() != 0 );
}

// tick core ticker, threads & deferred commands
{
SCOPE_CYCLE_COUNTER(STAT_DeferredTickTime);
// Delete the objects which were enqueued for deferred cleanup before the previous frame.
delete PreviousPendingCleanupObjects;

#if WITH_COREUOBJECT
DeleteLoaders(); // destroy all linkers pending delete
#endif

FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime());
FThreadManager::Get().Tick();
GEngine->TickDeferredCommands();
}

#if !UE_SERVER
// tick media framework
if (MediaModule != nullptr)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_MediaTickPostRender);
MediaModule->TickPostRender();
}
#endif

FCoreDelegates::OnEndFrame.Broadcast();

#if !UE_SERVER && WITH_ENGINE
{
// We emit dynamic resolution's end frame right before RHI's. GEngine is going to ignore it if no BeginFrame was done.
GEngine->EmitDynamicResolutionEvent(EDynamicResolutionStateEvent::EndFrame);
}
#endif

// end of RHI frame
ENQUEUE_UNIQUE_RENDER_COMMAND(EndFrame,
{
FCoreDelegates::OnEndFrameRT.Broadcast();
RHICmdList.EndFrame();
GPU_STATS_ENDFRAME(RHICmdList);
RHICmdList.PopEvent();
FPlatformMisc::EndNamedEvent();
});

// Set CPU utilization stats.
const FCPUTime CPUTime = FPlatformTime::GetCPUTime();
SET_FLOAT_STAT( STAT_CPUTimePct, CPUTime.CPUTimePct );
SET_FLOAT_STAT( STAT_CPUTimePctRelative, CPUTime.CPUTimePctRelative );

// Set the UObject count stat
#if UE_GC_TRACK_OBJ_AVAILABLE
SET_DWORD_STAT(STAT_Hash_NumObjects, GUObjectArray.GetObjectArrayNumMinusAvailable());
#endif
}
}

退出

//退出时要执行的方法
//各种销毁/关闭/清理/置空/停止线程/停止服务
void FEngineLoop::Exit()
{
STAT_ADD_CUSTOMMESSAGE_NAME( STAT_NamedMarker, TEXT( "EngineLoop.Exit" ) );
//运行标记置为false
GIsRunning = 0;
//Log控制台置空
GLogConsole = nullptr;

// shutdown visual logger and flush all data
// 关闭visual logger并刷新所有数据
#if ENABLE_VISUAL_LOG
FVisualLogger::Get().Shutdown();
#endif


// Make sure we're not in the middle of loading something.
// 刷新异步加载,确保没有在加载东西
FlushAsyncLoading();

// Block till all outstanding resource streaming requests are fulfilled.
// 直到所有资源流请求都全部完成
if (!IStreamingManager::HasShutdown())
{
UTexture2D::CancelPendingTextureStreaming();
IStreamingManager::Get().BlockTillAllRequestsFinished();
}

#if WITH_ENGINE
// shut down messaging
// 关闭消息
delete EngineService;
//引擎服务器置空
EngineService = nullptr;
//停止会话服务器
if (SessionService.IsValid())
{
SessionService->Stop();
SessionService.Reset();
}
//停止DistanceFieldAsyncQueue
if (GDistanceFieldAsyncQueue)
{
GDistanceFieldAsyncQueue->Shutdown();
delete GDistanceFieldAsyncQueue;
}
#endif // WITH_ENGINE
//停止声音设备管理
if ( GEngine != nullptr )
{
GEngine->ShutdownAudioDeviceManager();
}
//Pre退出
if ( GEngine != nullptr )
{
GEngine->PreExit();
}

// close all windows
// 关闭程序,关闭窗口
FSlateApplication::Shutdown();

#if !UE_SERVER
//销毁引擎字体服务
if ( FEngineFontServices::IsInitialized() )
{
FEngineFontServices::Destroy();
}
#endif

#if WITH_EDITOR
// These module must be shut down first because other modules may try to access them during shutdown.
// Accessing these modules at shutdown causes instability since the object system will have been shut down and these modules uses uobjects internally.
//卸载资源工具模块
FModuleManager::Get().UnloadModule("AssetTools", true);

#endif // WITH_EDITOR
//卸载资源注册模块
FModuleManager::Get().UnloadModule("AssetRegistry", true);
//非安卓平台走下面部分
#if !PLATFORM_ANDROID || PLATFORM_LUMIN // AppPreExit doesn't work on Android
AppPreExit();

TermGamePhys();
ParticleVertexFactoryPool_FreePool();
#else
// AppPreExit() stops malloc profiler, do it here instead
MALLOC_PROFILER( GMalloc->Exec(nullptr, TEXT("MPROF STOP"), *GLog); );
#endif // !ANDROID

#if WITH_PROFILEGPU
//清理
ClearLongGPUTaskQueries();
#endif

// Stop the rendering thread.
// 停止渲染线程
StopRenderingThread();


// Disable the shader cache
// 关闭着色器缓存
FShaderCache::ShutdownShaderCache();

// Close shader code map, if any
// 关闭着色器code map
FShaderCodeLibrary::Shutdown();

// Tear down the RHI.
// 推出并停止RHI线程
RHIExitAndStopRHIThread();

#if !PLATFORM_ANDROID || PLATFORM_LUMIN // UnloadModules doesn't work on Android
#if WITH_ENGINE
// Save the hot reload state
IHotReloadInterface* HotReload = IHotReloadInterface::GetPtr();
if(HotReload != nullptr)
{
HotReload->SaveConfig();
}
#endif
//卸载所有模块
//注意,这实际上并没有卸载模块的dll,卸载dll会发生在操作系统控制程序退出的时候
// Unload all modules. Note that this doesn't actually unload the module DLLs (that happens at
// process exit by the OS), but it does call ShutdownModule() on all loaded modules in the reverse
// order they were loaded in, so that systems can unregister and perform general clean up.
FModuleManager::Get().UnloadModulesAtShutdown();
#endif // !ANDROID
//销毁MoviePlayer
DestroyMoviePlayer();

// Move earlier?
// 停止stats线程(分析??)
// http://api.unrealengine.com/CHN/Engine/Performance/StatCommands/
// https://www.unrealengine.com/zh-CN/blog/how-to-improve-game-thread-cpu-performance
#if STATS
FThreadStats::StopThread();
#endif
//停止任务图接口
FTaskGraphInterface::Shutdown();
//停止流管理
IStreamingManager::Shutdown();
//平台相关杂项 停止并标记为存储
FPlatformMisc::ShutdownTaggedStorage();
}

加载模块

各个部分的模块,包含各个部分的功能

不同的模块在流程的不同位置加载

核心模块

CoreUObject模块

//加载核心模块
bool FEngineLoop::LoadCoreModules()
{
// Always attempt to load CoreUObject. It requires additional pre-init which is called from its module's StartupModule method.
#if WITH_COREUOBJECT
return FModuleManager::Get().LoadModule(TEXT("CoreUObject")) != nullptr;
#else
return true;
#endif
}

初始化模块

引擎模块/渲染器模块/实时动画图形模块/Slate RHI渲染器模块

地形模块/shader核心模块/图片压缩模块/音效编辑器模块/动画调节器模块

//加载初始化模块
void FEngineLoop::LoadPreInitModules()
{
DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading PreInit Modules"), STAT_PreInitModules, STATGROUP_LoadTime);

// GGetMapNameDelegate is initialized here
#if WITH_ENGINE
//引擎模块
FModuleManager::Get().LoadModule(TEXT("Engine"));
//渲染器模块
FModuleManager::Get().LoadModule(TEXT("Renderer"));
//实时动画图形模块
FModuleManager::Get().LoadModule(TEXT("AnimGraphRuntime"));

FPlatformApplicationMisc::LoadPreInitModules();

#if !UE_SERVER
if (!IsRunningDedicatedServer() )
{
if (!GUsingNullRHI)
{
//Slate RHI渲染器模块
// This needs to be loaded before InitializeShaderTypes is called
FModuleManager::Get().LoadModuleChecked<ISlateRHIRendererModule>("SlateRHIRenderer");
}
}
#endif
//地形模块
FModuleManager::Get().LoadModule(TEXT("Landscape"));

// Initialize ShaderCore before loading or compiling any shaders,
// But after Renderer and any other modules which implement shader types.
// shader核心模块
FModuleManager::Get().LoadModule(TEXT("ShaderCore"));

#if WITH_EDITORONLY_DATA
// Load the texture compressor module before any textures load. They may
// compress asynchronously and that can lead to a race condition.
// 图片压缩模块
FModuleManager::Get().LoadModule(TEXT("TextureCompressor"));
#endif

#endif // WITH_ENGINE

#if (WITH_EDITOR && !(UE_BUILD_SHIPPING || UE_BUILD_TEST))
// Load audio editor module before engine class CDOs are loaded
// 音效编辑器模块
FModuleManager::Get().LoadModule(TEXT("AudioEditor"));
//动画调节器模块
//https://docs.unrealengine.com/en-us/Engine/Animation/AnimModifiers
FModuleManager::Get().LoadModule(TEXT("AnimationModifiers"));
#endif
}

启动核心模块

比较多,看代码注释把

//加载启动核心模块
bool FEngineLoop::LoadStartupCoreModules()
{
FScopedSlowTask SlowTask(100);

DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading Startup Modules"), STAT_StartupModules, STATGROUP_LoadTime);

bool bSuccess = true;

// Load all Runtime modules
SlowTask.EnterProgressFrame(10);
{
//???核心模块??
FModuleManager::Get().LoadModule(TEXT("Core"));
//网络模块
FModuleManager::Get().LoadModule(TEXT("Networking"));
}

SlowTask.EnterProgressFrame(10);
FPlatformApplicationMisc::LoadStartupModules();

// initialize messaging
SlowTask.EnterProgressFrame(10);
if (FPlatformProcess::SupportsMultithreading())
{
//消息模块
FModuleManager::LoadModuleChecked<IMessagingModule>("Messaging");
}

// Init Scene Reconstruction support
#if !UE_SERVER
if (!IsRunningDedicatedServer())
{
//Mixed Reality Mesh混合现实网格模块
//https://docs.unrealengine.com/en-us/Platforms/AR/MagicLeap
FModuleManager::LoadModuleChecked<IMRMeshModule>("MRMesh");
}
#endif

SlowTask.EnterProgressFrame(10);
#if WITH_EDITOR
//编辑器style模块,应该是一种UI风格
//https://blog.csdn.net/lqpgfz/article/details/45225389
FModuleManager::LoadModuleChecked<IEditorStyleModule>("EditorStyle");
#endif //WITH_EDITOR

// Load UI modules
// 加载UI模块
SlowTask.EnterProgressFrame(10);
if ( !IsRunningDedicatedServer() )
{
//Slate模块
FModuleManager::Get().LoadModule("Slate");

#if !UE_BUILD_SHIPPING
// Need to load up the SlateReflector module to initialize the WidgetSnapshotService
FModuleManager::Get().LoadModule("SlateReflector");
#endif // !UE_BUILD_SHIPPING
}

#if WITH_EDITOR
// In dedicated server builds with the editor, we need to load UMG/UMGEditor for compiling blueprints.
// UMG must be loaded for runtime and cooking.
FModuleManager::Get().LoadModule("Slate");
#else
if ( !IsRunningDedicatedServer() )
{
//关于UMG:UMG is Blueprint extension of Slate(虚幻动态图形)
//http://aigo.iteye.com/blog/2268505
//http://api.unrealengine.com/CHN/Engine/UMG/UserGuide/BestPractices/index.html
// UMG must be loaded for runtime and cooking. 必须在运行时加载
// UMG模块
FModuleManager::Get().LoadModule("UMG");
}
#endif //WITH_EDITOR

// Load all Development modules
SlowTask.EnterProgressFrame(20);
if (!IsRunningDedicatedServer())
{
#if WITH_UNREAL_DEVELOPER_TOOLS
//消息日志模块
FModuleManager::Get().LoadModule("MessageLog");
//碰撞分析模块
FModuleManager::Get().LoadModule("CollisionAnalyzer");
#endif //WITH_UNREAL_DEVELOPER_TOOLS
}

#if WITH_UNREAL_DEVELOPER_TOOLS
//功能测试模块
FModuleManager::Get().LoadModule("FunctionalTesting");
#endif //WITH_UNREAL_DEVELOPER_TOOLS

SlowTask.EnterProgressFrame(30);
#if (WITH_EDITOR && !(UE_BUILD_SHIPPING || UE_BUILD_TEST))
// HACK: load BT editor as early as possible for statically initialized assets (non cooked BT assets needs it)
// cooking needs this module too
//行为树编辑器模块
FModuleManager::Get().LoadModule(TEXT("BehaviorTreeEditor"));

// Ability tasks are based on GameplayTasks, so we need to make sure that module is loaded as well
// 游戏逻辑任务编辑器模块
FModuleManager::Get().LoadModule(TEXT("GameplayTasksEditor"));

IAudioEditorModule* AudioEditorModule = &FModuleManager::LoadModuleChecked<IAudioEditorModule>("AudioEditor");
AudioEditorModule->RegisterAssetActions();

// Load the StringTableEditor module to register its asset actions
// StringTable编辑器模块
FModuleManager::Get().LoadModule("StringTableEditor");

if( !IsRunningDedicatedServer() )
{
// VREditor needs to be loaded in non-server editor builds early, so engine content Blueprints can be loaded during DDC generation
//VR编辑器
FModuleManager::Get().LoadModule(TEXT("VREditor"));
}
// -----------------------------------------------------

// HACK: load EQS editor as early as possible for statically initialized assets (non cooked EQS assets needs it)
// cooking needs this module too
bool bEnvironmentQueryEditor = false;
GConfig->GetBool(TEXT("EnvironmentQueryEd"), TEXT("EnableEnvironmentQueryEd"), bEnvironmentQueryEditor, GEngineIni);
if (bEnvironmentQueryEditor
#if WITH_EDITOR
|| GetDefault<UEditorExperimentalSettings>()->bEQSEditor
#endif // WITH_EDITOR
)
{
//EnvironmentQuery场景查询编辑器
//http://gad.qq.com/program/translateview/7189967
FModuleManager::Get().LoadModule(TEXT("EnvironmentQueryEditor"));
}

// We need this for blueprint projects that have online functionality.
//FModuleManager::Get().LoadModule(TEXT("OnlineBlueprintSupport"));

if (IsRunningCommandlet())
{
//介绍教程模块
FModuleManager::Get().LoadModule(TEXT("IntroTutorials"));
//Blutility模块
//https://zhuanlan.zhihu.com/p/41799378
FModuleManager::Get().LoadModule(TEXT("Blutility"));
}

#endif //(WITH_EDITOR && !(UE_BUILD_SHIPPING || UE_BUILD_TEST))

#if WITH_ENGINE
// Load runtime client modules (which are also needed at cook-time)
if( !IsRunningDedicatedServer() )
{
//Overlay模块
FModuleManager::Get().LoadModule(TEXT("Overlay"));
}
//媒体资源模块
FModuleManager::Get().LoadModule(TEXT("MediaAssets"));
#endif
//衣服系统运行时模块
FModuleManager::Get().LoadModule(TEXT("ClothingSystemRuntime"));
#if WITH_EDITOR
//衣服系统编辑器模块
FModuleManager::Get().LoadModule(TEXT("ClothingSystemEditor"));
#endif
//数据包处理模块
FModuleManager::Get().LoadModule(TEXT("PacketHandler"));


return bSuccess;
}

启动模块

//加载启动模块
//应该是加载自定义插件的地方,虚幻预置了三个加载次序,可以通过外部设置来加载
//在Plugins.CHN.udn发现了这个描述
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;
}

EngineLoop静态接口

包括应用程序的生命周期流程

应用程序初始化

//应用程序初始化(400+行代码)
bool FEngineLoop::AppInit( )
{
BeginInitTextLocalization();

// Avoiding potential exploits by not exposing command line overrides in the shipping games.
#if !UE_BUILD_SHIPPING && WITH_EDITORONLY_DATA
FString CmdLineFile;

if (FParse::Value(FCommandLine::Get(), TEXT("-CmdLineFile="), CmdLineFile))
{
if (CmdLineFile.EndsWith(TEXT(".txt")))
{
FString FileCmds;

if (FFileHelper::LoadFileToString(FileCmds, *CmdLineFile))
{
FileCmds = FString(TEXT(" ")) + FileCmds.TrimStartAndEnd();

if (FileCmds.Len() > 1)
{
UE_LOG(LogInit, Log, TEXT("Appending commandline from file:%s"), *FileCmds);

FCommandLine::Append(*FileCmds);
}
}
else
{
UE_LOG(LogInit, Warning, TEXT("Failed to load commandline file '%s'."), *CmdLineFile);
}
}
else
{
UE_LOG(LogInit, Warning, TEXT("Can only load commandline files ending with .txt, can't load: %s"), *CmdLineFile);
}
}


// 8192 is the maximum length of the command line on Windows XP.
TCHAR CmdLineEnv[8192];

// Retrieve additional command line arguments from environment variable.
FPlatformMisc::GetEnvironmentVariable(TEXT("UE-CmdLineArgs"), CmdLineEnv,ARRAY_COUNT(CmdLineEnv));

// Manually nullptr terminate just in case. The nullptr string is returned above in the error case so
// we don't have to worry about that.
CmdLineEnv[ARRAY_COUNT(CmdLineEnv)-1] = 0;
FString Env = FString(CmdLineEnv).TrimStart();

if (Env.Len())
{
// Append the command line environment after inserting a space as we can't set it in the
// environment. Note that any code accessing GCmdLine before appInit obviously won't
// respect the command line environment additions.
FCommandLine::Append(TEXT(" -EnvAfterHere "));
FCommandLine::Append(CmdLineEnv);
}
#endif

// Error history.
FCString::Strcpy(GErrorHist, TEXT("Fatal error!" LINE_TERMINATOR LINE_TERMINATOR));

// Platform specific pre-init.
FPlatformMisc::PlatformPreInit();
FPlatformApplicationMisc::PreInit();

// Keep track of start time.
GSystemStartTime = FDateTime::Now().ToString();

// Switch into executable's directory.
FPlatformProcess::SetCurrentWorkingDirectoryToBaseDir();

// Now finish initializing the file manager after the command line is set up
IFileManager::Get().ProcessCommandLineOptions();

FPageAllocator::LatchProtectedMode();

if (FParse::Param(FCommandLine::Get(), TEXT("purgatorymallocproxy")))
{
FMemory::EnablePurgatoryTests();
}

if (FParse::Param(FCommandLine::Get(), TEXT("poisonmallocproxy")))
{
FMemory::EnablePoisonTests();
}

#if !UE_BUILD_SHIPPING
if (FParse::Param(FCommandLine::Get(), TEXT("BUILDMACHINE")))
{
GIsBuildMachine = true;
}

// If "-WaitForDebugger" was specified, halt startup and wait for a debugger to attach before continuing
if( FParse::Param( FCommandLine::Get(), TEXT( "WaitForDebugger" ) ) )
{
while( !FPlatformMisc::IsDebuggerPresent() )
{
FPlatformProcess::Sleep( 0.1f );
}
}
#endif // !UE_BUILD_SHIPPING

#if PLATFORM_WINDOWS

// make sure that the log directory exists
IFileManager::Get().MakeDirectory( *FPaths::ProjectLogDir() );

// update the mini dump filename now that we have enough info to point it to the log folder even in installed builds
FCString::Strcpy(MiniDumpFilenameW, *IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*FString::Printf(TEXT("%sunreal-v%i-%s.dmp"), *FPaths::ProjectLogDir(), FEngineVersion::Current().GetChangelist(), *FDateTime::Now().ToString())));
#endif

// Init logging to disk
FPlatformOutputDevices::SetupOutputDevices();

{
LLM_SCOPE(ELLMTag::ConfigSystem);
// init config system
FConfigCacheIni::InitializeConfigSystem();
}

// Now that configs have been initialized, setup stack walking options
FPlatformStackWalk::Init();

#if WITH_EDITOR
FBlueprintSupport::InitializeCompilationManager();
#endif

CheckForPrintTimesOverride();

IPluginManager& PluginManager = IPluginManager::Get();
IProjectManager& ProjectManager = IProjectManager::Get();

// Check whether the project or any of its plugins are missing or are out of date
#if UE_EDITOR && !IS_MONOLITHIC
if(!GIsBuildMachine && FPaths::IsProjectFilePathSet() && PluginManager.AreRequiredPluginsAvailable())
{
bool bNeedCompile = false;
GConfig->GetBool(TEXT("/Script/UnrealEd.EditorLoadingSavingSettings"), TEXT("bForceCompilationAtStartup"), bNeedCompile, GEditorPerProjectIni);
if(FParse::Param(FCommandLine::Get(), TEXT("SKIPCOMPILE")) || FParse::Param(FCommandLine::Get(), TEXT("MULTIPROCESS")))
{
bNeedCompile = false;
}
if(!bNeedCompile)
{
// Check if any of the project or plugin modules are out of date, and the user wants to compile them.
TArray<FString> IncompatibleFiles;
ProjectManager.CheckModuleCompatibility(IncompatibleFiles);
PluginManager.CheckModuleCompatibility(IncompatibleFiles);

if (IncompatibleFiles.Num() > 0)
{
// Log the modules which need to be rebuilt
for (int Idx = 0; Idx < IncompatibleFiles.Num(); Idx++)
{
UE_LOG(LogInit, Warning, TEXT("Incompatible or missing module: %s"), *IncompatibleFiles[Idx]);
}

// Build the error message for the dialog box
FString ModulesList = TEXT("The following modules are missing or built with a different engine version:\n\n");

int NumModulesToDisplay = (IncompatibleFiles.Num() <= 20)? IncompatibleFiles.Num() : 15;
for (int Idx = 0; Idx < NumModulesToDisplay; Idx++)
{
ModulesList += FString::Printf(TEXT(" %s\n"), *IncompatibleFiles[Idx]);
}
if(IncompatibleFiles.Num() > NumModulesToDisplay)
{
ModulesList += FString::Printf(TEXT(" (+%d others, see log for details)\n"), IncompatibleFiles.Num() - NumModulesToDisplay);
}

ModulesList += TEXT("\nWould you like to rebuild them now?");

// If we're running with -stdout, assume that we're a non interactive process and about to fail
if (FApp::IsUnattended() || FParse::Param(FCommandLine::Get(), TEXT("stdout")))
{
return false;
}

// Ask whether to compile before continuing
if (FPlatformMisc::MessageBoxExt(EAppMsgType::YesNo, *ModulesList, *FString::Printf(TEXT("Missing %s Modules"), FApp::GetProjectName())) == EAppReturnType::No)
{
return false;
}

bNeedCompile = true;
}
}

if(bNeedCompile)
{
// Try to compile it
FFeedbackContext *Context = (FFeedbackContext*)FDesktopPlatformModule::Get()->GetNativeFeedbackContext();
Context->BeginSlowTask(FText::FromString(TEXT("Starting build...")), true, true);
bool bCompileResult = FDesktopPlatformModule::Get()->CompileGameProject(FPaths::RootDir(), FPaths::GetProjectFilePath(), Context);
Context->EndSlowTask();

// Get a list of modules which are still incompatible
TArray<FString> StillIncompatibleFiles;
ProjectManager.CheckModuleCompatibility(StillIncompatibleFiles);
PluginManager.CheckModuleCompatibility(StillIncompatibleFiles);

if(!bCompileResult || StillIncompatibleFiles.Num() > 0)
{
for (int Idx = 0; Idx < StillIncompatibleFiles.Num(); Idx++)
{
UE_LOG(LogInit, Warning, TEXT("Still incompatible or missing module: %s"), *StillIncompatibleFiles[Idx]);
}
if (!FApp::IsUnattended())
{
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *FString::Printf(TEXT("%s could not be compiled. Try rebuilding from source manually."), FApp::GetProjectName()), TEXT("Error"));
}
return false;
}
}
}
#endif

// Put the command line and config info into the suppression system (before plugins start loading)
FLogSuppressionInterface::Get().ProcessConfigAndCommandLine();

// NOTE: This is the earliest place to init the online subsystems (via plugins)
// Code needs GConfigFile to be valid
// Must be after FThreadStats::StartThread();
// Must be before Render/RHI subsystem D3DCreate() for platform services that need D3D hooks like Steam

// Load "pre-init" plugin modules
if (!ProjectManager.LoadModulesForProject(ELoadingPhase::PostConfigInit) || !PluginManager.LoadModulesForEnabledPlugins(ELoadingPhase::PostConfigInit))
{
return false;
}

// Register the callback that allows the text localization manager to load data for plugins
FCoreDelegates::GatherAdditionalLocResPathsCallback.AddLambda([&PluginManager](TArray<FString>& OutLocResPaths)
{
PluginManager.GetLocalizationPathsForEnabledPlugins(OutLocResPaths);
});

PreInitHMDDevice();

// after the above has run we now have the REQUIRED set of engine .INIs (all of the other .INIs)
// that are gotten from .h files' config() are not requires and are dynamically loaded when the .u files are loaded

#if !UE_BUILD_SHIPPING
// Prompt the user for remote debugging?
bool bPromptForRemoteDebug = false;
GConfig->GetBool(TEXT("Engine.ErrorHandling"), TEXT("bPromptForRemoteDebugging"), bPromptForRemoteDebug, GEngineIni);
bool bPromptForRemoteDebugOnEnsure = false;
GConfig->GetBool(TEXT("Engine.ErrorHandling"), TEXT("bPromptForRemoteDebugOnEnsure"), bPromptForRemoteDebugOnEnsure, GEngineIni);

if (FParse::Param(FCommandLine::Get(), TEXT("PROMPTREMOTEDEBUG")))
{
bPromptForRemoteDebug = true;
}

if (FParse::Param(FCommandLine::Get(), TEXT("PROMPTREMOTEDEBUGENSURE")))
{
bPromptForRemoteDebug = true;
bPromptForRemoteDebugOnEnsure = true;
}

FPlatformMisc::SetShouldPromptForRemoteDebugging(bPromptForRemoteDebug);
FPlatformMisc::SetShouldPromptForRemoteDebugOnEnsure(bPromptForRemoteDebugOnEnsure);

// Feedback context.
if (FParse::Param(FCommandLine::Get(), TEXT("WARNINGSASERRORS")))
{
GWarn->TreatWarningsAsErrors = true;
}

if (FParse::Param(FCommandLine::Get(), TEXT("SILENT")))
{
GIsSilent = true;
}

if (FParse::Param(FCommandLine::Get(), TEXT("RUNNINGUNATTENDEDSCRIPT")))
{
GIsRunningUnattendedScript = true;
}

#endif // !UE_BUILD_SHIPPING

// Show log if wanted.
if (GLogConsole && FParse::Param(FCommandLine::Get(), TEXT("LOG")))
{
GLogConsole->Show(true);
}

//// Command line.
UE_LOG(LogInit, Log, TEXT("Build: %s"), FApp::GetBuildVersion());
UE_LOG(LogInit, Log, TEXT("Engine Version: %s"), *FEngineVersion::Current().ToString());
UE_LOG(LogInit, Log, TEXT("Compatible Engine Version: %s"), *FEngineVersion::CompatibleWith().ToString());
UE_LOG(LogInit, Log, TEXT("Net CL: %u"), FNetworkVersion::GetNetworkCompatibleChangelist());
FString OSLabel, OSVersion;
FPlatformMisc::GetOSVersions(OSLabel, OSVersion);
UE_LOG(LogInit, Log, TEXT("OS: %s (%s), CPU: %s, GPU: %s"), *OSLabel, *OSVersion, *FPlatformMisc::GetCPUBrand(), *FPlatformMisc::GetPrimaryGPUBrand());

#if PLATFORM_64BITS
UE_LOG(LogInit, Log, TEXT("Compiled (64-bit): %s %s"), ANSI_TO_TCHAR(__DATE__), ANSI_TO_TCHAR(__TIME__));
#else
UE_LOG(LogInit, Log, TEXT("Compiled (32-bit): %s %s"), ANSI_TO_TCHAR(__DATE__), ANSI_TO_TCHAR(__TIME__));
#endif

// Print compiler version info
#if defined(__clang__)
UE_LOG(LogInit, Log, TEXT("Compiled with Clang: %s"), ANSI_TO_TCHAR( __clang_version__ ) );
#elif defined(__INTEL_COMPILER)
UE_LOG(LogInit, Log, TEXT("Compiled with ICL: %d"), __INTEL_COMPILER);
#elif defined( _MSC_VER )
#ifndef __INTELLISENSE__ // Intellisense compiler doesn't support _MSC_FULL_VER
{
const FString VisualCPPVersion( FString::Printf( TEXT( "%d" ), _MSC_FULL_VER ) );
const FString VisualCPPRevisionNumber( FString::Printf( TEXT( "%02d" ), _MSC_BUILD ) );
UE_LOG(LogInit, Log, TEXT("Compiled with Visual C++: %s.%s.%s.%s"),
*VisualCPPVersion.Mid( 0, 2 ), // Major version
*VisualCPPVersion.Mid( 2, 2 ), // Minor version
*VisualCPPVersion.Mid( 4 ), // Build version
*VisualCPPRevisionNumber // Revision number
);
}
#endif
#else
UE_LOG(LogInit, Log, TEXT("Compiled with unrecognized C++ compiler") );
#endif

UE_LOG(LogInit, Log, TEXT("Build Configuration: %s"), EBuildConfigurations::ToString(FApp::GetBuildConfiguration()));
UE_LOG(LogInit, Log, TEXT("Branch Name: %s"), *FApp::GetBranchName() );
FString FilteredString = FCommandLine::IsCommandLineLoggingFiltered() ? TEXT("Filtered ") : TEXT("");
UE_LOG(LogInit, Log, TEXT("%sCommand Line: %s"), *FilteredString, FCommandLine::GetForLogging() );
UE_LOG(LogInit, Log, TEXT("Base Directory: %s"), FPlatformProcess::BaseDir() );
//UE_LOG(LogInit, Log, TEXT("Character set: %s"), sizeof(TCHAR)==1 ? TEXT("ANSI") : TEXT("Unicode") );
UE_LOG(LogInit, Log, TEXT("Installed Engine Build: %d"), FApp::IsEngineInstalled() ? 1 : 0);

FDevVersionRegistration::DumpVersionsToLog();

// if a logging build, clear out old log files
#if !NO_LOGGING
FMaintenance::DeleteOldLogs();
#endif

#if !UE_BUILD_SHIPPING
FApp::InitializeSession();
#endif

// Checks.
check(sizeof(uint8) == 1);
check(sizeof(int8) == 1);
check(sizeof(uint16) == 2);
check(sizeof(uint32) == 4);
check(sizeof(uint64) == 8);
check(sizeof(ANSICHAR) == 1);

#if PLATFORM_TCHAR_IS_4_BYTES
check(sizeof(TCHAR) == 4);
#else
check(sizeof(TCHAR) == 2);
#endif

check(sizeof(int16) == 2);
check(sizeof(int32) == 4);
check(sizeof(int64) == 8);
check(sizeof(bool) == 1);
check(sizeof(float) == 4);
check(sizeof(double) == 8);

// Init list of common colors.
GColorList.CreateColorMap();

bool bForceSmokeTests = false;
GConfig->GetBool(TEXT("AutomationTesting"), TEXT("bForceSmokeTests"), bForceSmokeTests, GEngineIni);
bForceSmokeTests |= FParse::Param(FCommandLine::Get(), TEXT("bForceSmokeTests"));
FAutomationTestFramework::Get().SetForceSmokeTests(bForceSmokeTests);

// Init other systems.
FCoreDelegates::OnInit.Broadcast();
return true;
}

应用程序退出之前

//应用程序退出之前
void FEngineLoop::AppPreExit( )
{
//输出log 准备退出
UE_LOG(LogExit, Log, TEXT("Preparing to exit.") );
//广播状态“准备退出”
FCoreDelegates::OnPreExit.Broadcast();
//为分析器分配内存
MALLOC_PROFILER( GMalloc->Exec(nullptr, TEXT("MPROF STOP"), *GLog); );

#if WITH_ENGINE
if (FString(FCommandLine::Get()).Contains(TEXT("CreatePak")) && GetDerivedDataCache())
{
// if we are creating a Pak, we need to make sure everything is done and written before we exit
UE_LOG(LogInit, Display, TEXT("Closing DDC Pak File."));
GetDerivedDataCacheRef().WaitForQuiescence(true);
}
#endif

#if WITH_EDITOR
FRemoteConfig::Flush();
#endif
//广播状态“正在退出”
FCoreDelegates::OnExit.Broadcast();

#if WITH_EDITOR
//销毁线程池
if (GLargeThreadPool != nullptr)
{
GLargeThreadPool->Destroy();
}
#endif // WITH_EDITOR

// Clean up the thread pool
//销毁三种线程池
if (GThreadPool != nullptr)
{
GThreadPool->Destroy();
}

if (GBackgroundPriorityThreadPool != nullptr)
{
GBackgroundPriorityThreadPool->Destroy();
}

if (GIOThreadPool != nullptr)
{
GIOThreadPool->Destroy();
}

#if WITH_ENGINE
//关闭shader编译管理,清理置空
if ( GShaderCompilingManager )
{
GShaderCompilingManager->Shutdown();

delete GShaderCompilingManager;
GShaderCompilingManager = nullptr;
}
#endif
}

应用程序退出

//应用程序退出
void FEngineLoop::AppExit( )
{
#if !WITH_ENGINE
// when compiled WITH_ENGINE, this will happen in FEngineLoop::Exit()
FTaskGraphInterface::Shutdown();
#endif // WITH_ENGINE

UE_LOG(LogExit, Log, TEXT("Exiting."));

FPlatformApplicationMisc::TearDown();
FPlatformMisc::PlatformTearDown();

if (GConfig)
{
GConfig->Exit();
delete GConfig;
GConfig = nullptr;
}

if( GLog )
{
GLog->TearDown();
}

FInternationalization::TearDown();
}
//初始化RHI之后
void FEngineLoop::PostInitRHI()
{
#if WITH_ENGINE
TArray<uint32> PixelFormatByteWidth;
PixelFormatByteWidth.AddUninitialized(PF_MAX);
for (int i = 0; i < PF_MAX; i++)
{
PixelFormatByteWidth[i] = GPixelFormats[i].BlockBytes;
}
RHIPostInit(PixelFormatByteWidth);
#endif
}

其他

//预初始化HMD设备
void FEngineLoop::PreInitHMDDevice()
{
#if WITH_ENGINE && !UE_SERVER
if (!FParse::Param(FCommandLine::Get(), TEXT("nohmd")) && !FParse::Param(FCommandLine::Get(), TEXT("emulatestereo")))
{
// Get a list of modules that implement this feature
FName Type = IHeadMountedDisplayModule::GetModularFeatureName();
IModularFeatures& ModularFeatures = IModularFeatures::Get();
TArray<IHeadMountedDisplayModule*> HMDModules = ModularFeatures.GetModularFeatureImplementations<IHeadMountedDisplayModule>(Type);

// Check whether the user passed in an explicit HMD module on the command line
FString ExplicitHMDName;
bool bUseExplicitHMDName = FParse::Value(FCommandLine::Get(), TEXT("hmd="), ExplicitHMDName);

// Iterate over modules, checking ExplicitHMDName and calling PreInit
for (auto HMDModuleIt = HMDModules.CreateIterator(); HMDModuleIt; ++HMDModuleIt)
{
IHeadMountedDisplayModule* HMDModule = *HMDModuleIt;


bool bUnregisterHMDModule = false;
if (bUseExplicitHMDName)
{
TArray<FString> HMDAliases;
HMDModule->GetModuleAliases(HMDAliases);
HMDAliases.Add(HMDModule->GetModuleKeyName());

bUnregisterHMDModule = true;
for (const FString& HMDModuleName : HMDAliases)
{
if (ExplicitHMDName.Equals(HMDModuleName, ESearchCase::IgnoreCase))
{
bUnregisterHMDModule = false;
break;
}
}
}
else
{
bUnregisterHMDModule = !HMDModule->PreInit();
}

if (bUnregisterHMDModule)
{
// Unregister modules which don't match ExplicitHMDName, or which fail PreInit
ModularFeatures.UnregisterModularFeature(Type, HMDModule);
}
}
// Note we do not disable or warn here if no HMD modules matched ExplicitHMDName, as not all HMD plugins have been loaded yet.
}
#endif // #if WITH_ENGINE && !UE_SERVER
}

猜你喜欢

转载自blog.csdn.net/wolf96/article/details/82828435