简约写的一些读源码笔记,行文胡乱没章法,仅供备忘。
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)
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 >= 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可看到
__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
//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();//详细的窗口
}
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;
}