Unreal源码百问

Unreal

Posted by Bob on October 23, 2024

简约写的一些读源码笔记,行文胡乱没章法,仅供备忘。

1.如何识别引擎和插件DLL编译不匹配?

检查调用堆栈

int32 GuardedMain( const TCHAR* CmdLine )
EnginePreInit( CmdLine );
int32 FEngineLoop::PreInit(const TCHAR* CmdLine)
int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine)
bool FEngineLoop::AppInit( )
ProjectManager.CheckModuleCompatibility(IncompatibleFiles);
PluginManager.CheckModuleCompatibility(IncompatibleFiles, IncompatibleEngineFiles);
FModuleDescriptor::CheckModuleCompatibility(CurrentProject->Modules, OutIncompatibleModules);
ModuleManager.IsModuleUpToDate(Module.Name)
FModuleManifest::TryRead(FileName, Manifest)

通过***.modules文件记录编译信息,通过FSimpleParse解析这个文件,格式如下

{
	"BuildId": "488e2f37-bf07-4f46-8a3e-5e5a4141fbac",
	"Modules": 
	{
		"ShootLikeMe": "UnrealEditor-ShootLikeMe.dll",
		"ShootLikeMeEditor": "UnrealEditor-ShootLikeMeEditor.dll"
	}
}

FModuleManifest::TryRead中比较BuildId是否匹配,最后ModuleManager.IsModuleUpToDate中检查Modules中的Dll是否存在

2.启动项目何时编译c++代码?

如果ide(eg:vs/rider)启动游戏,在启动引擎前就会编译好所有dll文件。 如果不是ide启动,实际调用UBT去处理编译(实际还是调用ide依赖的编译器比如msvc),当然前置会做一些检查工作。 如果Intermediate目录还在,Binaries删了,应该是只有链接过程,很快就能在Binaries下生成dll。 如果编译的dll版本不匹配会提示:The following modules are missing or built with a different engine version

ProjectManager.CheckModuleCompatibility和PluginManager.CheckModuleCompatibility检查dll版本不匹配或者不存在就会启动编译 编译主要逻辑在DesktopPlatformWindows.cpp,实际是调用的RunUnrealBuildTool来编译

int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine)
bool bCompileResult = FDesktopPlatformModule::Get()->CompileGameProject(FPaths::RootDir(), FPaths::GetProjectFilePath(), Context, &CompilationResult);
FFeedbackContextMarkup::PipeProcessOutput(Description, UnrealBuildToolPath, Arguments, Warn, &OutExitCode) && OutExitCode == 0;

启动UBT的参数例如:”E:/Dev_5.4/Editor/Engine/Build/BatchFiles/Build.bat Development Win64 -Project=”E:/Dev_5.4/Project/**/**.uproject” -TargetType=Editor -Progress -NoEngineChanges -NoHotReloadFromIDE”

UnrealTargetPlatform:Win64 Mac IOS Android Linux UnrealTargetConfiguration: Debug DebugGame Development Test Shipping

最后调用dotnet Engine\Binaries\DotNET\UnrealBuildTool\UnrealBuildTool.dll

UnrealBuildTool.exe参数

Global options:                                                                                                                                                  
  -Help                :  Display this help.                                                                                                                     
  -Verbose             :  Increase output verbosity                                                                                                              
  -VeryVerbose         :  Increase output verbosity more                                                                                                         
  -Log                 :  Specify a log file location instead of the default Engine/Programs/UnrealBuildTool/Log.txt                                             
  -TraceWrites         :  Trace writes requested to the specified file                                                                                           
  -Timestamps          :  Include timestamps in the log                                                                                                          
  -FromMsBuild         :  Format messages for msbuild                                                                                                            
  -SuppressSDKWarnings :  Missing SDKs error verbosity level will be reduced from warning to log                                                                 
  -Progress            :  Write progress messages in a format that can be parsed by other programs                                                               
  -NoMutex             :  Allow more than one instance of the program to run at once                                                                             
  -WaitMutex           :  Wait for another instance to finish and then start, rather than aborting immediately                                                   
  -RemoteIni           :  Remote tool ini directory                                                                                                              
  -Mode=               :  Select tool mode. One of the following (default tool mode is "Build"):                                                                 
                            AggregateClangTimingInfo, AggregateParsedTimingInfo, Analyze, ApplePostBuildSync, Build,                                             
                            ClRepro, Clean, Deploy, Execute, FixIncludePaths, GenerateClangDatabase, GenerateProjectFiles,                                       
                            IOSPostBuildSync, IWYU, InlineGeneratedCpps, JsonExport, PVSGather, ParseMsvcTimingInfo,                                             
                            PipInstall, PrintBuildGraphInfo, ProfileUnitySizes, Query, QueryTargets, Server, SetupPlatforms,                                     
                            Test, UnrealHeaderTool, ValidatePlatforms, WriteDocumentation, WriteMetadata                                                         
  -Clean               :  Clean build products. Equivalent to -Mode=Clean                                                                                        
  -ProjectFiles        :  Generate project files based on IDE preference. Equivalent to -Mode=GenerateProjectFiles                                               
  -ProjectFileFormat=  :  Generate project files in specified format. May be used multiple times.                                                                
  -Makefile            :  Generate Linux Makefile                                                                                                                
  -CMakefile           :  Generate project files for CMake                                                                                                       
  -QMakefile           :  Generate project files for QMake                                                                                                       
  -KDevelopfile        :  Generate project files for KDevelop                                                                                                    
  -CodeliteFiles       :  Generate project files for Codelite                                                                                                    
  -XCodeProjectFiles   :  Generate project files for XCode                                                                                                       
  -EddieProjectFiles   :  Generate project files for Eddie                                                                                                       
  -VSCode              :  Generate project files for Visual Studio Code                                                                                          
  -VSMac               :  Generate project files for Visual Studio Mac                                                                                           
  -CLion               :  Generate project files for CLion                                                                                                       
  -Rider               :  Generate project files for Rider   

可看出生成uproject也是UnrealBuildTool操办。

UnrealBuildTool是个C#工程 E:\Dev_5.4\Editor\Engine\Source\Programs\UnrealBuildTool

编译日志:C:\Users\happyelements\AppData\Local\UnrealBuildTool\Log.txt

Compiler: C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.38.33130\bin\Hostx64\x64\cl.exe Linker: C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.38.33130\bin\Hostx64\x64\link.exe Library Manager: C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.38.33130\bin\Hostx64\x64\lib.exe

UnrealBuildTool Building Log堆栈

private static int Main(string[] ArgumentsArray)
ToolMode Mode = (ToolMode)Activator.CreateInstance(ModeType)!;
Result = Mode.ExecuteAsync(Arguments, Logger).GetAwaiter().GetResult();
	BuildConfiguration BuildConfiguration = new BuildConfiguration();
			XmlConfig.ApplyTo(BuildConfiguration);
			Arguments.ApplyTo(BuildConfiguration);

await BuildAsync(TargetDescriptors, BuildConfiguration, WorkingSet, Options, WriteOutdatedActionsFile, Logger, bSkipPreBuildTargets);
TargetMakefile NewMakefile = await CreateMakefileAsync(BuildConfiguration, TargetDescriptors[Idx], WorkingSet, Logger);
Target = UEBuildTarget.Create(TargetDescriptor, BuildConfiguration, Logger);
public void PreBuildSetup(ILogger Logger)
Logger.LogDebug("Building {AppName} - {TargetName} - {Platform} - {Configuration}", AppName, TargetName, Platform, Configuration);

3.启动项目何时编译蓝图?

在启项目时加载所有Uobject初始化蓝图编译器FBlueprintCompilationManager

PreInitPostStartupScreen(CmdLine)
ProcessNewlyLoadedUObjects();
UObjectLoadAllCompiledInDefaultProperties(AllNewClasses);
FBlueprintCompilationManager::NotifyBlueprintLoaded(UBlueprint* BPLoaded)

点编辑器蓝图编译按钮时调用如下函数编译blueprint FBlueprintCompilationManager::CompileSynchronously(const FBPCompileRequest& Request)

启动项目时不会主动编译所有蓝图,需要启动设置参数UnrealEditor-Cmd.exe MyGame\MyProject.uproject -RUN=CompileAllBlueprints 在PreInitPreStartupScreen函数中解析所有的Commandlet参数,编译蓝图启动的是UCompileAllBlueprintsCommandlet去处理所有蓝图

int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine)

//解析参数
if (ParsedSwitch.StartsWith(TEXT("RUN=")))
{
	FString LocalToken = ParsedSwitch.RightChop(4);
	LocalToken.TrimStartAndEndInline();
	if (!LocalToken.EndsWith(TEXT("Commandlet")))
	{
		LocalToken += TEXT("Commandlet");
	}
	SetIsRunningAsCommandlet(LocalToken);
	break;
}

int32 UCompileAllBlueprintsCommandlet::Main(const FString& Params)

//编译蓝图实际调用CompileSynchronously
FKismetEditorUtilities::CompileBlueprint(Blueprint, EBlueprintCompileOptions::SkipGarbageCollection, &MessageLog);
FBlueprintCompilationManager::CompileSynchronously(FBPCompileRequest(BlueprintObj, CompileFlags, pResults));

收集所有蓝图用到模块FAssetRegistryModule,文档说明如下 https://dev.epicgames.com/documentation/zh-cn/unreal-engine/asset-registry-in-unreal-engine?application_version=5.3

void UCompileAllBlueprintsCommandlet::BuildBlueprintAssetList()
{
	BlueprintAssetList.Empty();

	UE_LOG(LogCompileAllBlueprintsCommandlet, Display, TEXT("Loading Asset Registry..."));
	FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(AssetRegistryConstants::ModuleName);
	AssetRegistryModule.Get().SearchAllAssets(/*bSynchronousSearch =*/true);
	UE_LOG(LogCompileAllBlueprintsCommandlet, Display, TEXT("Finished Loading Asset Registry."));
	
	UE_LOG(LogCompileAllBlueprintsCommandlet, Display, TEXT("Gathering All Blueprints From Asset Registry..."));
	AssetRegistryModule.Get().GetAssetsByClass(BlueprintBaseClassName, BlueprintAssetList, true);
}

4.启动项目何时检查资源?

PreInitPostStartupScreen
FModuleManager::Get().LoadModule("AssetRegistry");
GetDefault<UAssetRegistryImpl>();
void FAssetRegistryImpl::Initialize(Impl::FInitializeContext& Context)
void FAssetRegistryImpl::SearchAllAssetsInitialAsync(Impl::FEventContext& EventContext,
	Impl::FClassInheritanceContext& InheritanceContext)
void FAssetRegistryImpl::SearchAllAssets(Impl::FEventContext& EventContext,
	Impl::FClassInheritanceContext& InheritanceContext, bool bSynchronousSearch)
{
	TRACE_BEGIN_REGION(TEXT("Asset Registry Scan"));

FAssetRegistryModule提供加载uasset转uobject的途径,可以查询操作资产,管理了资产的引用和依赖关系。如果要修复引用或者删除没引用的资产可以从这里开始。 AssetDataGatherer负责收集所有资产生成CachedAssetRegistry_*.bin

AssetRegistry耗时分布图 Asset Registry Scan (50.4s) UAssetRegistryImpl::SearchAllAssets (4s) image

5.FEngineLoop::Init()结束前干了什么?

  • Config
  • PluginManager
  • AssetRegistry
  • RenderDocPlugin
  • Device
  • Config CVar
  • RHI
  • Memory
  • TextLocalizationResource
  • RHI
  • AssetRegistry
  • DeviceProfileManager
  • ShaderCompilers
  • LiveCoding
  • Python
  • Initializing Engine…
  • Initializing game features subsystem
  • Texture streaming
  • Audio相关
  • SourceControl
  • 各种Profiler:网络/内存/时间/加载
  • Display: Engine is initialized. Leaving FEngineLoop::Init()
  • Load Map
  • PakFile
  • (Engine Initialization) Total time: 51.72 seconds

6.如何收集所有多语言?

UStabilizeLocalizationKeysCommandlet里处理

if (UUserDefinedStruct* const UserDefinedStruct = Cast<UUserDefinedStruct>(Obj))

	if (UUserDefinedStructEditorData* UDSEditorData = Cast<UUserDefinedStructEditorData>(UserDefinedStruct->EditorData))
	{
		for (FStructVariableDescription& StructVariableDesc : UDSEditorData->VariablesDescriptions)
		{
			static const FName TextCategory = TEXT("text"); // Must match UEdGraphSchema_K2::PC_Text
			if (StructVariableDesc.Category == TextCategory)
			{
				FText StructVariableValue;
				if (FTextStringHelper::ReadFromBuffer(*StructVariableDesc.DefaultValue, StructVariableValue) && KeyText(StructVariableValue))
				{
					FTextStringHelper::WriteToBuffer(StructVariableDesc.DefaultValue, StructVariableValue);
				}
			}
		}
	}

初始引擎多语言

int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine)
// InitEngineTextLocalization loads Paks, needs Oodle(压缩工具) to be setup before here
InitEngineTextLocalization();
ApplyDefaultCultureSettings(ApplyLocLoadFlags);

初始日志

LogInit: Overriding language with editor language configuration option (zh-Hans).
LogInit: Using OS detected locale (zh-CN).
LogTextLocalizationResource: LocRes '../../../Engine/Content/Localization/Editor/zh/Editor.locres' could not be opened for reading!
LogTextLocalizationResource: LocRes '../../../Engine/Content/Localization/EditorTutorials/zh/EditorTutorials.locres' could not be opened for reading!
LogTextLocalizationResource: LocRes '../../../Engine/Content/Localization/Keywords/zh/Keywords.locres' could not be opened for reading!
LogTextLocalizationResource: LocRes '../../../Engine/Content/Localization/Category/zh/Category.locres' could not be opened for reading!
LogTextLocalizationResource: LocRes '../../../Engine/Content/Localization/ToolTips/zh/ToolTips.locres' could not be opened for reading!
LogTextLocalizationResource: LocRes '../../../Engine/Content/Localization/Engine/zh/Engine.locres' could not be opened for reading!
LogTextLocalizationResource: LocRes '../../../Engine/Plugins/Online/OnlineSubsystemUtils/Content/Localization/OnlineSubsystemUtils/zh/OnlineSubsystemUtils.locres' could not be opened for reading!
LogTextLocalizationResource: LocRes '../../../Engine/Plugins/Online/OnlineSubsystem/Content/Localization/OnlineSubsystem/zh/OnlineSubsystem.locres' could not be opened for reading!
LogTextLocalizationResource: LocRes '../../../Engine/Plugins/Online/Android/OnlineSubsystemGooglePlay/Content/Localization/OnlineSubsystemGooglePlay/zh/OnlineSubsystemGooglePlay.locres' could not be opened for reading!
LogTextLocalizationResource: LocRes '../../../Engine/Plugins/Online/IOS/OnlineSubsystemIOS/Content/Localization/OnlineSubsystemIOS/zh/OnlineSubsystemIOS.locres' could not be opened for reading!

多语言文件

"Source":
{
	"Text": "(No slot name)\nSlot"
},
"Translation":
{
	"Text": "(没有插槽名称)\n插槽"
},
"Key": "SlotNodeTitle_NoName"

7.基本数据类型定义有哪些?

自定义的一些类型有:FText,FString,FName,TMap,TSet,TArray

基本数据类型有:bool,float,double等;其他有如下针对平台定义的

//------------------------------------------------------------------
// Transfer the platform types to global types
//------------------------------------------------------------------

//~ Unsigned base types.
/// An 8-bit unsigned integer.
typedef FPlatformTypes::uint8		uint8;
/// A 16-bit unsigned integer.
typedef FPlatformTypes::uint16		uint16;
/// A 32-bit unsigned integer.
typedef FPlatformTypes::uint32		uint32;
/// A 64-bit unsigned integer.
typedef FPlatformTypes::uint64		uint64;

//~ Signed base types.
/// An 8-bit signed integer.
typedef	FPlatformTypes::int8		int8;
/// A 16-bit signed integer.
typedef FPlatformTypes::int16		int16;
/// A 32-bit signed integer.
typedef FPlatformTypes::int32		int32;
/// A 64-bit signed integer.
typedef FPlatformTypes::int64		int64;

//~ Character types.
/// An ANSI character. Normally a signed type.
typedef FPlatformTypes::ANSICHAR	ANSICHAR;
/// A wide character. Normally a signed type.
typedef FPlatformTypes::WIDECHAR	WIDECHAR;
/// Either ANSICHAR or WIDECHAR, depending on whether the platform supports wide characters or the requirements of the licensee.
typedef FPlatformTypes::TCHAR		TCHAR;
/// An 8-bit character containing a UTF8 (Unicode, 8-bit, variable-width) code unit.
typedef FPlatformTypes::UTF8CHAR	UTF8CHAR;
/// A 16-bit character containing a UCS2 (Unicode, 16-bit, fixed-width) code unit, used for compatibility with 'Windows TCHAR' across multiple platforms.
typedef FPlatformTypes::CHAR16		UCS2CHAR;
/// A 16-bit character containing a UTF16 (Unicode, 16-bit, variable-width) code unit.
typedef FPlatformTypes::CHAR16		UTF16CHAR;
/// A 32-bit character containing a UTF32 (Unicode, 32-bit, fixed-width) code unit.
typedef FPlatformTypes::CHAR32		UTF32CHAR;

/// An unsigned integer the same size as a pointer
typedef FPlatformTypes::UPTRINT UPTRINT;
/// A signed integer the same size as a pointer
typedef FPlatformTypes::PTRINT PTRINT;
/// An unsigned integer the same size as a pointer, the same as UPTRINT
typedef FPlatformTypes::SIZE_T SIZE_T;
/// An integer the same size as a pointer, the same as PTRINT
typedef FPlatformTypes::SSIZE_T SSIZE_T;

/// The type of the NULL constant.
typedef FPlatformTypes::TYPE_OF_NULL	TYPE_OF_NULL;
/// The type of the C++ nullptr keyword.
typedef FPlatformTypes::TYPE_OF_NULLPTR	TYPE_OF_NULLPTR;
/**
* Generic types for almost all compilers and platforms
**/
struct FGenericPlatformTypes
{
	//~ Unsigned base types
	
	// 8-bit unsigned integer
	typedef unsigned char 		uint8;
	
	// 16-bit unsigned integer
	typedef unsigned short int	uint16;
	
	// 32-bit unsigned integer
	typedef unsigned int		uint32;
	
	// 64-bit unsigned integer
	typedef unsigned long long	uint64;

	//~ Signed base types.
	
	// 8-bit signed integer
	typedef	signed char			int8;
	
	// 16-bit signed integer
	typedef signed short int	int16;
	
	// 32-bit signed integer
	typedef signed int	 		int32;
	
	// 64-bit signed integer
	typedef signed long long	int64;

	//~ Character types
	
	// An ANSI character. 8-bit fixed-width representation of 7-bit characters.
	typedef char				ANSICHAR;
	
	// A wide character. In-memory only. ?-bit fixed-width representation of the platform's natural wide character set. Could be different sizes on different platforms.
	typedef wchar_t				WIDECHAR;
	
	// An 8-bit character type. In-memory only. 8-bit representation. Should really be char8_t but making this the generic option is easier for compilers which don't fully support C++20 yet.
	enum UTF8CHAR : unsigned char {};
	
	// An 8-bit character type. In-memory only. 8-bit representation.
	/* UE_DEPRECATED(5.0) */[[deprecated("FPlatformTypes::CHAR8 is deprecated, please use FPlatformTypes::UTF8CHAR instead.")]]
	typedef uint8				CHAR8;
	
	// A 16-bit character type. In-memory only.  16-bit representation. Should really be char16_t but making this the generic option is easier for compilers which don't fully support C++11 yet (i.e. MSVC).
	typedef uint16				CHAR16;		
	
	// A 32-bit character type. In-memory only. 32-bit representation. Should really be char32_t but making this the generic option is easier for compilers which don't fully support C++11 yet (i.e. MSVC).
	typedef uint32				CHAR32;
	
	// A switchable character. In-memory only. Either ANSICHAR or WIDECHAR, depending on a licensee's requirements.
	typedef WIDECHAR			TCHAR;

	// Unsigned int. The same size as a pointer.
	typedef SelectIntPointerType<uint32, uint64, sizeof(void*)>::TIntPointer UPTRINT;
	
	// Signed int. The same size as a pointer.
	typedef SelectIntPointerType<int32, int64, sizeof(void*)>::TIntPointer PTRINT;
	
	// Unsigned int. The same size as a pointer.
	typedef UPTRINT SIZE_T;
	
	// Signed int. The same size as a pointer.
	typedef PTRINT SSIZE_T;

	typedef int32					TYPE_OF_NULL;
	typedef decltype(nullptr)		TYPE_OF_NULLPTR;
};

8.支持的c++版本是多少?

UE5.4.1

namespace UnrealBuildTool
{
  /// <summary>
  /// Specifies which language standard to use. This enum should be kept in order, so that toolchains can check whether the requested setting is &gt;= values that they support.
  /// </summary>
  public enum CppStandardVersion
  {
    /// <summary>
    /// Supports C++14. No longer maintained, will be removed in 5.5
    /// </summary>
    Cpp14 = 0,
    /// <summary>Supports C++17</summary>
    Cpp17 = 1,
    /// <summary>Supports C++20</summary>
    Cpp20 = 2,
    /// <summary>
    /// Use the default standard version (BuildSettingsVersion.V1-V3: Cpp17, V4: Cpp20)
    /// </summary>
    Default = 2,
    /// <summary>Use the default standard version for engine modules</summary>
    EngineDefault = 2,
    /// <summary>Latest standard supported by the compiler</summary>
    Latest = 3,
  }
}

9.DateTime计量单位

1 秒(s) = 1000 毫秒(ms) 1 毫秒(ms) = 1000 微秒(μs) 1 微秒(μs) = 1000 纳秒(ns)

1 秒(s) = 1000×1000×1000 纳秒(ns) 1 秒(s) = 1000×1000 微秒(μs)

FDateTime中Tick计量单位为100 nanoseconds

struct FDateTime
{
/** Holds the ticks in 100 nanoseconds resolution since January 1, 0001 A.D. */
	int64 Ticks;
}

//时间范围
bool FDateTime::Validate(int32 Year, int32 Month, int32 Day, int32 Hour, int32 Minute, int32 Second, int32 Millisecond)
{
	return (Year >= 1) && (Year <= 9999) &&
		(Month >= 1) && (Month <= 12) &&
		(Day >= 1) && (Day <= DaysInMonth(Year, Month)) &&
		(Hour >= 0) && (Hour <= 23) &&
		(Minute >= 0) && (Minute <= 59) &&
		(Second >= 0) && (Second <= 59) &&
		(Millisecond >= 0) && (Millisecond <= 999);
}
/**
 * Time span related constants.
 */
namespace ETimespan
{
	/** The maximum number of ticks that can be represented in FTimespan. */
	inline constexpr int64 MaxTicks = 9223372036854775807;

	/** The minimum number of ticks that can be represented in FTimespan. */
	inline constexpr int64 MinTicks = -9223372036854775807 - 1;

	/** The number of nanoseconds per tick. */
	inline constexpr int64 NanosecondsPerTick = 100;

	/** The number of timespan ticks per day. */
	inline constexpr int64 TicksPerDay = 864000000000;

	/** The number of timespan ticks per hour. */
	inline constexpr int64 TicksPerHour = 36000000000;

	/** The number of timespan ticks per microsecond. */
	inline constexpr int64 TicksPerMicrosecond = 10;

	/** The number of timespan ticks per millisecond. */
	inline constexpr int64 TicksPerMillisecond = 10000;

	/** The number of timespan ticks per minute. */
	inline constexpr int64 TicksPerMinute = 600000000;

	/** The number of timespan ticks per second. */
	inline constexpr int64 TicksPerSecond = 10000000;

	/** The number of timespan ticks per week. */
	inline constexpr int64 TicksPerWeek = 6048000000000;

	/** The number of timespan ticks per year (365 days, not accounting for leap years). */
	inline constexpr int64 TicksPerYear = 365 * TicksPerDay;
}

10.LaunchWindowsStartup做了什么?

TRACE_BOOKMARK(TEXT(“WinMain.Enter”)); UnrealInsights可看到 image

__asan_set_error_report_callback(ASanErrorCallback); AddressSanitizer(ASan)是一个用于检测内存错误的工具,它在编译时通过对代码进行插桩(instrumentation)来工作。主要用于发现诸如缓冲区溢出、使用已释放的内存、栈缓冲区溢出等多种内存错误。 内存越界访问/使用已释放的内存/栈缓冲区溢出

_set_invalid_parameter_handler 非法参数的处理

_CrtSetReportMode 断言的错误输出到调试器的输出窗口

_CrtSetDebugFillThreshold( 0 );

https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2008/ms235389(v=vs.90)

https://stackoverflow.com/questions/370195/when-and-why-will-a-compiler-initialise-memory-to-0xcd-0xdd-etc-on-malloc-fre

在内存调试过程中,有时候会对未初始化的内存区域进行填充操作,以便在后续程序运行过程中能够通过观察这些填充值来发现可能存在的内存错误,比如访问了未初始化的内存区域等情况

bool bNoExceptionHandler = FParse::Param(CmdLine,TEXT(“noexceptionhandler”)); (void)bNoExceptionHandler; 在一个作用域内声明了一个变量,但后续没有对其进行任何实质性的使用(比如没有参与任何运算、没有作为函数参数传递等),有些编译器会发出警告,提示存在未使用的变量。通过将变量强制转换为 void 类型,就相当于告诉编译器 “我知道这个变量在这里暂时没做什么实际用途,但我就是要声明它,别给我发警告了”。

ErrorLevel = GuardedMainWrapper( CmdLine ); 内部异常处理程序会捕获原生 C++ 代码中的崩溃和断言情况,并且在运行 64 位可执行文件时,它是获取正确调用栈的唯一途径。然而,XAudio2(可能是一个音频相关的组件或库)不喜欢这种情况,并且这可能会导致没有声音(即音频无法正常播放)。


/**
 * Setup the common debug settings 
 */
void SetupWindowsEnvironment( void )
{
	// all crt validation should trigger the callback
	_set_invalid_parameter_handler(InvalidParameterHandler);

#if UE_BUILD_DEBUG
	// Disable the message box for assertions and just write to debugout instead
	_CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_DEBUG );
	// don't fill buffers with 0xfd as we make assumptions for FNames st we only use a fraction of the entire buffer
	_CrtSetDebugFillThreshold( 0 );
#endif
}

/**
 * The inner exception handler catches crashes/asserts in native C++ code and is the only way to get the correct callstack
 * when running a 64-bit executable. However, XAudio2 doesn't like this and it may result in no sound.
 */
LAUNCH_API int32 GuardedMainWrapper( const TCHAR* CmdLine )
{
	int32 ErrorLevel = 0;
	if ( GEnableInnerException )
	{
#if !PLATFORM_SEH_EXCEPTIONS_DISABLED
	 	__try
#endif
		{
			// Run the guarded code.
			ErrorLevel = GuardedMain( CmdLine );
		}
#if !PLATFORM_SEH_EXCEPTIONS_DISABLED
		__except( FPlatformMisc::GetCrashHandlingType() == ECrashHandlingType::Default ? (ReportCrash( GetExceptionInformation()), EXCEPTION_CONTINUE_SEARCH) : EXCEPTION_CONTINUE_SEARCH )
		{
			// Deliberately do nothing but avoid warning C6322: Empty _except block.
			(void)0;
		}
#endif
	}
	else
	{
		// Run the guarded code.
		ErrorLevel = GuardedMain( CmdLine );
	}
	return ErrorLevel;
}

LAUNCH_API int32 LaunchWindowsStartup( HINSTANCE hInInstance, HINSTANCE hPrevInstance, char*, int32 nCmdShow, const TCHAR* CmdLine )
{
	TRACE_BOOKMARK(TEXT("WinMain.Enter"));
#if USING_ADDRESS_SANITISER
	__asan_set_error_report_callback(ASanErrorCallback);
#endif

	// Setup common Windows settings
	SetupWindowsEnvironment();

	int32 ErrorLevel			= 0;
	hInstance				= hInInstance;

	if (!CmdLine)
	{
		CmdLine = ::GetCommandLineW();

		// Attempt to process the command-line arguments using the standard Windows implementation
		// (This ensures behavior parity with other platforms where argc and argv are used.)
		if ( ProcessCommandLine() )
		{
			CmdLine = *GSavedCommandLine;
		}
	}

	// If we're running in unattended mode, make sure we never display error dialogs if we crash.
	if ( FParse::Param( CmdLine, TEXT("unattended") ) )
	{
		SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
	}

	if ( FParse::Param( CmdLine,TEXT("crashreports") ) )
	{
		GAlwaysReportCrash = true;
	}

	bool bNoExceptionHandler = FParse::Param(CmdLine,TEXT("noexceptionhandler"));
	(void)bNoExceptionHandler;

	bool bIgnoreDebugger = FParse::Param(CmdLine, TEXT("IgnoreDebugger"));
	(void)bIgnoreDebugger;

	bool bIsDebuggerPresent = FPlatformMisc::IsDebuggerPresent() && !bIgnoreDebugger;
	(void)bIsDebuggerPresent;

	// Using the -noinnerexception parameter will disable the exception handler within native C++, which is call from managed code,
	// which is called from this function.
	// The default case is to have three wrapped exception handlers 
	// Native: WinMain() -> Native: GuardedMainWrapper().
	// The inner exception handler in GuardedMainWrapper() catches crashes/asserts in native C++ code and is the only way to get the
	// correct callstack when running a 64-bit executable. However, XAudio2 sometimes (?) don't like this and it may result in no sound.
#ifdef _WIN64
	if ( FParse::Param(CmdLine,TEXT("noinnerexception")) || FApp::IsBenchmarking() || bNoExceptionHandler)
	{
		GEnableInnerException = false;
	}
#endif	

	// When we're running embedded, assume that the outer application is going to be handling crash reporting
#if UE_BUILD_DEBUG
	if (GUELibraryOverrideSettings.bIsEmbedded || !GAlwaysReportCrash)
#else
	if (GUELibraryOverrideSettings.bIsEmbedded || bNoExceptionHandler || (bIsDebuggerPresent && !GAlwaysReportCrash))
#endif
	{
		// Don't use exception handling when a debugger is attached to exactly trap the crash. This does NOT check
		// whether we are the first instance or not!
		ErrorLevel = GuardedMain( CmdLine );
	}
	else
	{
		// Use structured exception handling to trap any crashes, walk the the stack and display a crash dialog box.
#if !PLATFORM_SEH_EXCEPTIONS_DISABLED
		__try
#endif
 		{
			GIsGuarded = 1;
			// Run the guarded code.
			ErrorLevel = GuardedMainWrapper( CmdLine );
			GIsGuarded = 0;
		}
#if !PLATFORM_SEH_EXCEPTIONS_DISABLED
		__except( FPlatformMisc::GetCrashHandlingType() == ECrashHandlingType::Default
				? ( GEnableInnerException ? EXCEPTION_EXECUTE_HANDLER : ReportCrash(GetExceptionInformation()) )
				: EXCEPTION_CONTINUE_SEARCH )	
		{
			// Crashed.
			ErrorLevel = 1;
			if(GError)
			{
				GError->HandleError();
			}
			LaunchStaticShutdownAfterError();
			FPlatformMallocCrash::Get().PrintPoolsUsage();
			FPlatformMisc::RequestExit( true, TEXT("LaunchWindowsStartup.ExceptionHandler"));
		}
#endif
	}

	TRACE_BOOKMARK(TEXT("WinMain.Exit"));

	return ErrorLevel;
}

11.GuardedMain做了什么?

主要做了EnginePreInit/EditorInit/EngineTick/EngineExit PreInit里分二步PreInitPreStartupScreen和PreInitPostStartupScreen

/**
 * Static guarded main function. Rolled into own function so we can have error handling for debug/ release builds depending
 * on whether a debugger is attached or not.
 */
int32 GuardedMain( const TCHAR* CmdLine )
{
	FTrackedActivity::GetEngineActivity().Update(TEXT("Starting"), FTrackedActivity::ELight::Yellow);

	FTaskTagScope Scope(ETaskTag::EGameThread);

#if !(UE_BUILD_SHIPPING)

	// If "-waitforattach" or "-WaitForDebugger" was specified, halt startup and wait for a debugger to attach before continuing
	if (FParse::Param(CmdLine, TEXT("waitforattach")) || FParse::Param(CmdLine, TEXT("WaitForDebugger")))
	{
		while (!FPlatformMisc::IsDebuggerPresent())
		{
			FPlatformProcess::Sleep(0.1f);
		}
		UE_DEBUG_BREAK();
	}

#endif

	BootTimingPoint("DefaultMain");

	// Super early init code. DO NOT MOVE THIS ANYWHERE ELSE!
	FCoreDelegates::GetPreMainInitDelegate().Broadcast();

	// make sure GEngineLoop::Exit() is always called.
	struct EngineLoopCleanupGuard 
	{ 
		~EngineLoopCleanupGuard()
		{
			// Don't shut down the engine on scope exit when we are running embedded
			// because the outer application will take care of that.
			if (!GUELibraryOverrideSettings.bIsEmbedded)
			{
				EngineExit();
			}
		}
	} CleanupGuard;

	// Set up minidump filename. We cannot do this directly inside main as we use an FString that requires 
	// destruction and main uses SEH.
	// These names will be updated as soon as the Filemanager is set up so we can write to the log file.
	// That will also use the user folder for installed builds so we don't write into program files or whatever.
#if PLATFORM_WINDOWS
	FCString::Strcpy(MiniDumpFilenameW, *FString::Printf(TEXT("unreal-v%i-%s.dmp"), FEngineVersion::Current().GetChangelist(), *FDateTime::Now().ToString()));
#endif

	FTrackedActivity::GetEngineActivity().Update(TEXT("Initializing"));
	int32 ErrorLevel = EnginePreInit( CmdLine );

	// exit if PreInit failed.
	if ( ErrorLevel != 0 || IsEngineExitRequested() )
	{
		return ErrorLevel;
	}

	{
		FScopedSlowTask SlowTask(100, NSLOCTEXT("EngineInit", "EngineInit_Loading", "Loading..."));

		// EnginePreInit leaves 20% unused in its slow task.
		// Here we consume 80% immediately so that the percentage value on the splash screen doesn't change from one slow task to the next.
		// (Note, we can't include the call to EnginePreInit in this ScopedSlowTask, because the engine isn't fully initialized at that point)
		SlowTask.EnterProgressFrame(80);

		SlowTask.EnterProgressFrame(20);

#if WITH_EDITOR
		if (GIsEditor)
		{
			ErrorLevel = EditorInit(GEngineLoop);
		}
		else
#endif
		{
			ErrorLevel = EngineInit();
		}
	}

	double EngineInitializationTime = FPlatformTime::Seconds() - GStartTime;
	UE_LOG(LogLoad, Log, TEXT("(Engine Initialization) Total time: %.2f seconds"), EngineInitializationTime);

	ACCUM_LOADTIME(TEXT("EngineInitialization"), EngineInitializationTime);

	BootTimingPoint("Tick loop starting");
	DumpBootTiming();

	FTrackedActivity::GetEngineActivity().Update(TEXT("Ticking loop"), FTrackedActivity::ELight::Green);

	// Don't tick if we're running an embedded engine - we rely on the outer
	// application ticking us instead.
	if (!GUELibraryOverrideSettings.bIsEmbedded)
	{
		while( !IsEngineExitRequested() )
		{
			EngineTick();
		}
	}

	TRACE_BOOKMARK(TEXT("Tick loop end"));

#if WITH_EDITOR
	if( GIsEditor )
	{
		EditorExit();
	}
#endif
	return ErrorLevel;
}

12.启动时间在哪里初始的?

/** Time at which FPlatformTime::Seconds() was first initialized (before main)								*/
double					GStartTime						= FPlatformTime::InitTiming();
double FWindowsPlatformTime::InitTiming(void)
{
	LARGE_INTEGER Frequency;
	verify( QueryPerformanceFrequency(&Frequency) );
	SecondsPerCycle = 1.0 / (double)Frequency.QuadPart;
	SecondsPerCycle64 = 1.0 / (double)Frequency.QuadPart;

	// Due to some limitation of the OS, we limit the polling frequency to 4 times per second, 
	// but it should be enough for longterm CPU usage monitoring.
	static const float PollingInterval = 1.0f / 4.0f;

	// Register a ticker delegate for updating the CPU utilization data.
	FTSTicker::GetCoreTicker().AddTicker( FTickerDelegate::CreateStatic( &FPlatformTime::UpdateCPUTime ), PollingInterval );

	return FPlatformTime::Seconds();
}

13.TArray基本结构

std::conditional_t 的作用是根据一个编译期条件表达式来选择不同的类型。在这里,条件是 AllocatorType::NeedsElementType。 如果 AllocatorType::NeedsElementType 为真: 那么会选择 typename AllocatorType::template ForElementType 作为 ElementAllocatorType。这意味着当分配器类型 AllocatorType 需要明确指定所处理的元素类型(即 NeedsElementType 条件满足)时,会使用针对当前 ElementType(也就是数组存储的元素类型)的特定分配器模板实例化 ForElementType。 如果 AllocatorType::NeedsElementType 为假: 则会选择 typename AllocatorType::ForAnyElementType 作为 ElementAllocatorType。这表示当分配器类型不需要明确指定元素类型(即 NeedsElementType 条件不满足)时,就使用更通用的 ForAnyElementType 这种形式的分配器相关类型。

//ContainerAllocationPolicies.h
template <int IndexSize, typename BaseMallocType = FMemory>
class TSizedHeapAllocator
{
};

//ContainerAllocationPolicies.h
template <int IndexSize> class TSizedDefaultAllocator : public TSizedHeapAllocator<IndexSize> { public: typedef TSizedHeapAllocator<IndexSize> Typedef; };

//ContainersFwd.h
template<int IndexSize> class TSizedDefaultAllocator;
using FDefaultAllocator = TSizedDefaultAllocator<32>;
using FDefaultAllocator64 = TSizedDefaultAllocator<64>;
class FDefaultSetAllocator;
template<typename T, typename Allocator = FDefaultAllocator> class TArray;

//Array.h
template<typename InElementType, typename InAllocatorType>
class TArray
{
public:
	typedef typename InAllocatorType::SizeType SizeType;
	typedef InElementType ElementType;
	typedef InAllocatorType AllocatorType;
	using ElementAllocatorType = std::conditional_t<
		AllocatorType::NeedsElementType,
		typename AllocatorType::template ForElementType<ElementType>,
		typename AllocatorType::ForAnyElementType
	>;
protected:
	ElementAllocatorType AllocatorInstance;
	SizeType             ArrayNum;
	SizeType             ArrayMax;
public:
	TArray()
		: ArrayNum(0)
		, ArrayMax(AllocatorInstance.GetInitialCapacity())
	{}

	const ElementAllocatorType& GetAllocatorInstance() const { return AllocatorInstance; }
	ElementAllocatorType& GetAllocatorInstance() { return AllocatorInstance; }
};

构造函数

/**
	 * Constructor, initializes element number counters.
	 */
	FORCEINLINE TArray()
		: ArrayNum(0)
		, ArrayMax(AllocatorInstance.GetInitialCapacity())
	{}

	/**
	 * Constructor from a raw array of elements.
	 *
	 * @param Ptr   A pointer to an array of elements to copy.
	 * @param Count The number of elements to copy from Ptr.
	 * @see Append
	 */
	FORCEINLINE TArray(const ElementType* Ptr, SizeType Count)
	{
		if (Count < 0)
		{
			// Cast to USizeType first to prevent sign extension on negative sizes, producing unusually large values.
			UE::Core::Private::OnInvalidArrayNum((unsigned long long)(USizeType)Count);
		}

		check(Ptr != nullptr || Count == 0);

		CopyToEmpty(Ptr, Count, 0);
	}

	template <typename OtherElementType, typename OtherSizeType>
	explicit TArray(const TArrayView<OtherElementType, OtherSizeType>& Other);

	/**
	 * Initializer list constructor
	 */
	TArray(std::initializer_list<InElementType> InitList)
	{
		// This is not strictly legal, as std::initializer_list's iterators are not guaranteed to be pointers, but
		// this appears to be the case on all of our implementations.  Also, if it's not true on a new implementation,
		// it will fail to compile rather than behave badly.
		CopyToEmpty(InitList.begin(), (SizeType)InitList.size(), 0);
	}

	/**
	 * Copy constructor with changed allocator. Use the common routine to perform the copy.
	 *
	 * @param Other The source array to copy.
	 */
	template <
		typename OtherElementType,
		typename OtherAllocator
		UE_REQUIRES(UE4Array_Private::TArrayElementsAreCompatible_V<ElementType, const OtherElementType&>)
	>
	FORCEINLINE explicit TArray(const TArray<OtherElementType, OtherAllocator>& Other)
	{
		CopyToEmpty(Other.GetData(), Other.Num(), 0);
	}

	/**
	 * Copy constructor. Use the common routine to perform the copy.
	 *
	 * @param Other The source array to copy.
	 */
	FORCEINLINE TArray(const TArray& Other)
	{
		CopyToEmpty(Other.GetData(), Other.Num(), 0);
	}

	/**
	 * Copy constructor. Use the common routine to perform the copy.
	 *
	 * @param Other The source array to copy.
	 * @param ExtraSlack Tells how much extra memory should be preallocated
	 *                   at the end of the array in the number of elements.
	 */
	FORCEINLINE TArray(const TArray& Other, SizeType ExtraSlack)
	{
		CopyToEmptyWithSlack(Other.GetData(), Other.Num(), 0, ExtraSlack);
	}

14.内存分配器有哪些?

UE5.4.1默认用的Mimalloc,来自微软:https://github.com/microsoft/mimalloc/

	/** Which allocator is being used */
	enum EMemoryAllocatorToUse
	{
		Ansi, // Default C allocator
		Stomp, // Allocator to check for memory stomping
		TBB, // Thread Building Blocks malloc
		Jemalloc, // Linux/FreeBSD malloc
		Binned, // Older binned malloc
		Binned2, // Newer binned malloc
		Binned3, // Newer VM-based binned malloc, 64 bit only
		Platform, // Custom platform specific allocator
		Mimalloc, // mimalloc //TBB 64-bit scalable memory allocator
		Libpas, // libpas
	};


FMalloc* FWindowsPlatformMemory::BaseAllocator()
{
	static FMalloc* Instance = nullptr;
	if (Instance != nullptr)
	{
		return Instance;
	}

#if ENABLE_WIN_ALLOC_TRACKING
	// This allows tracking of allocations that don't happen within the engine's wrappers.
	// This actually won't be compiled unless bDebugBuildsActuallyUseDebugCRT is set in the
	// build configuration for UBT.
	_CrtSetAllocHook(WindowsAllocHook);
#endif // ENABLE_WIN_ALLOC_TRACKING

	if (FORCE_ANSI_ALLOCATOR) //-V517
	{
		AllocatorToUse = EMemoryAllocatorToUse::Ansi;
	}
#if PLATFORM_64BITS
	// Mimalloc is now the default allocator for editor and programs because it has shown
	// both great performance and as much as half the memory usage of TBB after
	// heavy editor workloads. See CL 15887498 description for benchmarks.
	else if ((WITH_EDITORONLY_DATA || IS_PROGRAM) && MIMALLOC_ENABLED) //-V517
	{
		AllocatorToUse = EMemoryAllocatorToUse::Mimalloc;
	}
#endif
	else if ((WITH_EDITORONLY_DATA || IS_PROGRAM) && TBBMALLOC_ENABLED) //-V517
	{
		AllocatorToUse = EMemoryAllocatorToUse::TBB;
	}
#if PLATFORM_64BITS
	else if (USE_MALLOC_BINNED3)
	{
		AllocatorToUse = EMemoryAllocatorToUse::Binned3;
	}
#endif
	else if (USE_MALLOC_BINNED2)
	{
		AllocatorToUse = EMemoryAllocatorToUse::Binned2;
	}
	else
	{
		AllocatorToUse = EMemoryAllocatorToUse::Binned;
	}
	
#if !UE_BUILD_SHIPPING
	// If not shipping, allow overriding with command line options, this happens very early so we need to use windows functions
	const TCHAR* CommandLine = ::GetCommandLineW();

	if (FCString::Stristr(CommandLine, TEXT("-libpasmalloc")))
	{
		AllocatorToUse = EMemoryAllocatorToUse::Libpas;
	}
	else if (FCString::Stristr(CommandLine, TEXT("-ansimalloc")))
	{
		// see FPlatformMisc::GetProcessDiagnostics()
		AllocatorToUse = EMemoryAllocatorToUse::Ansi;
	}
#if TBBMALLOC_ENABLED
	else if (FCString::Stristr(CommandLine, TEXT("-tbbmalloc")))
	{
		AllocatorToUse = EMemoryAllocatorToUse::TBB;
	}
#endif
#if MIMALLOC_ENABLED
	else if (FCString::Stristr(CommandLine, TEXT("-mimalloc")))
	{
		AllocatorToUse = EMemoryAllocatorToUse::Mimalloc;
	}
#endif
#if PLATFORM_64BITS
	else if (FCString::Stristr(CommandLine, TEXT("-binnedmalloc3")))
	{
		AllocatorToUse = EMemoryAllocatorToUse::Binned3;
	}
#endif
	else if (FCString::Stristr(CommandLine, TEXT("-binnedmalloc2")))
	{
		AllocatorToUse = EMemoryAllocatorToUse::Binned2;
	}
	else if (FCString::Stristr(CommandLine, TEXT("-binnedmalloc")))
	{
		AllocatorToUse = EMemoryAllocatorToUse::Binned;
	}
#if WITH_MALLOC_STOMP
	else if (FCString::Stristr(CommandLine, TEXT("-stompmalloc")))
	{
		// see FPlatformMisc::GetProcessDiagnostics()
		AllocatorToUse = EMemoryAllocatorToUse::Stomp;
	}
#endif // WITH_MALLOC_STOMP
#if WITH_MALLOC_STOMP2
	if (FCString::Stristr(CommandLine, TEXT("-stomp2malloc")))
	{
		GMallocStomp2Enabled = true;
	}
#endif // WITH_MALLOC_STOMP2

	if (FCString::Stristr(CommandLine, TEXT("-doublefreefinder")))
	{
		GMallocDoubleFreeFinderEnabled = true;
	}

#endif // !UE_BUILD_SHIPPING

	switch (AllocatorToUse)
	{
	case EMemoryAllocatorToUse::Ansi:
		Instance = new FMallocAnsi();
		break;
#if WITH_MALLOC_STOMP
	case EMemoryAllocatorToUse::Stomp:
		Instance = new FMallocStomp();
		break;
#endif
#if TBBMALLOC_ENABLED
	case EMemoryAllocatorToUse::TBB:
		Instance = new FMallocTBB();
		break;
#endif
#if MIMALLOC_ENABLED
	case EMemoryAllocatorToUse::Mimalloc:
		Instance = new FMallocMimalloc();
		break;
#endif
#if LIBPASMALLOC_ENABLED
	case EMemoryAllocatorToUse::Libpas:
		Instance = new FMallocLibpas();
		break;
#endif
	case EMemoryAllocatorToUse::Binned2:
		Instance = new FMallocBinned2();
		break;
#if PLATFORM_64BITS
	case EMemoryAllocatorToUse::Binned3:
		Instance = new FMallocBinned3();
		break;
#endif
	default:	// intentional fall-through
	case EMemoryAllocatorToUse::Binned:
		Instance = new FMallocBinned((uint32)(GetConstants().BinnedPageSize&MAX_uint32), (uint64)MAX_uint32 + 1);
		break;
	}

	return Instance;
}

15.Slate示例在哪里?

源码工程中设置SlateViewer为启动项即可,Engine/Source/Programs/SlateViewer/Private/SlateViewerApp.cpp 默认只显示一个示例窗口,启动加testsuite参数可显示更详尽的

FGlobalTabmanager::Get()->SetApplicationTitle(LOCTEXT("AppTitle", "Starship Slate Viewer"));
		FAppStyle::SetAppStyleSetName(FStarshipCoreStyle::GetCoreStyle().GetStyleSetName());
		RestoreStarshipSuite();	//默认窗口

		if (FParse::Param(FCommandLine::Get(), TEXT("testsuite")))
		{
			RestoreSlateTestSuite();//详细的窗口
		}

image

16.GC

gc的配置

class UGarbageCollectionSettings : public UDeveloperSettings
{
	
protected:

	UPROPERTY(EditAnywhere, config, Category = General, meta = (
		ConsoleVariable = "gc.TimeBetweenPurgingPendingKillObjects", DisplayName = "Time Between Purging Pending Kill Objects",
		ToolTip = "Time in seconds (game time) we should wait between purging object references to objects that are pending kill."))
	float TimeBetweenPurgingPendingKillObjects
 }

主要是在UWorld的Tick里处理ForceGarbageCollection。

class UEngine{
/** Updates the timer between garbage collection such that at the next opportunity garbage collection will be run. */
	ENGINE_API void ForceGarbageCollection(bool bFullPurge = false);

	/**
	 *  Requests a one frame delay of Garbage Collection
	 */
	ENGINE_API void DelayGarbageCollection();

	/**
	 * Updates the timer (as a one-off) that is used to trigger garbage collection; this should only be used for things
	 * like performance tests, using it recklessly can dramatically increase memory usage and cost of the eventual GC.
	 *
	 * Note: Things that force a GC will still force a GC after using this method (and they will also reset the timer)
	 */
	ENGINE_API void SetTimeUntilNextGarbageCollection(float MinTimeUntilNextPass);

	/**
	 * Returns the current desired time between garbage collection passes (not the time remaining)
	 */
	ENGINE_API float GetTimeBetweenGarbageCollectionPasses() const;
}