In the UE engine, a large number of configurations are set and controlled using ini files. For projects, understanding which of these can be updated can help establish rules for updating the project. Moreover, many functionalities in UE are realized through configurations and dynamic switches, such as CVars, as well as Device Profiles settings for specific platforms or devices, which can similarly implement dynamic dispatch and application of configurations during hot updates.
This article analyzes the loading process of ini config from the engine mechanism, how different config modules are reloaded after hot updates, and how to apply them in projects, focusing on the runtime modification and reapplication of parameters in the engine or project based on ini configuration hot updates, enhancing the ability to update games.
Before studying how to update ini files, we first need to analyze how the engine loads ini and applies it to the game. Previously, I wrote an article analyzing the loading mechanism of GConfig: Analyzing UE Code: GConfig Loading. By default, ini files are loaded when the engine starts, and they are one of the first files to be loaded; subsequent engine module startups rely on these configurations. The official documentation: Configuration Files.
The engine also has a loading phase for the order of Module loading called PostConfigInit
, which ensures that modules are started only after the configuration files are loaded, avoiding cases where the configuration files cannot be read:
1 | // Phase at which this module should be loaded during startup. |
The code that starts the engine:
1 | bool FEngineLoop::AppInit() |
At the end of the FEngineLoop::AppInit
function, FCoreDelegates::OnInit.Broadcast();
is executed. This Delegate is bound to InitUObject()
in FCoreUObjectModule's
StartupModule, which initializes UObject in the engine. At this stage, the Config has also been fully loaded, so creating UObject can correctly read data from Config.
The article Analyzing UE Code: GConfig Loading describes the ini file loading process performed by FConfigCacheIni::InitializeConfigSystem
, which we will not repeat here. It can be understood that after the execution completes, the Config is in a usable state.
Hierarchy and Priority of Ini Files
Here are several aspects to note regarding updating UE’s ini files:
- Configurations of the same category in UE are composed of multiple levels of ini file hierarchy.
- Ini files at different levels have different priorities, where higher priority values will overwrite lower priority ones.
Taking the Engine configuration as an example, there are 28 configurational files that can be configured within the engine, and other category configurations like Game/Input/DeviceProfiles, etc., are similar (UE4.25):
These files (if they exist) can all be loaded and are arranged in ascending order of priority. This means that options in PROJECT_DIR/Config/DefaultEngine.ini
can replace those in ENGINE_DIR/Config/BaseEngine.ini
.
In typical project development, by default, the project attributes are usually configured in DefaultEngine.ini
, while configurations for a specific platform are in Config/Windows/WindowsEngine.ini
. It is rare to have so many configuration files, which can lead to a certain degree of confusion.
Thus, during updates, one can directly use Config/Default*.ini
and Config/PLATFORM/PLATFORM*.ini
for updates. It’s also possible to use files that originally did not exist for updates, such as if Config/UserEngine.ini
did not exist in the project, one can write the needed configuration changes to this file during updates and read them during runtime to append to the engine, and it is also supported in the automatic mounting method since the priority of Config/User*.ini
is the highest.
To enable ini updates, one must implement:
- Packing specified ini files during updates.
- Being able to obtain the already loaded FConfigFile instances in the engine based on the update information and append the new configurations.
- Updating the data in UObject objects that use the new configurations.
Loading Config in UObject
Before handling updates, it’s necessary to understand the process of loading config in UObject (only CDO of classes marked as Config
will execute loading). The stack is as follows:
The general process is:
- Check whether UClass has the
CLASS_Config
flag. - Check whether the Property has the
CPF_Config
flag. - Deserialize the attributes of CDO from strings.
For example:
1 | UCLASS(config=GameUserSettings) |
In this class, the UCLASS is appended with Config
, and UHT generates the CLASS_Config
flag for its UClass. Furthermore, the data member ival
of this class also adds Config
in UPROPERTY
, increasing CPF_Config
flag for the current property’s FProperty
, thus achieving unity between code and loading.
The specific loading implementation can be found in the CoreUObject\Private\UObject\Obj.cpp
file’s LoadConfig
function.
When creating non-CDO objects, all properties are copied from CDO (after the constructor executes):
1 | // Binary initialize object properties to zero or defaults. |
Therefore, the use of Config tagged by UObject affects game use in two steps:
- Load ini at engine startup, create CDO, and read configurations from ini.
- When creating Non-CDO objects, copy data from CDO.
If one wishes to override the configuration values in ini, they cannot be directly written in the constructor because the timing of LoadConfig occurs later than the constructor. However, one can override the PostInitProperties
function, which is called after LoadConfig:
1 | virtual void PostInitProperties()override; |
Updating Configuration Values in UObject
In terms of the need for hot updates, for the Config used by UObject, we can intervene in the process before the object is created to modify the CDO, and after the CDO is created, we can also call ReloadConfig on the objects to reload from file.
1 | bool UFlibUGConfigReloadHelper::ReloadIniFile(const FString& StrippedName,const FString& File) |
As shown in the code, first obtain all UClasses that have the Config flag in the engine, and ensure that the ini name bound to the Class matches with the ini that needs to be reloaded. Then iterate through each instance of UClass and call ReloadConfig on them.
Note: In the above code, the corresponding
FConfigFile
instance is modified first because the old ini file is already loaded at engine startup and stored inFConfigFile
. Before calling ReloadConfig, it needs to be updated first, so that ReloadConfig can read new data from GConfig.
Console Variables
In UE, Console Variables can be used as a way to pass data between modules. In my previous notes, I mentioned ways to obtain and modify Console Variables at runtime: Various Ways to Set ConsoleVariables Values.
There are two methods for automatically applying Console Variable values during engine startup:
- Settings can be made in
Engine/Config/ConsoleVariables.ini
under[Startup]
:
1 | [Startup] |
- Specifying them in the Engine category ini hierarchy (like
DefaultEngine.ini
), under[ConsoleVariables]
:
1 | [ConsoleVariables] |
When we update the Engine category’s FConfigCache, we can call the following function:
1 | ::ApplyCVarSettingsFromIni(TEXT("ConsoleVariables"), *GEngineIni, ECVF_SetBySystemSettingsIni); |
to apply the latest CVars values from [ConsoleVariables]
. If one wishes to update both settings in [Startup]
of ConsoleVariable.ini
and the settings in [ConsoleVariables]
, one can call:
1 | FConfigCacheIni::LoadConsoleVariablesFromINI(); |
This will first apply from [Startup]
, followed by applying from [ConsoleVariables]
.
The update process steps are:
- Reload the Engine category ini files.
- Call the ApplyCVarSettingsFromIni function.
Device Profiles
Device Profiles is a method in UE for setting parameters for specific platforms or devices. The official documentation: Setting Device Profiles. It can be used to achieve performance and parameter adaptations across different platforms and devices.
The default provided configurations of platform/device names with ini platform names in the engine: BaseDeviceProfiles.ini#L6 are stored under DeviceProfiles
in *DeviceProfiles.ini
, with the values of DeviceProfileNameAndTypes
. The BaseDeviceProfiles.ini includes dependencies for profiles, device specifications, iOS device mappings, specific device configurations, etc., and settings in your own project can refer to these.
Moreover, UE’s Device Profiles support inheritance, like iPhoneX->IOS_High->IOS->Mobile, which conveniently allows configuration reuse.
The UDeviceProfile
class is also marked as Config
and is perObjectConfig
, meaning each object’s configuration will be saved and loaded separately. In simple terms, the loading of Device Profiles is essentially the creation of many objects, each saving its configuration information for its platform or device. The Name passed in NewObject<UDeviceProfile>
is the platform name.
When creating a UDeviceProfile, it automatically loads from the ini (stack of NewObject<UDeviceProfile>
):
Since the loading of Device Profiles configurations also follows the default LoadConfig process, we can update UDeviceProfiles following the steps in Updating Configuration Values in UObject; we can obtain all UDeviceProfile instances and call ReloadConfig on them and also call it on their Parent Profiles (Parent Profiles are also UDeviceProfile objects):
1 | void UFlibUGConfigReloadHelper::RecursiveReloadDeviceProfile(UDeviceProfile* Profile) |
After that, one can call ReapplyDeviceProfile
from UDeviceProfileManager
:
1 | UDeviceProfileManager::Get().ReapplyDeviceProfile(); |
GameUserSettings
After overriding the configuration of GameUserSettings
, one can call ApplySettings
for application:
1 | void UFlibUGConfigReloadHelper::ReapplyGameUserSettings() |
The ApplySettings
code is as follows:
1 | void UGameUserSettings::ApplySettings(bool bCheckForCommandLineOverrides) |
Conclusion
The loading of various configuration files in UE is actually based on the hierarchical update of UE’s ini files. First, the values in GConfig need to be updated, and then applied to different modules. This article enumerates several different configurations’ reloading and application; other unlisted situations can be handled in the same way.