Unreal中ini配置文件的hierarchy
Config
UE的很多配置是通过ini文件实现的,相对于二进制文件来说,ini文件的优点是读取、阅读、修改都非常方便,因为所有的文本编辑器都可以修改。但是UE中的ini文件可谓是眼花缭乱,在Engine、project文件夹下,同样的Engine.ini可能存在baseengine.ini、defaultengine.ini、engine.ini,platform下的engine.ini等,这些结构可能对相同的一个key都会有配置,看起来就有些困惑。
最后在UE的官网可以看到这个关于File Hierarchy的说明:大致的意思就是同一个大类的配置项(例如,前面提到的engine相同的base name都是engine,但是存在base、default等不同前、后缀或者不同文件夹下的ini)使用的是一个简单的层级(hierarchy)来实现。这相当于使用了大家熟知的(well-known)的ini结构的基础上,使用文件系统的结构来实现各种定制化。
因为这个概念比较基础,所以把文字摘录一下作为备份:
The configuration file hierarchy is read in starting with Base.ini, with values in later files in the hierarchy overriding earlier values. All files in the Engine folder will be applied to all projects, while project-specific settings should be in files in the project directory. Finally, all project-specific and platform-specific differences are saved out to [Project Directory]/Saved/Config/[Platform]/[Category].ini.
The below file hierarchy example is for the Engine category of configuration files.
Engine/Config/Base.ini
Base.ini is usually empty.
Engine/Config/BaseEngine.ini
Engine/Config/[Platform]/base[Platform]Engine.ini
[Project Directory]/Config/DefaultEngine.ini
Engine/Config/[Platform]/[Platform]Engine.ini
[Project Directory]/Config/[Platform]/[Platform]Engine.ini
The configuration file in the Saved directory only stores the project-specific and platform-specific differences in the stack of configuration files.
创建project
从工程的Templates文件夹生成具体工程(project)。在工程的根目录下存在一个Templates文件夹,该文件夹下存在了很多可以使用的模板,这些模板中包含了基础的Config、Source、uproject文件,这些文件夹的内容将会被拷贝到新创建的project文件中(可能在拷贝的过程中有一些修改?)。
///@file: Engine\Source\Editor\GameProjectGeneration\Private\GameProjectUtils.cpp
TOptional<FGuid> GameProjectUtils::CreateProjectFromTemplate(const FProjectInformation& InProjectInfo, FText& OutFailReason, FText& OutFailLog, TArray<FString>* OutCreatedFiles)
{
///...
FGuid ProjectID = FGuid::NewGuid();
ConfigValuesToSet.Emplace(TEXT("DefaultGame.ini"), TEXT("/Script/EngineSettings.GeneralProjectSettings"), TEXT("ProjectID"), ProjectID.ToString(), /*InShouldReplaceExistingValue=*/true);
// Add all classname fixups
for (const TPair<FString, FString>& Rename : ClassRenames)
{
const FString ClassRedirectString = FString::Printf(TEXT("(OldClassName=\"%s\",NewClassName=\"%s\")"), *Rename.Key, *Rename.Value);
ConfigValuesToSet.Emplace(TEXT("DefaultEngine.ini"), TEXT("/Script/Engine.Engine"), TEXT("+ActiveClassRedirects"), *ClassRedirectString, /*InShouldReplaceExistingValue=*/false);
}
SlowTask.EnterProgressFrame();
if (!SaveConfigValues(InProjectInfo, ConfigValuesToSet, OutFailReason))
{
return TOptional<FGuid>();
}
///...
// Generate the project file
{
}
hierarchy
从实现上看,这个层级又分为了静态和动态两个大类,其中的静态就是约定的配置位置及优先级,它们以数组的形式写死在代码中;另一类动态的则是主要给插件使用的配置,它们的动态主要是因为插件是动态的,所以需要在运行时确定。
static
/**
* Structure to define all the layers of the config system. Layers can be expanded by expansion files (NoRedist, etc), or by ini platform parents
*/
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;
// Special flag
EConfigLayerFlags Flag;
};
///@file: Engine\Source\Runtime\Core\Public\Misc\ConfigHierarchy.h
// See FConfigContext.cpp for the types here
static FConfigLayer 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}/Config/Base.ini"), EConfigLayerFlags::NoExpand},
// Engine/Base*.ini
{ TEXT("Base"), TEXT("{ENGINE}/Config/Base{TYPE}.ini") },
// Engine/Platform/BasePlatform*.ini
{ TEXT("BasePlatform"), TEXT("{ENGINE}/Config/{PLATFORM}/Base{PLATFORM}{TYPE}.ini") },
// Project/Default*.ini
{ TEXT("ProjectDefault"), TEXT("{PROJECT}/Config/Default{TYPE}.ini"), EConfigLayerFlags::AllowCommandLineOverride },
// Project/Generated*.ini Reserved for files generated by build process and should never be checked in
{ TEXT("ProjectGenerated"), TEXT("{PROJECT}/Config/Generated{TYPE}.ini") },
// Project/Custom/CustomConfig/Default*.ini only if CustomConfig is defined
{ TEXT("CustomConfig"), TEXT("{PROJECT}/Config/Custom/{CUSTOMCONFIG}/Default{TYPE}.ini"), EConfigLayerFlags::RequiresCustomConfig },
// Engine/Platform/Platform*.ini
{ TEXT("EnginePlatform"), TEXT("{ENGINE}/Config/{PLATFORM}/{PLATFORM}{TYPE}.ini") },
// Project/Platform/Platform*.ini
{ TEXT("ProjectPlatform"), TEXT("{PROJECT}/Config/{PLATFORM}/{PLATFORM}{TYPE}.ini") },
// Project/Platform/GeneratedPlatform*.ini Reserved for files generated by build process and should never be checked in
{ TEXT("ProjectPlatformGenerated"), TEXT("{PROJECT}/Config/{PLATFORM}/Generated{PLATFORM}{TYPE}.ini") },
// Project/Platform/Custom/CustomConfig/Platform*.ini only if CustomConfig is defined
{ TEXT("CustomConfigPlatform"), TEXT("{PROJECT}/Config/{PLATFORM}/Custom/{CUSTOMCONFIG}/{PLATFORM}{TYPE}.ini"), EConfigLayerFlags::RequiresCustomConfig },
// UserSettings/.../User*.ini
{ TEXT("UserSettingsDir"), TEXT("{USERSETTINGS}Unreal Engine/Engine/Config/User{TYPE}.ini"), EConfigLayerFlags::NoExpand },
// UserDir/.../User*.ini
{ TEXT("UserDir"), TEXT("{USER}Unreal Engine/Engine/Config/User{TYPE}.ini"), EConfigLayerFlags::NoExpand },
// Project/User*.ini
{ TEXT("GameDirUser"), TEXT("{PROJECT}/Config/User{TYPE}.ini"), EConfigLayerFlags::NoExpand },
};
/// <summary>
/// Plugins don't need to look at the same number of insane layers. Here PROJECT is the Plugin dir
/// </summary>
static FConfigLayer GPluginLayers[] =
{
// Engine/Base.ini
{ TEXT("AbsoluteBase"), TEXT("{ENGINE}/Config/Base.ini"), EConfigLayerFlags::NoExpand},
// Plugin/Base*.ini
{ TEXT("PluginBase"), TEXT("{PLUGIN}/Config/Base{TYPE}.ini") },
// Plugin/Default*.ini (we use Base and Default as we can have both depending on Engine or Project plugin, but going forward we should stick with Default)
{ TEXT("PluginDefault"), TEXT("{PLUGIN}/Config/Default{TYPE}.ini") },
// Plugin/Platform/Platform*.ini
{ TEXT("PluginPlatform"), TEXT("{PLUGIN}/Config/{PLATFORM}/{PLATFORM}{TYPE}.ini") },
// Project/Default.ini
{ TEXT("ProjectDefault"), TEXT("{PROJECT}/Config/Default{TYPE}.ini") },
// Project/Platform/.ini
{ TEXT("ProjectDefault"), TEXT("{PROJECT}/Config/{PLATFORM}/{PLATFORM}{TYPE}.ini") },
};
/**************************************************
**** CRITICAL NOTES
**** If you change these arrays, you need to also change EnumerateConfigFileLocations() in ConfigHierarchy.cs!!!
**************************************************/
static FConfigLayerExpansion GConfigExpansions[] =
{
// No replacements
{ nullptr, nullptr, nullptr, nullptr, EConfigExpansionFlags::All },
// Restricted Locations
{
TEXT("{ENGINE}/"), TEXT("{ENGINE}/Restricted/NotForLicensees/"),
TEXT("{PROJECT}/Config/"), TEXT("{RESTRICTEDPROJECT_NFL}/Config/"),
EConfigExpansionFlags::ForUncooked | EConfigExpansionFlags::ForCooked
},
{
TEXT("{ENGINE}/"), TEXT("{ENGINE}/Restricted/NoRedist/"),
TEXT("{PROJECT}/Config/"), TEXT("{RESTRICTEDPROJECT_NR}/Config/"),
EConfigExpansionFlags::ForUncooked
},
// Platform Extensions
{
TEXT("{ENGINE}/Config/{PLATFORM}/"), TEXT("{EXTENGINE}/Config/"),
TEXT("{PROJECT}/Config/{PLATFORM}/"), TEXT("{EXTPROJECT}/Config/"),
EConfigExpansionFlags::ForUncooked | EConfigExpansionFlags::ForCooked | EConfigExpansionFlags::ForPlugin
},
// Platform Extensions in Restricted Locations
//
// Regarding the commented EConfigExpansionFlags::ForPlugin expansions: in the interest of keeping plugin ini scanning fast,
// we disable these expansions for plugins because they are not used by Epic, and are unlikely to be used by licensees. If
// we can make scanning fast (caching what directories exist, etc), then we could turn this back on to be future-proof.
{
TEXT("{ENGINE}/Config/{PLATFORM}/"), TEXT("{ENGINE}/Restricted/NotForLicensees/Platforms/{PLATFORM}/Config/"),
TEXT("{PROJECT}/Config/{PLATFORM}/"), TEXT("{RESTRICTEDPROJECT_NFL}/Platforms/{PLATFORM}/Config/"),
EConfigExpansionFlags::ForUncooked | EConfigExpansionFlags::ForCooked // | EConfigExpansionFlags::ForPlugin
},
{
TEXT("{ENGINE}/Config/{PLATFORM}/"), TEXT("{ENGINE}/Restricted/NoRedist/Platforms/{PLATFORM}/Config/"),
TEXT("{PROJECT}/Config/{PLATFORM}/"), TEXT("{RESTRICTEDPROJECT_NR}/Platforms/{PLATFORM}/Config/"),
EConfigExpansionFlags::ForUncooked // | EConfigExpansionFlags::ForPlugin
},
};
///@file: Engine\Source\Runtime\Core\Private\Misc\ConfigContext.cpp
void FConfigContext::AddStaticLayersToHierarchy()
{
// remember where this file was loaded from
ConfigFile->SourceEngineConfigDir = EngineConfigDir;
ConfigFile->SourceProjectConfigDir = ProjectConfigDir;
// string that can have a reference to it, lower down
const FString DedicatedServerString = IsRunningDedicatedServer() ? TEXT("DedicatedServer") : TEXT("");
// cache some platform extension information that can be used inside the loops
const bool bHasCustomConfig = !FConfigCacheIni::GetCustomConfigString().IsEmpty();
// figure out what layers and expansions we will want
EConfigExpansionFlags ExpansionMode = EConfigExpansionFlags::ForUncooked;
FConfigLayer* Layers = GConfigLayers;
int32 NumLayers = UE_ARRAY_COUNT(GConfigLayers);
if (FPlatformProperties::RequiresCookedData())
{
ExpansionMode = EConfigExpansionFlags::ForCooked;
}
if (bIsForPlugin)
{
// this has priority over cooked/uncooked
ExpansionMode = EConfigExpansionFlags::ForPlugin;
Layers = GPluginLayers;
NumLayers = UE_ARRAY_COUNT(GPluginLayers);
}
// go over all the config layers
for (int32 LayerIndex = 0; LayerIndex < NumLayers; LayerIndex++)
{
const FConfigLayer& Layer = Layers[LayerIndex];
// skip optional layers
if (EnumHasAnyFlags(Layer.Flag, EConfigLayerFlags::RequiresCustomConfig) && !bHasCustomConfig)
{
continue;
}
// start replacing basic variables
FString LayerPath = PerformBasicReplacements(Layer.Path, *BaseIniName);
bool bHasPlatformTag = LayerPath.Contains(TEXT("{PLATFORM}"));
// expand if it it has {ED} or {EF} expansion tags
if (!EnumHasAnyFlags(Layer.Flag, EConfigLayerFlags::NoExpand))
{
// we assume none of the more special tags in expanded ones
checkfSlow(FCString::Strstr(Layer.Path, TEXT("{USERSETTINGS}")) == nullptr && FCString::Strstr(Layer.Path, TEXT("{USER}")) == nullptr, TEXT("Expanded config %s shouldn't have a {USER*} tags in it"), *Layer.Path);
// loop over all the possible expansions
for (int32 ExpansionIndex = 0; ExpansionIndex < UE_ARRAY_COUNT(GConfigExpansions); ExpansionIndex++)
{
// does this expansion match our current mode?
if ((GConfigExpansions[ExpansionIndex].Flags & ExpansionMode) == EConfigExpansionFlags::None)
{
continue;
}
FString ExpandedPath = PerformExpansionReplacements(GConfigExpansions[ExpansionIndex], LayerPath);
// if we didn't replace anything, skip it
if (ExpandedPath.Len() == 0)
{
continue;
}
// allow for override, only on BASE EXPANSION!
if (EnumHasAnyFlags(Layer.Flag, EConfigLayerFlags::AllowCommandLineOverride) && ExpansionIndex == 0)
{
checkfSlow(!bHasPlatformTag, TEXT("EConfigLayerFlags::AllowCommandLineOverride config %s shouldn't have a PLATFORM in it"), Layer.Path);
ConditionalOverrideIniFilename(ExpandedPath, *BaseIniName);
}
const FDataDrivenPlatformInfo& Info = FDataDrivenPlatformInfoRegistry::GetPlatformInfo(Platform);
// go over parents, and then this platform, unless there's no platform tag, then we simply want to run through the loop one time to add it to the
int32 NumPlatforms = bHasPlatformTag ? Info.IniParentChain.Num() + 1 : 1;
int32 CurrentPlatformIndex = NumPlatforms - 1;
int32 DedicatedServerIndex = -1;
// make DedicatedServer another platform
if (bHasPlatformTag && IsRunningDedicatedServer())
{
NumPlatforms++;
DedicatedServerIndex = CurrentPlatformIndex + 1;
}
for (int PlatformIndex = 0; PlatformIndex < NumPlatforms; PlatformIndex++)
{
const FString CurrentPlatform =
(PlatformIndex == DedicatedServerIndex) ? DedicatedServerString :
(PlatformIndex == CurrentPlatformIndex) ? Platform :
Info.IniParentChain[PlatformIndex];
FString PlatformPath = PerformFinalExpansions(ExpandedPath, CurrentPlatform);
// @todo restricted - ideally, we would move DedicatedServer files into a directory, like platforms are, but for short term compat,
// convert the path back to the original (DedicatedServer/DedicatedServerEngine.ini -> DedicatedServerEngine.ini)
if (PlatformIndex == DedicatedServerIndex)
{
PlatformPath.ReplaceInline(TEXT("Config/DedicatedServer/"), TEXT("Config/"));
}
// if we match the StartSkippingAtFilename, we are done adding to the hierarchy, so just return
if (PlatformPath == StartSkippingAtFilename)
{
return;
}
// add this to the list!
ConfigFile->SourceIniHierarchy.AddStaticLayer(PlatformPath, LayerIndex, ExpansionIndex, PlatformIndex);
}
}
}
// if no expansion, just process the special tags (assume no PLATFORM tags)
else
{
checkfSlow(!bHasPlatformTag, TEXT("Non-expanded config %s shouldn't have a PLATFORM in it"), *Layer.Path);
checkfSlow(!EnumHasAnyFlags(Layer.Flag, EConfigLayerFlags::AllowCommandLineOverride), TEXT("Non-expanded config can't have a EConfigLayerFlags::AllowCommandLineOverride"));
FString FinalPath = PerformFinalExpansions(LayerPath, TEXT(""));
// if we match the StartSkippingAtFilename, we are done adding to the hierarchy, so just return
if (FinalPath == StartSkippingAtFilename)
{
return;
}
// add with no expansion
ConfigFile->SourceIniHierarchy.AddStaticLayer(FinalPath, LayerIndex);
}
}
}
dynamic
这些主要用在插件(plugin)的配置文件中
bool FPluginManager::IntegratePluginsIntoConfig(FConfigCacheIni& ConfigSystem, const TCHAR* EngineIniName, const TCHAR* PlatformName, const TCHAR* StagedPluginsFile)
{
///...
for (const FString& ConfigFile : PluginConfigs)
{
FString BaseConfigFile = *FPaths::GetBaseFilename(ConfigFile);
// Use GetConfigFilename to find the proper config file to combine into, since it manages command line overrides and path sanitization
FString PluginConfigFilename = ConfigSystem.GetConfigFilename(*BaseConfigFile);
FConfigFile* FoundConfig = ConfigSystem.FindConfigFile(PluginConfigFilename);
if (FoundConfig != nullptr)
{
UE_LOG(LogPluginManager, Log, TEXT("Found config from plugin[%s] %s"), *Plugin.GetName(), *PluginConfigFilename);
FoundConfig->AddDynamicLayerToHierarchy(FPaths::Combine(PluginConfigDir, ConfigFile));
}
}
///...
}
runtime
在加载的过程中,会逐层执行ProcessIniContents>>FConfigFile::Combine>>FConfigFile::CombineFromBuffer中进行读取和覆盖。
///@file: Engine\Source\Runtime\Core\Private\Misc\ConfigContext.cpp
/**
* This will completely load .ini file hierarchy into the passed in FConfigFile. The passed in FConfigFile will then
* have the data after combining all of those .ini
*
* @param FilenameToLoad - this is the path to the file to
* @param ConfigFile - This is the FConfigFile which will have the contents of the .ini loaded into and Combined()
*
**/
static bool LoadIniFileHierarchy(const FConfigFileHierarchy& HierarchyToLoad, FConfigFile& ConfigFile, bool bUseCache, const TSet<FString>* IniCacheSet)
{
// Traverse ini list back to front, merging along the way.
for (const TPair<int32, FString>& HierarchyIt : HierarchyToLoad)
{
bool bDoCombine = (HierarchyIt.Key != 0);
const FString& IniFileName = HierarchyIt.Value;
// skip non-existant files
if (IsUsingLocalIniFile(*IniFileName, nullptr) && !DoesConfigFileExistWrapper(*IniFileName, IniCacheSet))
{
continue;
}
bool bDoEmptyConfig = false;
//UE_LOG(LogConfig, Log, TEXT( "Combining configFile: %s" ), *IniList(IniIndex) );
ProcessIniContents(*IniFileName, *IniFileName, &ConfigFile, bDoEmptyConfig, bDoCombine);
}
// Set this configs files source ini hierarchy to show where it was loaded from.
ConfigFile.SourceIniHierarchy = HierarchyToLoad;
return true;
}
well-knonw配置类型
这里列出了一些熟知配置文件,其中包括了最为常见的Engine、Game两种类型。
///@file: Engine\Source\Runtime\Core\Public\Misc\ConfigCacheIni.h
#define ENUMERATE_KNOWN_INI_FILES(op) \
op(Engine) \
op(Game) \
op(Input) \
op(DeviceProfiles) \
op(GameUserSettings) \
op(Scalability) \
op(RuntimeOptions) \
op(InstallBundle) \
op(Hardware) \
op(GameplayTags)
#define KNOWN_INI_ENUM(IniName) IniName,
///@file:Engine\Source\Runtime\Core\Private\Misc\ConfigCacheIni.cpp
FConfigCacheIni::FKnownConfigFiles::FKnownConfigFiles()
{
// set the FNames associated with each file
// Files[(uint8)EKnownIniFile::Engine].IniName = FName("Engine");
#define SET_KNOWN_NAME(Ini) Files[(uint8)EKnownIniFile::Ini].IniName = FName(#Ini);
ENUMERATE_KNOWN_INI_FILES(SET_KNOWN_NAME);
#undef SET_KNOWN_NAME
}
Saved
///@file: Engine\Source\Runtime\Core\Private\Misc\ConfigContext.cpp
bool FConfigContext::PrepareForLoad(bool& bPerformLoad)
{
checkf(ConfigSystem != nullptr || ConfigFile != nullptr, TEXT("Loading config expects to either have a ConfigFile already passed in, or have a ConfigSystem passed in"));
if (bForceReload)
{
// re-use an existing ConfigFile's Engine/Project directories if we have a config system to look in,
// or no config system and the platform matches current platform (which will look in GConfig)
if (ConfigSystem != nullptr || (Platform == FPlatformProperties::IniPlatformName() && GConfig != nullptr))
{
bool bNeedRecache = false;
FConfigCacheIni* SearchSystem = ConfigSystem == nullptr ? GConfig : ConfigSystem;
FConfigFile* BaseConfigFile = SearchSystem->FindConfigFileWithBaseName(*BaseIniName);
if (BaseConfigFile != nullptr)
{
if (!BaseConfigFile->SourceEngineConfigDir.IsEmpty() && BaseConfigFile->SourceEngineConfigDir != EngineConfigDir)
{
EngineConfigDir = BaseConfigFile->SourceEngineConfigDir;
bNeedRecache = true;
}
if (!BaseConfigFile->SourceProjectConfigDir.IsEmpty() && BaseConfigFile->SourceProjectConfigDir != ProjectConfigDir)
{
ProjectConfigDir = BaseConfigFile->SourceProjectConfigDir;
bNeedRecache = true;
}
if (bNeedRecache)
{
CachePaths();
}
}
}
}
// setup for writing out later on
if (bWriteDestIni || bAllowGeneratedIniWhenCooked || FPlatformProperties::RequiresCookedData())
{
// delay filling out GeneratedConfigDir because some early configs can be read in that set -savedir, and
// FPaths::GeneratedConfigDir() will permanently cache the value
if (GeneratedConfigDir.IsEmpty())
{
GeneratedConfigDir = FPaths::GeneratedConfigDir();
}
// calculate where this file will be saved/generated to (or at least the key to look up in the ConfigSystem)
DestIniFilename = FConfigCacheIni::GetDestIniFilename(*BaseIniName, *SavePlatform, *GeneratedConfigDir);
if (bAllowRemoteConfig)
{
// Start the loading process for the remote config file when appropriate
if (FRemoteConfig::Get()->ShouldReadRemoteFile(*DestIniFilename))
{
FRemoteConfig::Get()->Read(*DestIniFilename, *BaseIniName);
}
FRemoteConfigAsyncIOInfo* RemoteInfo = FRemoteConfig::Get()->FindConfig(*DestIniFilename);
if (RemoteInfo && (!RemoteInfo->bWasProcessed || !FRemoteConfig::Get()->IsFinished(*DestIniFilename)))
{
// Defer processing this remote config file to until it has finish its IO operation
bPerformLoad = false;
return false;
}
}
}
// we can re-use an existing file if:
// we are not loading into an existing ConfigFile
// we don't want to reload
// we found an existing file in the ConfigSystem
// the existing file has entries (because Known config files are always going to be found, but they will be empty)
bool bLookForExistingFile = ConfigFile == nullptr && !bForceReload && ConfigSystem != nullptr;
if (bLookForExistingFile)
{
// look up a file that already exists and matches the name
FConfigFile* FoundConfigFile = ConfigSystem->KnownFiles.GetMutableFile(*BaseIniName);
if (FoundConfigFile == nullptr)
{
FoundConfigFile = ConfigSystem->FindConfigFile(*DestIniFilename);
//// @todo: this is test to see if we can simplify this to FindConfigFileWithBaseName always (if it never fires, we can)
//check(FoundConfigFile == nullptr || FoundConfigFile == ConfigSystem->FindConfigFileWithBaseName(*BaseIniName))
}
if (FoundConfigFile != nullptr && FoundConfigFile->Num() > 0)
{
ConfigFile = FoundConfigFile;
bPerformLoad = false;
return true;
}
}
// setup ConfigFile to read into if one isn't already set
if (ConfigFile == nullptr)
{
// first look for a KnownFile
ConfigFile = ConfigSystem->KnownFiles.GetMutableFile(*BaseIniName);
if (ConfigFile == nullptr)
{
check(!DestIniFilename.IsEmpty());
ConfigFile = &ConfigSystem->Add(DestIniFilename, FConfigFile());
}
}
bPerformLoad = true;
return true;
}
bool FConfigContext::Load(const TCHAR* InBaseIniName, FString& OutFinalFilename)
{
// for single file loads, just return early of the file doesn't exist
const bool bBaseIniNameIsFullInIFilePath = FString(InBaseIniName).EndsWith(TEXT(".ini"));
if (!bIsHierarchicalConfig && bBaseIniNameIsFullInIFilePath && !DoesConfigFileExistWrapper(InBaseIniName, IniCacheSet))
{
return false;
}
if (bCacheOnNextLoad || BaseIniName != InBaseIniName)
{
ResetBaseIni(InBaseIniName);
CachePaths();
bCacheOnNextLoad = false;
}
bool bPerformLoad;
if (!PrepareForLoad(bPerformLoad))
{
return false;
}
// if we are reloading a known ini file (where OutFinalIniFilename already has a value), then we need to leave the OutFinalFilename alone until we can remove LoadGlobalIniFile completely
if (OutFinalFilename.Len() > 0 && OutFinalFilename == BaseIniName)
{
// do nothing
}
else
{
check(!bWriteDestIni || !DestIniFilename.IsEmpty());
OutFinalFilename = DestIniFilename;
}
// now load if we need (PrepareForLoad may find an existing file and just use it)
return bPerformLoad ? PerformLoad() : true;
}
由于Generated文件夹默认是存储在Saved文件夹,所以通常可写的内容都是写入到Saved\Config文件夹下,这也是为什么这个文件夹下的Config内容会自动生成的原因。
const TCHAR* FGenericPlatformMisc::GeneratedConfigDir()
{
static FString Dir = FPaths::ProjectSavedDir() / TEXT("Config/");
return *Dir;
}
const FString& FPaths::ProjectSavedDir()
{
FStaticData& StaticData = TLazySingleton<FStaticData>::Get();
if (!StaticData.bGameSavedDirInitialized)
{
StaticData.GameSavedDir = UE4Paths_Private::GameSavedDir();
StaticData.bGameSavedDirInitialized = true;
}
return StaticData.GameSavedDir;
}
栗子
确定ini配置
在启动过程中,使用"EditorSettings"作为ini的base名字,经过配置获得的GEditorSettingsIni变量值为"../../../Engine/Saved/Config/WindowsEditor/EditorSettings.ini"。
///@file: Engine\Source\Runtime\Core\Private\Misc\ConfigCacheIni.cpp
static void LoadRemainingConfigFiles(FConfigContext& Context)
{
SCOPED_BOOT_TIMING("LoadRemainingConfigFiles");
#if PLATFORM_DESKTOP
// load some desktop only .ini files
Context.Load(TEXT("Compat"), GCompatIni);
Context.Load(TEXT("Lightmass"), GLightmassIni);
#endif
#if WITH_EDITOR
// load some editor specific .ini files
Context.Load(TEXT("Editor"), GEditorIni);
// Upgrade editor user settings before loading the editor per project user settings
FConfigManifest::MigrateEditorUserSettings();
Context.Load(TEXT("EditorPerProjectUserSettings"), GEditorPerProjectIni);
// Project agnostic editor ini files, so save them to a shared location (Engine, not Project)
Context.GeneratedConfigDir = FPaths::EngineEditorSettingsDir();
Context.Load(TEXT("EditorSettings"), GEditorSettingsIni);
Context.Load(TEXT("EditorKeyBindings"), GEditorKeyBindingsIni);
Context.Load(TEXT("EditorLayout"), GEditorLayoutIni);
#endif
if (FParse::Param(FCommandLine::Get(), TEXT("dumpconfig")))
{
GConfig->Dump(*GLog);
}
}
读取键值
从全局配置cache中读取
FString GameEngineClassName;
GConfig->GetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), GameEngineClassName, GEngineIni);
// Find the editor target
FString EditorTargetFileName;
FString DefaultEditorTarget;
GConfig->GetString(TEXT("/Script/BuildSettings.BuildSettings"), TEXT("DefaultEditorTarget"), DefaultEditorTarget, GEngineIni);