Settings Data Layer
The data layer manages the storage, validation, and reactive application of settings. It consists of three core modules: Schema, Store, and Apply.
Architecture
graph LR
Schema[Schema<br/>Defaults & Migrations] --> Store[Store<br/>Path-based Storage]
Store --> Callbacks[Callbacks]
Callbacks --> Apply[Apply<br/>Reactive Effects]
Apply --> Queue[Combat Queue]
Queue --> Execute[Execute When Safe]
style Schema fill:#e1f5ff
style Store fill:#e1f5ff
style Apply fill:#e1f5ff
Schema Module
Location: SpectrumFederation/modules/Settings/Schema.lua
The Schema module defines the structure and defaults for all settings.
Default Values
SF.Settings.Schema.DEFAULTS = {
version = 3,
global = {
windowStyle = "blizzard", -- "blizzard" or "custom"
fontStyle = "default", -- Font family
fontSize = 12 -- Font size
},
lootHelper = {
enabled = true,
profiles = {}, -- ID-keyed profile storage
activeProfileId = nil -- Current active profile
}
}
Enums
SF.Settings.Schema.WINDOW_STYLES = { "blizzard", "custom" }
SF.Settings.Schema.FONT_FAMILIES = {
"Fonts\\FRIZQT__.TTF",
"Fonts\\ARIALN.TTF",
-- ... more fonts
}
Schema Versioning
The Schema uses versioning to handle data migrations:
SF.Settings.Schema.VERSION = 3
SF.Settings.Schema.MIGRATIONS = {
[2] = function(db)
-- Migration from v1 to v2
end,
[3] = function(db)
-- Migration from v2 to v3: Convert name-based profiles to ID-based
local oldProfiles = db.lootHelper.profiles or {}
local newProfiles = {}
for name, profile in pairs(oldProfiles) do
local id = GenerateProfileId()
newProfiles[id] = {
id = id,
name = name,
-- ... migrate other fields
}
end
db.lootHelper.profiles = newProfiles
end
}
Validation
Profile Name Validation
Parameters:
name(string) - The profile name to validate
Returns:
valid(boolean) - Whether the name is validerror(string|nil) - Error message if invalid
Rules:
- Must not be empty after trimming
- Maximum 24 characters
- No dots (
.) allowed - Automatically trimmed
Example:
local valid, error = SF.Settings.Schema.ValidateProfileName("My Profile")
if not valid then
SF:PrintError(error)
end
Store Module
Location: SpectrumFederation/modules/Settings/Store.lua
The Store module provides path-based access to settings with callback support.
Path-based Access
Settings are accessed using dot notation paths:
-- Get a setting
local fontSize = SF.Settings.Store:Get("global.fontSize")
-- Set a setting
SF.Settings.Store:Set("global.fontSize", 14)
-- Profile-specific setting
local profileId = SF.Settings.Store:Get("lootHelper.activeProfileId")
Core Methods
Get
Parameters:
path(string) - Dot-separated path to the settingdefault(any, optional) - Default value if path doesn't exist
Returns:
value(any) - The setting value or default
Example:
local fontSize = SF.Settings.Store:Get("global.fontSize", 12)
local profiles = SF.Settings.Store:Get("lootHelper.profiles", {})
Set
Parameters:
path(string) - Dot-separated path to the settingvalue(any) - The new value
Side Effects:
- Triggers callbacks registered for this path
- Updates SavedVariables immediately
- Logs change via Debug system
Example:
SF.Settings.Store:Set("global.windowStyle", "custom")
SF.Settings.Store:Set("lootHelper.activeProfileId", "p_abc123")
Callbacks
RegisterCallback
Parameters:
id(string) - Unique identifier for this callbackfunc(function) - Callback function(newValue, oldValue, path)
Callback Signature:
function(newValue, oldValue, path)
-- newValue: The new value that was set
-- oldValue: The previous value
-- path: The setting path that changed
end
Example:
SF.Settings.Store:RegisterCallback("fontUpdater", function(newValue, oldValue, path)
if path:match("^global%.font") then
-- Font setting changed
UpdateFonts()
end
end)
UnregisterCallback
Parameters:
id(string) - The callback identifier to remove
Profile Management
GetActiveLootHelperProfileData
Returns:
profile(table|nil) - The active profile data or nil
Example:
local profile = SF.Settings.Store:GetActiveLootHelperProfileData()
if profile then
print("Active profile:", profile.name)
end
SetActiveLootHelperProfileId
Parameters:
profileId(string) - The profile ID to activate
Side Effects:
- Updates
lootHelper.activeProfileId - Triggers callbacks
- Logs profile switch
GetActiveProfileSetting
Parameters:
key(string) - The profile setting keydefault(any, optional) - Default value if not found
Returns:
value(any) - The setting value from active profile
Example:
local safeMode = SF.Settings.Store:GetActiveProfileSetting("raidWideSafeMode", false)
local pointName = SF.Settings.Store:GetActiveProfileSetting("pointName", "DKP")
Database Initialization
Parameters:
db(table) - The SavedVariables table
Process:
- Applies schema migrations if version mismatch
- Deep merges defaults with existing data
- Sets up internal references
- Initializes callback system
Apply Module
Location: SpectrumFederation/modules/Settings/Apply.lua
The Apply module handles reactive effects when settings change, with combat awareness and debouncing.
Core Concepts
Debouncing
Operations are debounced to prevent redundant execution:
- Short delay (0.05s): Window style, font changes
- Medium delay (0.1s): Profile switches
- Long delay (0.2s): Bulk operations
Combat Awareness
Operations that modify UI are queued during combat and executed when safe:
if InCombatLockdown() then
-- Queue the operation
table.insert(pendingOperations, operation)
else
-- Execute immediately
operation()
end
Reactive Effects
The Apply module registers callbacks with the Store to react to changes:
-- Window style changes
SF.Settings.Store:RegisterCallback("Apply:WindowStyle", function(newValue, oldValue, path)
if path == "global.windowStyle" then
Apply:ApplyWindowStyle(newValue)
end
end)
Methods
RegisterCallback
Registers a callback with the Store (wrapper method).
Example:
SF.Settings.Apply:RegisterCallback("myFeature", function(newValue, oldValue, path)
if path:match("^global%.") then
-- Global setting changed
UpdateMyFeature()
end
end)
ApplyWindowStyle
Parameters:
style(string) - "blizzard" or "custom"
Effects:
- Updates window backdrop
- Refreshes UI panels
- Combat-aware and debounced
ApplyFontStyle
Parameters:
fontPath(string) - Path to font file
Effects:
- Updates all text elements
- Applies to current and future controls
- Combat-aware and debounced
ApplySafeMode
Parameters:
enabled(boolean) - Safe mode state
Effects:
- Notifies LootHelper system
- Updates sync coordinator
- Immediate execution (not combat-dependent)
Initialization
Process:
- Registers all reactive effect callbacks
- Sets up combat event listener
- Initializes debounce timers
- Applies initial settings from Store
Called automatically during addon load.
Data Flow Example
Here's how a setting change flows through the data layer:
sequenceDiagram
participant User
participant Control
participant Store
participant Callbacks
participant Apply
participant WoW
User->>Control: Click checkbox
Control->>Store: Set("global.windowStyle", "custom")
Store->>Store: Update SavedVariables
Store->>Callbacks: Trigger callbacks
Callbacks->>Apply: WindowStyle callback
Apply->>Apply: Debounce(0.05s)
Apply->>Apply: Check InCombatLockdown()
alt In Combat
Apply->>Apply: Queue operation
Note over Apply: Wait for PLAYER_REGEN_ENABLED
else Not in Combat
Apply->>WoW: Update UI
end
Best Practices
Setting Paths
DO:
- Use clear, hierarchical paths:
"global.fontSize","lootHelper.enabled" - Group related settings under common prefixes
- Use descriptive names
DON'T:
- Use flat structure:
"fontSize"(ambiguous) - Mix concerns:
"globalLootHelperEnabled"(should be"lootHelper.enabled")
Callbacks
DO:
- Use specific path matching in callbacks
- Unregister callbacks when module unloads
- Keep callback logic lightweight
DON'T:
- Register multiple callbacks for the same path
- Perform heavy operations in callbacks (use Apply for that)
- Modify settings inside callbacks (can cause loops)
Schema Evolution
DO:
- Increment version for breaking changes
- Write migrations for data structure changes
- Test migrations with old SavedVariables
DON'T:
- Remove old migration code (users may skip versions)
- Assume data structure without version check
Debugging
Enable debug logging to see data layer operations:
Log Categories:
SETTINGS:STORE- Store operationsSETTINGS:APPLY- Apply effectsSETTINGS:SCHEMA- Schema migrations
Example Output: