From af7208dfa060f9b0bade9bfc5aadeb35147848ee Mon Sep 17 00:00:00 2001 From: Raenore <172234435+Raenore@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:46:59 +0100 Subject: [PATCH 1/2] Fix database saving issues, clean-up a lot of DB-related stuff --- Core/Database.lua | 249 ++++++++++++++++++------------------ Core/Utils.lua | 2 +- Modules/Minimap/Minimap.lua | 9 +- UI/Config.lua | 97 +++++++------- 4 files changed, 181 insertions(+), 176 deletions(-) diff --git a/Core/Database.lua b/Core/Database.lua index 4a6fb13..aea33f3 100644 --- a/Core/Database.lua +++ b/Core/Database.lua @@ -304,120 +304,108 @@ table.sort(categories, function(a, b) return SIPPYCUP_TEXT.Normalize(locA:lower()) < SIPPYCUP_TEXT.Normalize(locB:lower()); end); ----UpdateSetting updates a value in the saved minimal table and runtime resolved table. ----If the value matches default, it removes the saved override to keep minimal data clean. ----@param scope string Either "profile" or "global" ----@param key string The setting key to update, e.g. "noggenfoggerSelectDown" or "FlashTaskbar" ----@param subKey string? Optional subkey inside that key, e.g. "enable" ----@param value any The new value to set ----@return boolean success True if update was successful, false if scope invalid or profile missing. -function SIPPYCUP.Database.UpdateSetting(scope, key, subKey, value) - local db = SIPPYCUP.db; - local defaultTable, targetMinimal, targetRuntime; - - if scope == "profile" then - local profileName = SIPPYCUP.Database.GetCurrentProfileName(); - if not profileName then return false; end - - db.profiles = db.profiles or {}; - db.profiles[profileName] = db.profiles[profileName] or {}; - targetMinimal = db.profiles[profileName]; - - SIPPYCUP.Profile = SIPPYCUP.Profile or {}; - targetRuntime = SIPPYCUP.Profile; - - defaultTable = defaults.profiles.Default; +---Gets a value from the current profile, falling back to defaults. +---@param key string +---@return any +function SIPPYCUP.Database.GetSetting(key) + local profile = SIPPYCUP.Profile; + local defaultsProfile = SIPPYCUP.Database.defaults.profiles.Default; + + if not defaultsProfile then return nil; end + if not profile then return defaultsProfile[key]; end + + local value = profile[key]; + if value == nil then + local def = defaultsProfile[key]; + if type(def) == "table" then + value = {}; + for k, v in pairs(def) do + value[k] = v; + end + profile[key] = value; + else + value = def; + end + end - elseif scope == "global" then - db.global = db.global or {}; - targetMinimal = db.global; + return value; +end - SIPPYCUP.global = SIPPYCUP.global or {}; - targetRuntime = SIPPYCUP.global; +---Sets a value in the current profile. +---@param key string +---@param value any +function SIPPYCUP.Database.SetSetting(key, value) + local profile = SIPPYCUP.Profile; + if not profile then return; end - defaultTable = defaults.global; + local defaultsProfile = SIPPYCUP.Database.defaults.profiles.Default; + local def = defaultsProfile and defaultsProfile[key]; + if type(value) == "table" then + profile[key] = profile[key] or {}; + for k, v in pairs(value) do + profile[key][k] = v; + end + elseif value == def then + profile[key] = nil; else - return false; + profile[key] = value; end - local defaultValue = defaultTable and defaultTable[key]; - if subKey then - local defaultSubValue = (type(defaultValue) == "table") and defaultValue[subKey]; + -- Persist to saved variables + local profileName = SIPPYCUP.Database.GetCurrentProfileName(); + if profileName then + local defaultProfile = SIPPYCUP.Database.defaults.profiles.Default or {}; + local minimal = GetMinimalTable(profile, defaultProfile); + SIPPYCUP.db.profiles[profileName] = minimal; + end +end - -- Save minimal - if defaultSubValue == value then - if targetMinimal[key] then - targetMinimal[key][subKey] = nil; - if next(targetMinimal[key]) == nil then - targetMinimal[key] = nil; - end +---Gets a value from the global database, falling back to defaults. +---@param key string +---@return any +function SIPPYCUP.Database.GetGlobalSetting(key) + local global = SIPPYCUP.global; + + local value = global[key]; + if value == nil then + local def = SIPPYCUP.Database.defaults.global[key]; + if type(def) == "table" then + value = {}; + for k, v in pairs(def) do + value[k] = v; end + global[key] = value; else - targetMinimal[key] = targetMinimal[key] or {}; - targetMinimal[key][subKey] = value; + value = def; + global[key] = value; end - - -- Update runtime - targetRuntime[key] = targetRuntime[key] or {}; - targetRuntime[key][subKey] = value; - - else - if defaultValue == value then - targetMinimal[key] = nil; - else - targetMinimal[key] = value; - end - - targetRuntime[key] = value; end - return true; + return value; end ----GetSetting retrieves a value from the runtime profile or global table. ----Falls back to default value if no override is present. ----@param scope "profile" | "global" Scope to fetch the setting from ----@param key string The setting key, e.g. "noggenfoggerSelectDown" or "FlashTaskbar" ----@param subKey string? Optional subkey inside the key ----@return any value The resolved value from runtime or default, or nil if not found -function SIPPYCUP.Database.GetSetting(scope, key, subKey) - if scope == "profile" then - local profile = SIPPYCUP.Profile or {}; - if subKey then - if profile[key] and profile[key][subKey] ~= nil then - return profile[key][subKey]; - end - if defaults.profiles.Default and defaults.profiles.Default[key] then - return defaults.profiles.Default[key][subKey]; - end - return nil; - else - if profile[key] ~= nil then - return profile[key]; - end - return defaults.profiles.Default and defaults.profiles.Default[key]; - end - - elseif scope == "global" then - local global = SIPPYCUP.global or {}; - if subKey then - if global[key] and global[key][subKey] ~= nil then - return global[key][subKey]; - end - if defaults.global and defaults.global[key] then - return defaults.global[key][subKey]; - end - return nil; - else - if global[key] ~= nil then - return global[key]; - end - return defaults.global and defaults.global[key]; +---Sets a value in the global database. +---@param key string +---@param value any +function SIPPYCUP.Database.SetGlobalSetting(key, value) + local global = SIPPYCUP.global; + -- local def = SIPPYCUP.Database.defaults.global[key]; + + if type(value) == "table" then + global[key] = global[key] or {}; + for k, v in pairs(value) do + global[key][k] = v; end + else + global[key] = value; end - return nil; + -- Persist to saved variables + if SIPPYCUP.db then + SIPPYCUP.db.global = SIPPYCUP.db.global or {}; + SIPPYCUP.db.global[key] = value; + end end ---RefreshUI refreshes the configuration menu by updating widgets and syncing profile values. @@ -464,22 +452,15 @@ function SIPPYCUP.Database.Setup() -- Fill in missing global keys from defaults (preserves saved overrides) DeepCopyDefaults(defaults.global, db.global); - -- Create a resolved working copy of global settings for runtime use - local workingGlobal = {}; - DeepCopyDefaults(defaults.global, workingGlobal); - DeepMerge(db.global, workingGlobal); -- saved overrides overwrite defaults - - -- Save runtime global for quick access - SIPPYCUP.global = workingGlobal; + -- Use the saved table directly for runtime global + SIPPYCUP.global = db.global; -- Get current character identifier local charKey = SIPPYCUP.Database.GetUnitName() or "Unknown"; - - -- Assign profile key for this character local profileName = db.profileKeys[charKey] or "Default"; db.profileKeys[charKey] = profileName; - -- Ensure the named profile exists in saved variables + -- Ensure the named profile exists db.profiles[profileName] = db.profiles[profileName] or {}; -- Start from a clean copy of the default profile @@ -487,14 +468,13 @@ function SIPPYCUP.Database.Setup() local workingProfile = {}; DeepCopyDefaults(defaultProfile, workingProfile); - -- Merge user's minimal saved data into working profile + -- Merge minimal saved data into working profile DeepMerge(db.profiles[profileName], workingProfile); - -- Compute minimal diffs and save back into saved variables - local minimalProfile = GetMinimalTable(workingProfile, defaultProfile); - db.profiles[profileName] = minimalProfile; + -- Save minimal differences back to saved variables + db.profiles[profileName] = GetMinimalTable(workingProfile, defaultProfile); - -- Set resolved working profile for runtime use + -- Set runtime profile SIPPYCUP.Profile = workingProfile; SIPPYCUP.States.databaseLoaded = true; @@ -559,24 +539,36 @@ function SIPPYCUP.Database.SetProfile(profileName) return false, "Database not initialized or invalid profile name"; end - -- Create profile if it doesn't exist - if not SIPPYCUP.db.profiles[profileName] then - SIPPYCUP.db.profiles[profileName] = {}; + local db = SIPPYCUP.db; + local defaultProfile = defaults.profiles.Default or {}; + + -- Persist current runtime profile before switching + local currentProfileName = SIPPYCUP.Database.GetCurrentProfileName(); + if currentProfileName and SIPPYCUP.Profile then + local minimal = GetMinimalTable(SIPPYCUP.Profile, defaultProfile); + db.profiles[currentProfileName] = minimal; end - local defaultProfile = defaults.profiles.Default or {}; - local minimalProfile = SIPPYCUP.db.profiles[profileName]; + -- Create target profile if it does not exist + if not db.profiles[profileName] then + db.profiles[profileName] = {}; + end - -- Resolve full profile with defaults merged + -- Resolve full profile (defaults + minimal overrides) + local minimalProfile = db.profiles[profileName]; local resolvedProfile = {}; DeepCopyDefaults(defaultProfile, resolvedProfile); DeepMerge(minimalProfile, resolvedProfile); -- Update keys and runtime profile - local key = SIPPYCUP.Database.GetUnitName(); - SIPPYCUP.db.profileKeys[key] = profileName; + local charKey = SIPPYCUP.Database.GetUnitName(); + db.profileKeys = db.profileKeys or {}; + db.profileKeys[charKey] = profileName; + -- Set runtime profile SIPPYCUP.Profile = resolvedProfile; + + -- Refresh UI SIPPYCUP.Database.RefreshUI(); return true; @@ -596,16 +588,25 @@ function SIPPYCUP.Database.CreateProfile(profileName) return false, "Profile already exists"; end + local db = SIPPYCUP.db; + local defaultProfile = defaults.profiles.Default or {}; + + -- Persist current runtime profile before switching + local currentProfileName = SIPPYCUP.Database.GetCurrentProfileName(); + if currentProfileName and SIPPYCUP.Profile then + local minimal = GetMinimalTable(SIPPYCUP.Profile, defaultProfile); + db.profiles[currentProfileName] = minimal; + end + -- Create an empty minimal profile (no overrides) - SIPPYCUP.db.profiles[profileName] = {}; + db.profiles[profileName] = {}; -- Assign new profile to current character local key = SIPPYCUP.Database.GetUnitName(); - SIPPYCUP.db.profileKeys = SIPPYCUP.db.profileKeys or {}; - SIPPYCUP.db.profileKeys[key] = profileName; + db.profileKeys = db.profileKeys or {}; + db.profileKeys[key] = profileName; - -- Set runtime profile as default profile since minimal is empty - local defaultProfile = defaults.profiles.Default or {}; + -- Set runtime profile to defaults (since minimal is empty) SIPPYCUP.Profile = {}; DeepCopyDefaults(defaultProfile, SIPPYCUP.Profile); diff --git a/Core/Utils.lua b/Core/Utils.lua index 788950e..89880d2 100644 --- a/Core/Utils.lua +++ b/Core/Utils.lua @@ -154,7 +154,7 @@ end ---Accepts any number of arguments and joins them with space. ---@param ... any Values to print (strings, numbers, tables, etc.) function SIPPYCUP_OUTPUT.Debug(...) - if not SIPPYCUP.IS_DEV_BUILD or not SIPPYCUP.Database.GetSetting("global", "DebugOutput", nil) then return; end + if not SIPPYCUP.IS_DEV_BUILD or not SIPPYCUP.Database.GetGlobalSetting("DebugOutput") then return; end local args = {...}; local outputLines = {}; diff --git a/Modules/Minimap/Minimap.lua b/Modules/Minimap/Minimap.lua index a3ab2e0..06c7859 100644 --- a/Modules/Minimap/Minimap.lua +++ b/Modules/Minimap/Minimap.lua @@ -32,7 +32,8 @@ function SIPPYCUP.Minimap:SetupMinimapButtons() OnTooltipShow = OnTooltipShow, }); - LibDBIcon:Register(SIPPYCUP.AddonMetadata.title, ldb, SIPPYCUP.global.MinimapButton); + local minimapSettings = SIPPYCUP.Database.GetGlobalSetting("MinimapButton"); + LibDBIcon:Register(SIPPYCUP.AddonMetadata.title, ldb, minimapSettings); LibDBCompartment:Register(SIPPYCUP.AddonMetadata.title, ldb); self:UpdateMinimapButtons(); @@ -41,9 +42,9 @@ end ---UpdateMinimapButtons toggles visibility of minimap-related buttons based on addon settings. ---@return nil function SIPPYCUP.Minimap:UpdateMinimapButtons() - local btn = SIPPYCUP.global.MinimapButton; - if btn and not btn.Hide then - LibDBCompartment:SetShown(SIPPYCUP.AddonMetadata.title, SIPPYCUP.global.MinimapButton.ShowAddonCompartmentButton); + local minimapSettings = SIPPYCUP.Database.GetGlobalSetting("MinimapButton"); + if minimapSettings and not minimapSettings.Hide then + LibDBCompartment:SetShown(SIPPYCUP.AddonMetadata.title, minimapSettings.ShowAddonCompartmentButton); LibDBIcon:Refresh(SIPPYCUP.AddonMetadata.title); else LibDBCompartment:Hide(SIPPYCUP.AddonMetadata.title); diff --git a/UI/Config.lua b/UI/Config.lua index eb9d7cb..804241e 100644 --- a/UI/Config.lua +++ b/UI/Config.lua @@ -526,12 +526,12 @@ local function CreateInset(parent, insetData) printCheckbox:SetSize(22, 22); ElvUI.RegisterSkinnableElement(printCheckbox, "checkbox"); - printCheckbox:SetChecked(SIPPYCUP.Database.GetSetting("global", "DebugOutput", nil)); + printCheckbox:SetChecked(SIPPYCUP.Database.GetGlobalSetting("DebugOutput")); AttachTooltip(printCheckbox, "Enable Debug Output", "Click this to enable the debug prints.|n|nIf you see this without knowing what debug is, you might've done something wrong!"); printCheckbox:SetScript("OnClick", function(self) - SIPPYCUP.Database.UpdateSetting("global", "DebugOutput", nil, self:GetChecked()) + SIPPYCUP.Database.SetGlobalSetting("DebugOutput", self:GetChecked()); end); end end @@ -1293,11 +1293,10 @@ function SIPPYCUP_ConfigMixin:OnLoad() label = L.OPTIONS_GENERAL_WELCOME_NAME, tooltip = L.OPTIONS_GENERAL_WELCOME_DESC, get = function() - -- return SIPPYCUP.db.global.WelcomeMessage; - return SIPPYCUP.Database.GetSetting("global", "WelcomeMessage", nil); + return SIPPYCUP.Database.GetGlobalSetting("WelcomeMessage"); end, set = function(val) - SIPPYCUP.Database.UpdateSetting("global", "WelcomeMessage", nil, val); + SIPPYCUP.Database.SetGlobalSetting("WelcomeMessage", val); end, }, { @@ -1305,11 +1304,13 @@ function SIPPYCUP_ConfigMixin:OnLoad() label = L.OPTIONS_GENERAL_MINIMAPBUTTON_NAME, tooltip = L.OPTIONS_GENERAL_MINIMAPBUTTON_DESC, get = function() - return not SIPPYCUP.Database.GetSetting("global", "MinimapButton", "Hide"); + return not SIPPYCUP.Database.GetGlobalSetting("MinimapButton").Hide; end, set = function(val) -- Update with inversion: save 'Hide' as NOT val - SIPPYCUP.Database.UpdateSetting("global", "MinimapButton", "Hide", not val); + local minimapSettings = SIPPYCUP.Database.GetGlobalSetting("MinimapButton"); + minimapSettings.Hide = not val; + SIPPYCUP.Database.SetGlobalSetting("MinimapButton", minimapSettings); SIPPYCUP.Minimap:UpdateMinimapButtons(); end, }, @@ -1318,10 +1319,12 @@ function SIPPYCUP_ConfigMixin:OnLoad() label = L.OPTIONS_GENERAL_ADDONCOMPARTMENT_NAME, tooltip = L.OPTIONS_GENERAL_ADDONCOMPARTMENT_DESC, get = function() - return SIPPYCUP.Database.GetSetting("global", "MinimapButton", "ShowAddonCompartmentButton") or false; + return SIPPYCUP.Database.GetGlobalSetting("MinimapButton").ShowAddonCompartmentButton; end, set = function(val) - SIPPYCUP.Database.UpdateSetting("global", "MinimapButton", "ShowAddonCompartmentButton", val); + local minimapSettings = SIPPYCUP.Database.GetGlobalSetting("MinimapButton"); + minimapSettings.ShowAddonCompartmentButton = val; + SIPPYCUP.Database.SetGlobalSetting("MinimapButton", minimapSettings); SIPPYCUP.Minimap:UpdateMinimapButtons(); end, }, @@ -1331,12 +1334,12 @@ function SIPPYCUP_ConfigMixin:OnLoad() tooltip = L.OPTIONS_GENERAL_NEW_FEATURE_NOTIFICATION_DESC, notificationToggle = true, get = function() - return SIPPYCUP.Database.GetSetting("global", "NewFeatureNotification", nil); + return SIPPYCUP.Database.GetGlobalSetting("NewFeatureNotification"); end, set = function(val) SIPPYCUP.Popups.CreateReloadPopup( function() -- ACCEPT - SIPPYCUP.Database.UpdateSetting("global", "NewFeatureNotification", nil, val); + SIPPYCUP.Database.SetGlobalSetting("NewFeatureNotification", val); ReloadUI(); end ); @@ -1358,10 +1361,10 @@ function SIPPYCUP_ConfigMixin:OnLoad() label = L.OPTIONS_GENERAL_POPUPS_PRE_EXPIRATION_CHECKS_ENABLE, tooltip = L.OPTIONS_GENERAL_POPUPS_PRE_EXPIRATION_CHECKS_ENABLE_DESC, get = function() - return SIPPYCUP.Database.GetSetting("global", "PreExpirationChecks", nil); + return SIPPYCUP.Database.GetGlobalSetting("PreExpirationChecks"); end, set = function(val) - SIPPYCUP.Database.UpdateSetting("global", "PreExpirationChecks", nil, val); + SIPPYCUP.Database.SetGlobalSetting("PreExpirationChecks", val); if val then SIPPYCUP.Options.RefreshStackSizes(SIPPYCUP.MSP.IsEnabled() and SIPPYCUP.global.MSPStatusCheck, false, true); else @@ -1381,13 +1384,13 @@ function SIPPYCUP_ConfigMixin:OnLoad() max = 5, step = 1, disabled = function() - return not SIPPYCUP.Database.GetSetting("global", "PreExpirationChecks", nil); + return not SIPPYCUP.Database.GetGlobalSetting("PreExpirationChecks"); end, get = function() - return SIPPYCUP.Database.GetSetting("global", "PreExpirationLeadTimer", nil); + return SIPPYCUP.Database.GetGlobalSetting("PreExpirationLeadTimer"); end, set = function(val) - SIPPYCUP.Database.UpdateSetting("global", "PreExpirationLeadTimer", nil, val); + SIPPYCUP.Database.SetGlobalSetting("PreExpirationLeadTimer", val); local reason = SIPPYCUP.Popups.Reason.PRE_EXPIRATION; SIPPYCUP.Auras.CancelAllPreExpirationTimers(); SIPPYCUP.Items.CancelAllItemTimers(reason); @@ -1400,10 +1403,10 @@ function SIPPYCUP_ConfigMixin:OnLoad() label = L.OPTIONS_GENERAL_POPUPS_INSUFFICIENT_REMINDER_ENABLE, tooltip = L.OPTIONS_GENERAL_POPUPS_INSUFFICIENT_REMINDER_ENABLE_DESC, get = function() - return SIPPYCUP.Database.GetSetting("global", "InsufficientReminder", nil); + return SIPPYCUP.Database.GetGlobalSetting("InsufficientReminder"); end, set = function(val) - SIPPYCUP.Database.UpdateSetting("global", "InsufficientReminder", nil, val); + SIPPYCUP.Database.SetGlobalSetting("InsufficientReminder", val); end, }, { @@ -1412,10 +1415,10 @@ function SIPPYCUP_ConfigMixin:OnLoad() tooltip = L.OPTIONS_GENERAL_POPUPS_TRACK_TOY_ITEM_CD_ENABLE_DESC, buildAdded = "0.6.0|120000", get = function() - return SIPPYCUP.Database.GetSetting("global", "UseToyCooldown", nil); + return SIPPYCUP.Database.GetGlobalSetting("UseToyCooldown"); end, set = function(val) - SIPPYCUP.Database.UpdateSetting("global", "UseToyCooldown", nil, val); + SIPPYCUP.Database.SetGlobalSetting("UseToyCooldown", val); end, }, { @@ -1449,10 +1452,10 @@ function SIPPYCUP_ConfigMixin:OnLoad() "BOTTOM", }, get = function() - return SIPPYCUP.Database.GetSetting("global", "PopupPosition", nil); + return SIPPYCUP.Database.GetGlobalSetting("PopupPosition"); end, set = function(val) - SIPPYCUP.Database.UpdateSetting("global", "PopupPosition", nil, val); + SIPPYCUP.Database.SetGlobalSetting("PopupPosition", val); end, }, }; @@ -1465,10 +1468,10 @@ function SIPPYCUP_ConfigMixin:OnLoad() label = BINDING_NAME_TOGGLESOUND, tooltip = L.OPTIONS_GENERAL_POPUPS_SOUND_ENABLE_DESC, get = function() - return SIPPYCUP.Database.GetSetting("global", "AlertSound", nil); + return SIPPYCUP.Database.GetGlobalSetting("AlertSound"); end, set = function(val) - SIPPYCUP.Database.UpdateSetting("global", "AlertSound", nil, val); + SIPPYCUP.Database.SetGlobalSetting("AlertSound", val); end, }, { @@ -1478,16 +1481,16 @@ function SIPPYCUP_ConfigMixin:OnLoad() align = "right", values = soundList, disabled = function() - return not SIPPYCUP.Database.GetSetting("global", "AlertSound", nil); + return not SIPPYCUP.Database.GetGlobalSetting("AlertSound"); end, get = function() - return SIPPYCUP.Database.GetSetting("global", "AlertSoundID", nil); + return SIPPYCUP.Database.GetGlobalSetting("AlertSoundID"); end, set = function(val) local soundPath = SharedMedia:Fetch("sound", val); if soundPath then PlaySoundFile(soundPath, "Master"); - SIPPYCUP.Database.UpdateSetting("global", "AlertSoundID", nil, val); + SIPPYCUP.Database.SetGlobalSetting("AlertSoundID", val); end end, }, @@ -1496,10 +1499,10 @@ function SIPPYCUP_ConfigMixin:OnLoad() label = L.OPTIONS_GENERAL_POPUPS_FLASHTASKBAR_ENABLE, tooltip = L.OPTIONS_GENERAL_POPUPS_FLASHTASKBAR_ENABLE_DESC, get = function() - return SIPPYCUP.Database.GetSetting("global", "FlashTaskbar", nil); + return SIPPYCUP.Database.GetGlobalSetting("FlashTaskbar"); end, set = function(val) - SIPPYCUP.Database.UpdateSetting("global", "FlashTaskbar", nil, val); + SIPPYCUP.Database.SetGlobalSetting("FlashTaskbar", val); end, }, }; @@ -1517,10 +1520,10 @@ function SIPPYCUP_ConfigMixin:OnLoad() return not SIPPYCUP.MSP.IsEnabled(); end, get = function() - return SIPPYCUP.Database.GetSetting("global", "MSPStatusCheck", nil); + return SIPPYCUP.Database.GetGlobalSetting("MSPStatusCheck"); end, set = function(val) - SIPPYCUP.Database.UpdateSetting("global", "MSPStatusCheck", nil, val); + SIPPYCUP.Database.SetGlobalSetting("MSPStatusCheck", val); SIPPYCUP.MSP.CheckRPStatus(); if val then SIPPYCUP.Options.RefreshStackSizes(val); @@ -1603,10 +1606,10 @@ function SIPPYCUP_ConfigMixin:OnLoad() stacks = optionData.stacks, buildAdded = optionData.buildAdded, get = function() - return SIPPYCUP.Database.GetSetting("profile", checkboxProfileKey, "enable"); + return SIPPYCUP.Database.GetSetting(checkboxProfileKey).enable; end, set = function(val) - SIPPYCUP.Database.UpdateSetting("profile", checkboxProfileKey, "enable", val); + SIPPYCUP.Database.SetSetting(checkboxProfileKey, { enable = val }); SIPPYCUP.Popups.Toggle(consumableName, consumableAura, val); end, }; @@ -1624,13 +1627,13 @@ function SIPPYCUP_ConfigMixin:OnLoad() max = optionData.maxStacks, step = 1, disabled = function() - return not SIPPYCUP.Database.GetSetting("profile", sliderProfileKey, "enable"); + return not SIPPYCUP.Database.GetSetting(sliderProfileKey).enable; end, get = function() - return SIPPYCUP.Database.GetSetting("profile", sliderProfileKey, "desiredStacks"); + return SIPPYCUP.Database.GetSetting(sliderProfileKey).desiredStacks; end, set = function(val) - SIPPYCUP.Database.UpdateSetting("profile", sliderProfileKey, "desiredStacks", val); + SIPPYCUP.Database.SetSetting(sliderProfileKey, { desiredStacks = val }); if SIPPYCUP.Profile[sliderProfileKey].enable then SIPPYCUP.Popups.Toggle(consumableName, consumableAura, true); end @@ -1654,13 +1657,13 @@ function SIPPYCUP_ConfigMixin:OnLoad() step = 1, height = 35, disabled = function() - return not SIPPYCUP.Database.GetSetting("global", "ProjectionPrismPreExpirationLeadTimer", nil); + return not SIPPYCUP.Database.GetGlobalSetting("ProjectionPrismPreExpirationLeadTimer"); end, get = function() - return SIPPYCUP.Database.GetSetting("global", "ProjectionPrismPreExpirationLeadTimer", nil); + return SIPPYCUP.Database.GetGlobalSetting("ProjectionPrismPreExpirationLeadTimer"); end, set = function(val) - SIPPYCUP.Database.UpdateSetting("global", "ProjectionPrismPreExpirationLeadTimer", nil, val); + SIPPYCUP.Database.SetGlobalSetting("ProjectionPrismPreExpirationLeadTimer", val); local reason = SIPPYCUP.Popups.Reason.PRE_EXPIRATION; SIPPYCUP.Auras.CancelAllPreExpirationTimers(); SIPPYCUP.Items.CancelAllItemTimers(reason); @@ -1678,13 +1681,13 @@ function SIPPYCUP_ConfigMixin:OnLoad() step = 1, height = 35, disabled = function() - return not SIPPYCUP.Database.GetSetting("global", "ReflectingPrismPreExpirationLeadTimer", nil); + return not SIPPYCUP.Database.GetGlobalSetting("ReflectingPrismPreExpirationLeadTimer"); end, get = function() - return SIPPYCUP.Database.GetSetting("global", "ReflectingPrismPreExpirationLeadTimer", nil); + return SIPPYCUP.Database.GetGlobalSetting("ReflectingPrismPreExpirationLeadTimer"); end, set = function(val) - SIPPYCUP.Database.UpdateSetting("global", "ReflectingPrismPreExpirationLeadTimer", nil, val); + SIPPYCUP.Database.SetGlobalSetting("ReflectingPrismPreExpirationLeadTimer", val); local reason = SIPPYCUP.Popups.Reason.PRE_EXPIRATION; SIPPYCUP.Auras.CancelAllPreExpirationTimers(); SIPPYCUP.Items.CancelAllItemTimers(reason); @@ -1738,10 +1741,10 @@ function SIPPYCUP_ConfigMixin:OnLoad() return IsToyDisabled(toyID); end, get = function() - return SIPPYCUP.Database.GetSetting("profile", checkboxProfileKey, "enable"); + return SIPPYCUP.Database.GetSetting(checkboxProfileKey).enable; end, set = function(val) - SIPPYCUP.Database.UpdateSetting("profile", checkboxProfileKey, "enable", val); + SIPPYCUP.Database.SetSetting(checkboxProfileKey, { enable = val }); SIPPYCUP.Popups.Toggle(toyName, toyAura, val); end, }; @@ -1758,13 +1761,13 @@ function SIPPYCUP_ConfigMixin:OnLoad() max = optionData.maxStacks, step = 1, disabled = function() - return not SIPPYCUP.Database.GetSetting("profile", sliderProfileKey, "enable"); + return not SIPPYCUP.Database.GetSetting(sliderProfileKey).enable; end, get = function() - return SIPPYCUP.Database.GetSetting("profile", sliderProfileKey, "desiredStacks"); + return SIPPYCUP.Database.GetSetting(sliderProfileKey).desiredStacks; end, set = function(val) - SIPPYCUP.Database.UpdateSetting("profile", sliderProfileKey, "desiredStacks", val); + SIPPYCUP.Database.SetSetting(sliderProfileKey, { desiredStacks = val }); if SIPPYCUP.Profile[sliderProfileKey].enable then SIPPYCUP.Popups.Toggle(toyName, toyAura, true); end From 9d7bf24a9d8efaeef394673f1de2cc36f8ee357c Mon Sep 17 00:00:00 2001 From: Raenore <172234435+Raenore@users.noreply.github.com> Date: Mon, 23 Feb 2026 18:30:21 +0100 Subject: [PATCH 2/2] Small DB optimizations --- Core/Database.lua | 55 ++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/Core/Database.lua b/Core/Database.lua index aea33f3..79fdbae 100644 --- a/Core/Database.lua +++ b/Core/Database.lua @@ -12,10 +12,14 @@ SIPPYCUP.Profile = {}; ---@return nil local function DeepCopyDefaults(source, target) for k, v in pairs(source) do + local tgt = target[k]; if type(v) == "table" then - target[k] = target[k] or {}; - DeepCopyDefaults(v, target[k]); - elseif target[k] == nil then + if not tgt then + tgt = {}; + target[k] = tgt; + end + DeepCopyDefaults(v, tgt); + elseif tgt == nil then target[k] = v; end end @@ -27,9 +31,13 @@ end ---@return nil local function DeepMerge(source, target) for k, v in pairs(source) do + local tgt = target[k]; if type(v) == "table" then - target[k] = target[k] or {}; - DeepMerge(v, target[k]); + if not tgt then + tgt = {}; + target[k] = tgt; + end + DeepMerge(v, tgt); else target[k] = v; end @@ -41,22 +49,22 @@ end ---@param default table? Optional default table to compare against. ---@return table minimal The minimal table with only differing values. local function GetMinimalTable(current, default) - local minimal = {}; + local minimal; for k, v in pairs(current) do local defVal = default and default[k]; if type(v) == "table" and type(defVal) == "table" then local nested = GetMinimalTable(v, defVal); -- Only include nested tables if they have keys (non-empty) if next(nested) then + minimal = minimal or {}; minimal[k] = nested; end - else - if v ~= defVal then - minimal[k] = v; - end + elseif v ~= defVal then + minimal = minimal or {}; + minimal[k] = v; end end - return minimal; + return minimal or {}; end ---Default saved variable structure for the SippyCup addon. @@ -145,6 +153,7 @@ local defaults = SIPPYCUP.Database.defaults; ---@return nil local function PopulateDefaultOptions() local optionsData = SIPPYCUP.Options.Data; + local defaultsProfile = defaults.profiles.Default; for i = 1, #optionsData do local option = optionsData[i]; local spellID = option.auraID; @@ -157,7 +166,7 @@ local function PopulateDefaultOptions() if spellID then -- Use auraID as the key, not profileKey - defaults.profiles.Default[spellID] = { + defaultsProfile[spellID] = { enable = false, desiredStacks = 1, currentInstanceID = nil, @@ -318,11 +327,11 @@ function SIPPYCUP.Database.GetSetting(key) if value == nil then local def = defaultsProfile[key]; if type(def) == "table" then - value = {}; + profile[key] = {}; for k, v in pairs(def) do - value[k] = v; + profile[key][k] = v; end - profile[key] = value; + value = profile[key]; else value = def; end @@ -366,23 +375,21 @@ end ---@return any function SIPPYCUP.Database.GetGlobalSetting(key) local global = SIPPYCUP.global; + local def = SIPPYCUP.Database.defaults.global[key]; - local value = global[key]; - if value == nil then - local def = SIPPYCUP.Database.defaults.global[key]; + if not global[key] then if type(def) == "table" then - value = {}; + global[key] = global[key] or {}; + local t = global[key]; for k, v in pairs(def) do - value[k] = v; + t[k] = t[k] or v; end - global[key] = value; else - value = def; - global[key] = value; + global[key] = def; end end - return value; + return global[key]; end ---Sets a value in the global database.