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

Posted wolf96

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了虚幻4引擎源码学习笔记:主循环LaunchEngineLoop相关的知识,希望对你有一定的参考价值。

点此查看大图

虚幻引擎主循环为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"));
//网络模块
FM

以上是关于虚幻4引擎源码学习笔记:主循环LaunchEngineLoop的主要内容,如果未能解决你的问题,请参考以下文章

学习笔记Unreal(虚幻)4引擎入门

如何学习大型项目的源码?虚幻引擎源码学习思路分享

Epic Games官方<<理解项目和文件结构>>虚幻引擎教程学习笔记

虚幻引擎 4 (ue4) 中带有自定义 c++ 蓝图函数库的蓝图循环/for/while 节点

UE5学习笔记——蓝图基础之入门

Unreal Engine 大象无形学习笔记 (第一部分:虚幻C++编程)