本文的重点:理解UE4的“Ini层级”概念,以及找到其对应代码,与观察运行结果。
官方文档中介绍的“层级”概念
官方文档https://dev.epicgames.com/documentation/en-us/unreal-engine/configuration-files-in-unreal-engine?application_version=5.4 在UE4的官方文档中,搜索“Configuration File Hierarchy”小节,可以看到Ini配置文件的优先级层级如下:
Category configuration files are loaded in the following order:
Engine/Config/Base.ini
Engine/Config/Base<CATEGORY>.ini
Engine/Config/<PLATFORM>/Base<PLATFORM><CATEGORY>.ini
Engine/Platforms/<PLATFORM>/Config/Base<PLATFORM><CATEGORY>.ini
<PROJECT_DIRECTORY>/Config/Default<CATEGORY>.ini
Engine/Config/<PLATFORM>/<PLATFORM><CATEGORY>.ini
Engine/Platforms/<PLATFORM>/Config/<PLATFORM><CATEGORY>.ini
<PROJECT_DIRECTORY>/Config/<PLATFORM>/<PLATFORM><CATEGORY>.ini
<PROJECT_DIRECTORY>/Platforms/<PLATFORM>/Config/<PLATFORM><CATEGORY>.ini
<LOCAL_APP_DATA>/Unreal Engine/Engine/Config/User<CATEGORY>.ini
<MY_DOCUMENTS>/Unreal Engine/Engine/Config/User<CATEGORY>.ini
<PROJECT_DIRECTORY>/Config/User<CATEGORY>.ini
越往后的越高优先级,它对应的代码是:
/**
* Structure to define all the layers of the config system. Layers can be expanded by expansion files (NoRedist, etc), or by ini platform parents
* (coming soon from another branch)
*/
struct FConfigLayer
{
// Used by the editor to display in the ini-editor
const TCHAR* EditorName;
// Path to the ini file (with variables)
const TCHAR* Path;
// Path to the platform extension version
const TCHAR* PlatformExtensionPath;
// Special flag
EConfigLayerFlags Flag;
} GConfigLayers[] =
{
/**************************************************
**** CRITICAL NOTES
**** If you change this array, you need to also change EnumerateConfigFileLocations() in ConfigHierarchy.cs!!!
**** And maybe UObject::GetDefaultConfigFilename(), UObject::GetGlobalUserConfigFilename()
**************************************************/
// Engine/Base.ini
{ TEXT("AbsoluteBase"), TEXT("{ENGINE}Base.ini"), TEXT(""), EConfigLayerFlags::Required },
// Engine/Base*.ini
{ TEXT("Base"), TEXT("{ENGINE}{ED}{EF}Base{TYPE}.ini") },
// Engine/Platform/BasePlatform*.ini
{ TEXT("BasePlatform"), TEXT("{ENGINE}{ED}{PLATFORM}/{EF}Base{PLATFORM}{TYPE}.ini"), TEXT("{EXTENGINE}/{ED}{EF}Base{PLATFORM}{TYPE}.ini"), },
// Project/Default*.ini
{ TEXT("ProjectDefault"), TEXT("{PROJECT}{ED}{EF}Default{TYPE}.ini"), TEXT(""), EConfigLayerFlags::AllowCommandLineOverride | EConfigLayerFlags::GenerateCacheKey },
// Engine/Platform/Platform*.ini
{ TEXT("EnginePlatform"), TEXT("{ENGINE}{ED}{PLATFORM}/{EF}{PLATFORM}{TYPE}.ini"), TEXT("{EXTENGINE}/{ED}{EF}{PLATFORM}{TYPE}.ini") },
// Project/Platform/Platform*.ini
{ TEXT("ProjectPlatform"), TEXT("{PROJECT}{ED}{PLATFORM}/{EF}{PLATFORM}{TYPE}.ini"), TEXT("{EXTPROJECT}/{ED}{EF}{PLATFORM}{TYPE}.ini") },
// UserSettings/.../User*.ini
{ TEXT("UserSettingsDir"), TEXT("{USERSETTINGS}Unreal Engine/Engine/Config/User{TYPE}.ini") },
// UserDir/.../User*.ini
{ TEXT("UserDir"), TEXT("{USER}Unreal Engine/Engine/Config/User{TYPE}.ini") },
// Project/User*.ini
{ TEXT("GameDirUser"), TEXT("{PROJECT}User{TYPE}.ini"), TEXT(""), EConfigLayerFlags::GenerateCacheKey },
};
(代码块1)
其中的 {ED} {EF} 是用于替换的记号,{ED}表示文件夹的前缀,{EF}表示文件的前缀,代码位于
static FString GetExpansionPath(const FConfigLayerExpansion& Expansion, const FString& LayerPath, bool bHasPlatformTag)
{
// replace the expansion tags
FString ExpansionPath = LayerPath.Replace(TEXT("{ED}"), Expansion.DirectoryPrefix, ESearchCase::CaseSensitive);
ExpansionPath = ExpansionPath.Replace(TEXT("{EF}"), Expansion.FilePrefix, ESearchCase::CaseSensitive);
替换的规则见 GConfigLayerExpansions[] 。因为代码很多很杂,接下来我找到了重点的代码进行学习和罗列。
代码示意图
我找到了重点代码,绘制了如下调用关系图:
其中的 “Final” 或说 “Dest” Ini的概念,对应的接口文档是:
https://docs.unrealengine.com/4.27/en-US/API/Runtime/Core/Misc/FConfigCacheIni/GetDestIniFilename/
其中MarkC处 AddStaticLayersToHierarchy() 的含义是:
一、第一层For循环:遍历各层级
// go over all the config layers
for (int32 LayerIndex = 0; LayerIndex < UE_ARRAY_COUNT(GConfigLayers); LayerIndex++)
见前文“代码块1”;
二、第二层For循环:遍历各平台
for (int PlatformIndex = 0; PlatformIndex < NumPlatforms; PlatformIndex++)
例如安卓。实测没有看到 Android 等字样,不细究。
调试“加载Ini的过程”
1、在 void FConfigCacheIni::InitializeConfigSystem() 的最后一行 FCoreDelegates::ConfigReadyForUse.Broadcast() 处下断点;
2、以调试Game的方式(UE4如何直接调试Game-CSDN博客)进行调试。
UE4加载Ini的堆栈是:
FConfigCacheIni::InitializeConfigSystem() ConfigCacheIni.cpp:3908
FEngineLoop::AppInit() LaunchEngineLoop.cpp:5716
FEngineLoop::PreInitPreStartupScreen(const wchar_t *) LaunchEngineLoop.cpp:2083
FEngineLoop::PreInit(const wchar_t *) LaunchEngineLoop.cpp:3593
[Inlined] FEngineLoop::PreInit(int, wchar_t **, const wchar_t *) LaunchEngineLoop.cpp:1104
wmain(int, wchar_t **) UnrealPak.cpp:13
UE_LOG(LogTemp, Warning, TEXT("MyCodeTrace:ConfigFile.Name:>>>> %s"), * ConfigFile.Name.ToString());
// ConfigFile.Name
for(auto each /* TMap<FString,FConfigSection> */ : ConfigFile.SourceIniHierarchy)
{
UE_LOG(LogTemp, Warning, TEXT("MyCodeTrace:WatchStaticLayers %d : %s"), each.Key, * each.Value.Filename);
}
UE_LOG(LogTemp, Warning, TEXT("MyCodeTrace:ConfigFile.Name:<<<< %s"), * ConfigFile.Name.ToString());
通过添加日志的方式,如上,观察到 MarkC处 AddStaticLayersToHierarchy() 处添加的 Layers 是这些内容:
0 : ../../../Engine/Config/Base.ini
10000 : ../../../Engine/Config/BaseInput.ini
10200 : ../../../Engine/Config/ShippableBaseInput.ini
10300 : ../../../Engine/Config/NotForLicensees/BaseInput.ini
10400 : ../../../Engine/Config/NotForLicensees/ShippableBaseInput.ini
10500 : ../../../Engine/Config/NoRedist/BaseInput.ini
10600 : ../../../Engine/Config/NoRedist/ShippableBaseInput.ini
20000 : ../../../Engine/Config/Windows/BaseWindowsInput.ini
20200 : ../../../Engine/Config/Windows/ShippableBaseWindowsInput.ini
20300 : ../../../Engine/Config/NotForLicensees/Windows/BaseWindowsInput.ini
20400 : ../../../Engine/Config/NotForLicensees/Windows/ShippableBaseWindowsInput.ini
20500 : ../../../Engine/Config/NoRedist/Windows/BaseWindowsInput.ini
20600 : ../../../Engine/Config/NoRedist/Windows/ShippableBaseWindowsInput.ini
30000 : ../../../Engine/Programs/UnrealPak/Config/DefaultInput.ini
30200 : ../../../Engine/Programs/UnrealPak/Config/ShippableDefaultInput.ini
30300 : ../../../Engine/Programs/UnrealPak/Config/NotForLicensees/DefaultInput.ini
30400 : ../../../Engine/Programs/UnrealPak/Config/NotForLicensees/ShippableDefaultInput.ini
30500 : ../../../Engine/Programs/UnrealPak/Config/NoRedist/DefaultInput.ini
30600 : ../../../Engine/Programs/UnrealPak/Config/NoRedist/ShippableDefaultInput.ini
40000 : ../../../Engine/Config/Windows/WindowsInput.ini
40200 : ../../../Engine/Config/Windows/ShippableWindowsInput.ini
40300 : ../../../Engine/Config/NotForLicensees/Windows/WindowsInput.ini
40400 : ../../../Engine/Config/NotForLicensees/Windows/ShippableWindowsInput.ini
40500 : ../../../Engine/Config/NoRedist/Windows/WindowsInput.ini
40600 : ../../../Engine/Config/NoRedist/Windows/ShippableWindowsInput.ini
50000 : ../../../Engine/Programs/UnrealPak/Config/Windows/WindowsInput.ini
50200 : ../../../Engine/Programs/UnrealPak/Config/Windows/ShippableWindowsInput.ini
50300 : ../../../Engine/Programs/UnrealPak/Config/NotForLicensees/Windows/WindowsInput.ini
50400 : ../../../Engine/Programs/UnrealPak/Config/NotForLicensees/Windows/ShippableWindowsInput.ini
50500 : ../../../Engine/Programs/UnrealPak/Config/NoRedist/Windows/WindowsInput.ini
50600 : ../../../Engine/Programs/UnrealPak/Config/NoRedist/Windows/ShippableWindowsInput.ini
60000 : C:/Users/wenjiezou/AppData/Local/Unreal Engine/Engine/Config/UserInput.ini
70000 : C:/Users/wenjiezou/Documents/Unreal Engine/Engine/Config/UserInput.ini
80000 : ../../../Engine/Programs/UnrealPak/Config/UserInput.ini
其中,最先的是
0 : ../../../Engine/Config/Base.ini
10000 : ../../../Engine/Config/BaseInput.ini
最后(最终 / 最优先生效)的是:
70000 : C:/Users/wenjiezou/Documents/Unreal Engine/Engine/Config/UserInput.ini
80000 : ../../../Engine/Programs/UnrealPak/Config/UserInput.ini
但实际上并不一定具有这些文件,例如实际上就不存在 C:/Users/wenjiezou/Documents/Unreal Engine/Engine/Config/UserInput.ini 这个文件。
实际上观察到的最终Ini文件是空的,例如 G:\St\EngineSource\Engine\Programs\UnrealPak\Saved\Config\Windows\GameUserSettings.ini 的内容是空行,原因可以参考源代码中的注释,当引擎初始化的时候,其实不应该产生变化,因此不需要输出最终Ini。
// Check GIsInitialLoad since no INI changes that should be persisted could have occurred this early.
// INI changes from code, environment variables, CLI parameters, etc should not be persisted.
if (!GIsInitialLoad && bWriteDestIni && (!FPlatformProperties::RequiresCookedData() || bAllowGeneratedIniWhenCooked)
// We shouldn't save config files when in multiprocess mode,
// otherwise we get file contention in XGE shader builds.
&& !FParse::Param(FCommandLine::Get(), TEXT("Multiprocess")))
{
// Check the config system for any changes made to defaults and propagate through to the saved.
ConfigFile.ProcessSourceAndCheckAgainstBackup();
if (bNeedsWrite)
{
// if it was dirtied during the above function, save it out now
ConfigFile.Write(DestIniFilename);
}
}
那么最终的GConfig是怎么样的呢?它其实是Map映射 { 最终Ini名 → FConfigFile },
其中,FConfigFile是映射 { Section名 → 众字段 } ,如下图所示:
总结
UE4采用“Ini层级”的概念,让引擎、项目具有默认的配置值、逐项目的配置值、逐实际运行的实例的配置值(Saved下)、甚至是和本地环境相关的配置值。本文还找到了相关代码与运行结果进行学习。