Skip to content

File SFSettingsSubsystem.h

File List > Public > SFSettingsSubsystem.h

Go to the documentation of this file

// Copyright 2026 Anh Pham. All Rights Reserved.

#pragma once

#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "GameplayTagContainer.h"
#include "SFSettingsSubsystem.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSFSettingChanged, const FGameplayTag&, SettingTag, class USFSettingValue*, NewValue);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSFSettingSaved, const FGameplayTag&, SettingTag, class USFSettingValue*, SavedValue);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSFSettingsInitialized);

UCLASS(BlueprintType)
class SETTINGSFRAMEWORK_API USFSettingsSubsystem : public UGameInstanceSubsystem
{
    GENERATED_BODY()

#pragma region Initialization
public: 
    UFUNCTION(BlueprintPure, Category = "SFSettingsSubsystem|Initialization")
    bool IsInitialized() const { return bIsInitialized; };

    virtual void Initialize(FSubsystemCollectionBase& Collection) override;

protected:
    void OnSettingsRegistryLoaded(TSoftObjectPtr<class USFSettingsRegistry> SettingsRegistrySoftPtr);
    void RegisterCategory(const class USFSettingCategory* Category);
    void RegisterSetting(class USFSettingDefinition* Definition);
public:
    UPROPERTY(BlueprintAssignable, Category = "SFSettingsSubsystem|Initialization")
    FOnSFSettingsInitialized OnSettingsInitialized;

protected:
    bool bIsInitialized = false;
#pragma endregion

#pragma region Setting Data
public:
    UFUNCTION(BlueprintPure, Category = "SFSettingsSubsystem|Setting Data")
    TArray<USFSettingCategory*> GetRootCategories() const;

    UFUNCTION(BlueprintPure, Category = "SFSettingsSubsystem|Setting Data")
    class USFSettingDefinition* GetSettingDefinition(const FGameplayTag& SettingTag) const;

    UFUNCTION(BlueprintPure, Category = "SFSettingsSubsystem|Setting Data")
    class USFSettingOptionSource* GetDynamicOptionSource(const FGameplayTag& SettingTag) const;

protected:
    UPROPERTY(Transient)
    TMap<FGameplayTag, TObjectPtr<class USFSettingOptionSource>> DynamicOptionSources;
#pragma endregion

#pragma region Settings State Management
public:
    UFUNCTION(BlueprintPure, Category = "SFSettingsSubsystem|State Management")
    class USFSettingValue* GetSettingValue(const FGameplayTag SettingTag) const;

    UFUNCTION(BlueprintPure, Category = "SFSettingsSubsystem|State Management")
    class USFSettingValue* GetSavedSettingValue(const FGameplayTag SettingTag) const;

    UFUNCTION(BlueprintPure, Category = "SFSettingsSubsystem|State Management")
    class USFSettingValue* GetDefaultSettingValue(const FGameplayTag SettingTag) const;

    UFUNCTION(BlueprintCallable, Category = "SFSettingsSubsystem|State Management")
    void SetSettingValue(const FGameplayTag SettingTag, const class USFSettingValue* NewValue);

    UFUNCTION(BlueprintPure, Category = "SFSettingsSubsystem|State Management")
    bool IsSettingDirty(const FGameplayTag SettingTag) const;

    UFUNCTION(BlueprintPure, Category = "SFSettingsSubsystem|State Management")
    bool AreAnySettingsDirty() const;

    UFUNCTION(BlueprintCallable, Category = "SFSettingsSubsystem|State Management")
    void RevertSetting(const FGameplayTag SettingTag);

    UFUNCTION(BlueprintCallable, Category = "SFSettingsSubsystem|State Management")
    void ResetSettingToDefault(const FGameplayTag SettingTag);

    UFUNCTION(BlueprintCallable, Category = "SFSettingsSubsystem|State Management")
    void SaveSettings();

    UFUNCTION(BlueprintCallable, Category = "SFSettingsSubsystem|State Management")
    void RevertSettings();

    UFUNCTION(BlueprintCallable, Category = "SFSettingsSubsystem|State Management")
    void ResetSettingsToDefault();

public:
    UPROPERTY(BlueprintAssignable, Category = "SFSettingsSubsystem|State Management")
    FOnSFSettingChanged OnSettingValueChanged;

    UPROPERTY(BlueprintAssignable, Category = "SFSettingsSubsystem|State Management")
    FOnSFSettingSaved OnSettingValueSaved;

protected:
    UPROPERTY(Transient)
    TObjectPtr<class USFSettingsRegistry> SettingsRegistry;

    UPROPERTY(Transient)
    TMap<FGameplayTag, TObjectPtr<class USFSettingDefinition>> RegisteredSettings;

    UPROPERTY(Transient)
    TMap<FGameplayTag, TObjectPtr<class USFSettingValue>> CurrentValues;

    UPROPERTY(Transient)
    TMap<FGameplayTag, TObjectPtr<class USFSettingValue>> SavedValues;
#pragma endregion

#pragma region Condition Checks
public:
    UFUNCTION(BlueprintPure, Category = "SFSettingsSubsystem|Conditions")
    bool IsSettingVisible(const FGameplayTag& SettingTag) const;

    UFUNCTION(BlueprintPure, Category = "SFSettingsSubsystem|Conditions")
    bool IsSettingEditable(const FGameplayTag& SettingTag) const;

protected:
    void UpdateSettingDependencies();

    // Recursion guard
    int32 MaxUpdateDepth = 10;

    int32 UpdateDepth = 0;
#pragma endregion

#pragma region Serialization
protected:
    void LoadSettingsFromSaveGame();

    void SaveSettingsToSaveGame();

protected:
    FString SaveGameSlotName = TEXT("SF_SaveSlot");
#pragma endregion

#pragma region Keybinding
public:
    UFUNCTION(BlueprintPure, Category = "SFSettingsSubsystem|Keybinding")
    FGameplayTag GetKeybindingCollision(const struct FKey Key, const FGameplayTagContainer CollisionChannels, const FGameplayTag SettingTagToIgnore) const;

    UFUNCTION(BlueprintCallable, Category = "SFSettingsSubsystem|Keybinding")
    bool UpdateKeybinding(const FGameplayTag SettingTag, struct FSFKeybindValueData& NewValue, const enum ESFKeybindCollisionResolution ResolutionPolicy);

protected:
    bool ResolveKeybindingCollision(const FGameplayTag& SettingBeingUpdated, const struct FKey& NewKey, const struct FKey& OldKey, const FGameplayTagContainer& CollisionChannels, const enum ESFKeybindCollisionResolution& ResolutionPolicy);
#pragma endregion
};