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

虚幻4引擎源码学习笔记(二):主循环LaunchEngineLoop_第1张图片

点此查看大图

虚幻引擎主循环为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 
#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 
#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 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 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 ExitPhrases;
public:
FOutputDeviceTestExit(const TArray& 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 GScopedLogConsole;
static TUniquePtr GScopedStdOut;
static TUniquePtr 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();
//把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 TickWithSlate;

public:

FExecuteConcurrentWithSlateTickTask(TFunctionRef 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::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  folder exists. If it doesn't, it could instead be 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(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(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("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("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("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(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::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(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(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("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("Messaging");
}

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

SlowTask.EnterProgressFrame(10);
#if WITH_EDITOR
//编辑器style模块,应该是一种UI风格
//https://blog.csdn.net/lqpgfz/article/details/45225389
FModuleManager::LoadModuleChecked("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("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()->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 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 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& 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 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 HMDModules = ModularFeatures.GetModularFeatureImplementations(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 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
}

 

 

 

你可能感兴趣的:(Game,Engine,虚幻4引擎源码学习笔记)