diff --git a/Source/HarmonyLink/Private/HarmonyLink.cpp b/Source/HarmonyLink/Private/HarmonyLink.cpp index b508013..167806e 100644 --- a/Source/HarmonyLink/Private/HarmonyLink.cpp +++ b/Source/HarmonyLink/Private/HarmonyLink.cpp @@ -10,11 +10,12 @@ DEFINE_LOG_CATEGORY(LogHarmonyLink); void FHarmonyLinkModule::StartupModule() { - UHarmonyLinkGraphics::GetSettings(); + } void FHarmonyLinkModule::ShutdownModule() { + // Ensure we safely destroy our singleton instance UHarmonyLinkGraphics::DestroySettings(); } diff --git a/Source/HarmonyLink/Private/Objects/HarmonyLinkGraphics.cpp b/Source/HarmonyLink/Private/Objects/HarmonyLinkGraphics.cpp index bd6b1b5..62c6c14 100644 --- a/Source/HarmonyLink/Private/Objects/HarmonyLinkGraphics.cpp +++ b/Source/HarmonyLink/Private/Objects/HarmonyLinkGraphics.cpp @@ -5,11 +5,13 @@ #include "ComponentRecreateRenderStateContext.h" #include "HarmonyLink.h" #include "HarmonyLinkLibrary.h" +#include "GameFramework/GameUserSettings.h" -UHarmonyLinkGraphics* UHarmonyLinkGraphics::Instance = nullptr; -FString UHarmonyLinkGraphics::IniLocation = "HarmonyLink"; +UHarmonyLinkGraphics* UHarmonyLinkGraphics::_INSTANCE = nullptr; +FString UHarmonyLinkGraphics::_IniLocation = "HarmonyLink"; +int32 UHarmonyLinkGraphics::_TickRate = 1; -TMap> UHarmonyLinkGraphics::DefaultSettings = { +TMap> UHarmonyLinkGraphics::_DefaultSettings = { { "Battery", { { TEXT("r.ReflectionMethod"), FHLConfigValue(0) }, { TEXT("r.DynamicGlobalIlluminationMethod"), FHLConfigValue(2) }, @@ -33,6 +35,14 @@ TMap> UHarmonyLinkGraphics::DefaultSettings = UHarmonyLinkGraphics::UHarmonyLinkGraphics() { UE_LOG(LogHarmonyLink, Warning, TEXT("HarmonyLinkGraphics initialized.")); + + _bAutomaticSwitch = true; + FWorldDelegates::OnPostWorldInitialization.AddUObject(this, &UHarmonyLinkGraphics::OnPostWorldInitialization); +} + +UHarmonyLinkGraphics::~UHarmonyLinkGraphics() +{ + FWorldDelegates::OnPostWorldInitialization.RemoveAll(this); } void UHarmonyLinkGraphics::LoadConfig(const bool bForceReload) @@ -56,12 +66,18 @@ bool UHarmonyLinkGraphics::LoadSettingsFromConfig() const FString Filename = GetDefaultConfigFilename(); - if (FConfigCacheIni::LoadLocalIniFile(ConfigFile, *Filename, true, nullptr, false)) + if (!GConfig) + { + UE_LOG(LogHarmonyLink, Error, TEXT("Failed to access GConfig!")) + return false; + } + + if (GConfig->LoadLocalIniFile(ConfigFile, *Filename, false, nullptr, false)) { // Load each profile section bool bLoaded = true; - for (const TPair& Profile : ProfileNames) + for (const TPair& Profile : _ProfileNames) { if (!LoadSection(ConfigFile, Profile)) { @@ -90,7 +106,7 @@ bool UHarmonyLinkGraphics::LoadSection(const FConfigFile& ConfigFile, const TPai if (const FConfigSection* Section = ConfigFile.FindSection(*SectionName.ToString())) { - FSettingsProfile& SettingsProfile = Profiles.FindOrAdd(ProfileKey); + FSettingsProfile& SettingsProfile = _Profiles.FindOrAdd(ProfileKey); SettingsProfile.SectionName = SectionName; SettingsProfile.Settings.Empty(); // Clear previous settings @@ -137,7 +153,7 @@ void UHarmonyLinkGraphics::SaveConfig() const { QUICK_SCOPE_CYCLE_COUNTER(HarmonyLinkGraphics_SaveConfig); - for (TPair Profile : Profiles) + for (TPair Profile : _Profiles) { SaveSection(Profile.Value); } @@ -147,20 +163,119 @@ void UHarmonyLinkGraphics::SaveConfig() const GConfig->Flush(false, Filename); } +void UHarmonyLinkGraphics::SetSetting(const EProfile Profile, const FName Setting, const FHLConfigValue Value) +{ + // Find the profile name associated with the given EProfile + const FName* ProfileName = _ProfileNames.Find(Profile); + + if (!ProfileName) + { + UE_LOG(LogHarmonyLink, Error, TEXT("Profile not found.")); + return; + } + + FString TypeString; + FString ValueString; + + switch (Value.GetType()) + { + case EConfigValueType::Int: + ValueString = FString::FromInt(Value.GetValue()); + TypeString = TEXT("int"); + break; + case EConfigValueType::Float: + ValueString = FString::SanitizeFloat(Value.GetValue()); + TypeString = TEXT("float"); + break; + case EConfigValueType::Bool: + ValueString = Value.GetValue() ? TEXT("true") : TEXT("false"); + TypeString = TEXT("bool"); + break; + case EConfigValueType::String: + ValueString = Value.GetValue(); + TypeString = TEXT("string"); + break; + } + + UE_LOG(LogHarmonyLink, Log, TEXT("Applying '%s': Value='%s', Type='%s' to profile '%s'."), *Setting.ToString(), *TypeString, *ValueString, *ProfileName->ToString()); + + // Find the settings associated with the profile + FSettingsProfile* SettingsProfile = _Profiles.Find(Profile); + + if (!SettingsProfile) + { + UE_LOG(LogHarmonyLink, Error, TEXT("No settings found for profile %s."), *ProfileName->ToString()); + return; + } + + SettingsProfile->Settings.FindOrAdd(Setting) = Value; +} UHarmonyLinkGraphics* UHarmonyLinkGraphics::GetSettings() { // Check if we already initialised - if (Instance) + if (_INSTANCE) { - return Instance; + return _INSTANCE; } // Proceed to create a new singleton instance - Instance = NewObject(); - Instance->AddToRoot(); - Instance->LoadConfig(); + _INSTANCE = NewObject(); + _INSTANCE->AddToRoot(); + _INSTANCE->Init(); + + return _INSTANCE; +} + +FSettingsProfile UHarmonyLinkGraphics::GetSettingProfile(const EProfile Profile) +{ + // Find the profile name associated with the given EProfile + const FName* ProfileName = _ProfileNames.Find(Profile); + + if (!ProfileName) + { + UE_LOG(LogHarmonyLink, Error, TEXT("Profile not found.")); + return FSettingsProfile(); + } + + // Find the settings associated with the profile + FSettingsProfile* SettingsProfile = _Profiles.Find(Profile); + + if (!SettingsProfile) + { + UE_LOG(LogHarmonyLink, Error, TEXT("No settings found for profile %s."), *ProfileName->ToString()); + return FSettingsProfile(); + } + + return *SettingsProfile; + +} + +EProfile UHarmonyLinkGraphics::GetActiveProfile() const +{ + return _ActiveProfile; +} + +void UHarmonyLinkGraphics::SetAutomaticSwitching(const bool bAutomaticSwitch) +{ + _bAutomaticSwitch = bAutomaticSwitch; +} + +void UHarmonyLinkGraphics::DestroySettings() +{ + if (_INSTANCE) + { + FWorldDelegates::OnPostWorldInitialization.RemoveAll(_INSTANCE); + _INSTANCE->RemoveFromRoot(); + _INSTANCE->MarkAsGarbage(); + _INSTANCE = nullptr; + } +} + +void UHarmonyLinkGraphics::Init() +{ + LoadConfig(); const FBattery BatteryStatus = UHarmonyLinkLibrary::GetBatteryStatus(); @@ -169,19 +284,35 @@ UHarmonyLinkGraphics* UHarmonyLinkGraphics::GetSettings() // BUG: Remove this before release! if (!BatteryStatus.HasBattery) { - Instance->ApplyProfile(EProfile::BATTERY); + ApplyProfile(EProfile::BATTERY); } - - return Instance; } -void UHarmonyLinkGraphics::DestroySettings() +void UHarmonyLinkGraphics::Tick() { - if (Instance) + if (!_bAutomaticSwitch) { - Instance->RemoveFromRoot(); - Instance->MarkAsGarbage(); - Instance = nullptr; + return; + } + + const FBattery BatteryStatus = UHarmonyLinkLibrary::GetBatteryStatus(); + + if (BatteryStatus.HasBattery) + { + if (BatteryStatus.IsACConnected) + { + if (_ActiveProfile != EProfile::CHARGING) + { + ApplyProfile(EProfile::CHARGING); + } + } + else + { + if (_ActiveProfile != EProfile::BATTERY) + { + ApplyProfile(EProfile::BATTERY); + } + } } } @@ -242,38 +373,86 @@ void UHarmonyLinkGraphics::LoadDefaults() { UE_LOG(LogHarmonyLink, Log, TEXT("LoadDefaults started.")); - Profiles.Reset(); + _Profiles.Reset(); // Iterate over ProfileNames to create default settings - for (const TPair& Profile : ProfileNames) + for (const TPair& Profile : _ProfileNames) { FSettingsProfile NewProfileSettings; NewProfileSettings.SectionName = Profile.Value; - if (const TMap* Settings = DefaultSettings.Find(Profile.Value)) + if (const TMap* Settings = _DefaultSettings.Find(Profile.Value)) { NewProfileSettings.Settings = *Settings; } - Profiles.Add(Profile.Key, NewProfileSettings); + _Profiles.Add(Profile.Key, NewProfileSettings); + } +} + +void UHarmonyLinkGraphics::OnPostWorldInitialization(UWorld* World, UWorld::InitializationValues IVS) +{ + if (!World) + { + UE_LOG(LogHarmonyLink, Error, TEXT("Failed to Hook into World Initialisation!")) + return; + } + + if (World->IsGameWorld()) + { + if (UHarmonyLinkGraphics* Settings = GetSettings()) + { + if (World->IsPlayInEditor()) + { + Settings->LoadConfig(true); + } + + if (!World->GetTimerManager().TimerExists(Settings->_TickTimerHandle) || !World->GetTimerManager().IsTimerActive(Settings->_TickTimerHandle)) + { + World->GetTimerManager().SetTimer(Settings->_TickTimerHandle, [Settings] + { + Settings->Tick(); + }, _TickRate, true); + } + } } } bool UHarmonyLinkGraphics::ApplyProfile(const EProfile Profile) { + // If the profile is None, revert to the original user game settings + if (Profile == EProfile::NONE) + { + UE_LOG(LogHarmonyLink, Log, TEXT("Reverting to original user game settings.")); + + if (UGameUserSettings* UserSettings = GEngine->GetGameUserSettings()) + { + UserSettings->LoadSettings(true); + UserSettings->ApplySettings(true); + _ActiveProfile = EProfile::NONE; + OnProfileChanged.Broadcast(_ActiveProfile); + UE_LOG(LogHarmonyLink, Log, TEXT("Original user game settings applied.")); + return true; + } + + UE_LOG(LogHarmonyLink, Warning, TEXT("Failed to get user game settings.")); + return false; + } + + // Find the profile name associated with the given EProfile - const FName* ProfileName = ProfileNames.Find(Profile); + const FName* ProfileName = _ProfileNames.Find(Profile); if (!ProfileName) { - UE_LOG(LogHarmonyLink, Warning, TEXT("Profile not found.")); + UE_LOG(LogHarmonyLink, Error, TEXT("Profile not found.")); return false; } UE_LOG(LogHarmonyLink, Log, TEXT("Applying profile %s."), *ProfileName->ToString()); // Find the settings associated with the profile - FSettingsProfile* SettingsProfile = Profiles.Find(Profile); + FSettingsProfile* SettingsProfile = _Profiles.Find(Profile); if (!SettingsProfile) { @@ -288,13 +467,15 @@ bool UHarmonyLinkGraphics::ApplyProfile(const EProfile Profile) for (const TPair& Setting : SettingsProfile->Settings) { // Example of logging each setting being applied - UE_LOG(LogHarmonyLink, Log, TEXT("Applying setting: %s = %s"), + UE_LOG(LogHarmonyLink, Log, TEXT("Patching CVar override: %s = %s"), *Setting.Key.ToString(), *Setting.Value.ToString()); ApplySetting(Setting); } } + _ActiveProfile = Profile; + OnProfileChanged.Broadcast(_ActiveProfile); return true; } @@ -335,7 +516,7 @@ void UHarmonyLinkGraphics::DebugPrintProfiles() const { UE_LOG(LogHarmonyLink, Log, TEXT("DebugPrintProfiles started.")); - for (TPair Profile : Profiles) + for (TPair Profile : _Profiles) { PrintDebugSection(Profile.Value); } @@ -378,6 +559,6 @@ void UHarmonyLinkGraphics::PrintDebugSection(FSettingsProfile& SettingsProfile) void UHarmonyLinkGraphics::ResetInstance() { - Instance->DestroySettings(); + _INSTANCE->DestroySettings(); GetSettings(); } diff --git a/Source/HarmonyLink/Public/Objects/HarmonyLinkGraphics.h b/Source/HarmonyLink/Public/Objects/HarmonyLinkGraphics.h index 64f5913..69e167a 100644 --- a/Source/HarmonyLink/Public/Objects/HarmonyLinkGraphics.h +++ b/Source/HarmonyLink/Public/Objects/HarmonyLinkGraphics.h @@ -9,6 +9,8 @@ #include "UObject/Object.h" #include "HarmonyLinkGraphics.generated.h" +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnProfileChanged, EProfile, Profile); + /** * */ @@ -19,6 +21,10 @@ class HARMONYLINK_API UHarmonyLinkGraphics : public UObject public: UHarmonyLinkGraphics(); + virtual ~UHarmonyLinkGraphics() override; + + UPROPERTY(BlueprintAssignable) + FOnProfileChanged OnProfileChanged; UFUNCTION(BlueprintCallable, Category="HarmonyLink Settings") void LoadConfig(const bool bForceReload = false); @@ -26,19 +32,37 @@ public: UFUNCTION(BlueprintCallable, Category="HarmonyLink Settings") void SaveConfig() const; + UFUNCTION(BlueprintCallable, Category="HarmonyLink Settings") + void SetSetting(EProfile Profile, FName Setting, FHLConfigValue Value); + + UFUNCTION(BlueprintCallable, Category="HarmonyLink Settings") + bool ApplyProfile(EProfile Profile); + /** Returns the game local machine settings (resolution, windowing mode, scalability settings, etc...) */ UFUNCTION(BlueprintCallable, Category="HarmonyLink Settings") static UHarmonyLinkGraphics* GetSettings(); + UFUNCTION(BlueprintCallable, BlueprintPure, Category="HarmonyLink Settings") + FSettingsProfile GetSettingProfile(const EProfile Profile); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category="HarmonyLink Settings") + EProfile GetActiveProfile() const; + + UFUNCTION(BlueprintCallable, Category="HarmonyLink Settings") + void SetAutomaticSwitching(const bool bAutomaticSwitch); + static void DestroySettings(); private: + void Init(); + void Tick(); void CreateDefaultConfigFile(); bool LoadSettingsFromConfig(); bool LoadSection(const FConfigFile& ConfigFile, const TPair Profile); void SaveSection(FSettingsProfile& SettingsProfile, const bool bFlush = false) const; void LoadDefaults(); - bool ApplyProfile(EProfile Profile); + + void OnPostWorldInitialization(UWorld* World, UWorld::InitializationValues IVS); static void ResetInstance(); @@ -48,17 +72,26 @@ private: void DebugPrintProfiles() const; static void PrintDebugSection(FSettingsProfile& SettingsProfile); - static FString IniLocation; + static FString _IniLocation; - TMap ProfileNames = { + uint8 _bAutomaticSwitch :1; + + // How many times to query HarmonyLinkLib for hardware info + static int32 _TickRate; + + FTimerHandle _TickTimerHandle; + + EProfile _ActiveProfile = EProfile::NONE; + + TMap _ProfileNames = { {EProfile::BATTERY, "Battery"}, {EProfile::CHARGING, "Charging"}, {EProfile::DOCKED, "Docked"}, }; - TMap Profiles; + TMap _Profiles; - static TMap> DefaultSettings; + static TMap> _DefaultSettings; - static UHarmonyLinkGraphics* Instance; + static UHarmonyLinkGraphics* _INSTANCE; }; diff --git a/Source/HarmonyLink/Public/Structs/HLConfigValue.h b/Source/HarmonyLink/Public/Structs/HLConfigValue.h index 98711ec..2ce69f3 100644 --- a/Source/HarmonyLink/Public/Structs/HLConfigValue.h +++ b/Source/HarmonyLink/Public/Structs/HLConfigValue.h @@ -14,25 +14,29 @@ enum class EConfigValueType : uint8 String }; +/** + * Note: In Blueprints, all values will be visible, but only the value corresponding to the 'Type' will be used. + */ USTRUCT(BlueprintType) struct FHLConfigValue { GENERATED_BODY() private: - UPROPERTY(EditAnywhere, Category="ConfigValue") + // Allow Blueprint access to these private variables + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ConfigValue", meta = (AllowPrivateAccess = "true")) EConfigValueType Type; - UPROPERTY(EditAnywhere, Category="ConfigValue") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ConfigValue", meta = (AllowPrivateAccess = "true", EditCondition = "Type == EConfigValueType::Int", EditConditionHides)) int32 IntValue; - UPROPERTY(EditAnywhere, Category="ConfigValue") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ConfigValue", meta = (AllowPrivateAccess = "true", EditCondition = "Type == EConfigValueType::Float", EditConditionHides)) float FloatValue; - UPROPERTY(EditAnywhere, Category="ConfigValue") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ConfigValue", meta = (AllowPrivateAccess = "true", EditCondition = "Type == EConfigValueType::Bool", EditConditionHides)) bool BoolValue; - UPROPERTY(EditAnywhere, Category="ConfigValue") + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ConfigValue", meta = (AllowPrivateAccess = "true", EditCondition = "Type == EConfigValueType::String", EditConditionHides)) FString StringValue; public: diff --git a/Source/HarmonyLink/Public/Structs/SettingsProfile.h b/Source/HarmonyLink/Public/Structs/SettingsProfile.h index d38d146..7d12c33 100644 --- a/Source/HarmonyLink/Public/Structs/SettingsProfile.h +++ b/Source/HarmonyLink/Public/Structs/SettingsProfile.h @@ -12,10 +12,10 @@ struct FSettingsProfile { GENERATED_BODY() - UPROPERTY() + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings") FName SectionName; - UPROPERTY() + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Settings") TMap Settings; // Equality operators