Thread Tools Display Modes
06-15-21, 05:33 PM   #1
Srzm
A Murloc Raider
Join Date: Jun 2021
Posts: 4
Saved Variables DB with a subtable: weird behaviour

Hey guys,

while developing my first greater AddOn I had several issues that I could fix myself. However, after research and testing I just can't get to the bottom of the following problem regarding some weird behaviour of a database subtable from SavedVariables. Hope you can help. And sorry for the incoming wall of text.

tl;dr: the full code is posted below.

I'm building a Config Dialog for my addon with AceConfigDialog and AceDBOptions. So far, everything works. My SavedVariables Database is listed in my .toc file . Lets say it's called addondb.

One part of addondb is a table with several subtables, containing stats a specific class + role should not have. It looks like this:

Code:
addondb = {
   failstats = {
      ["WARRIOR"] = {
         ["Arms"] = {"INT, "SPI", "SPW", "MP5","DEF","DDG","PAR","RES"}
      }
   }
}
And so on for every specc and class. You get the idea.
Those "failstats" are predefined (my research, several guides etc.). I want the player to have the option to change them the way he wants with a multiline input textbox. Here is the idea:



The multiline-input field get function. My code
- gets the failstats from the addondb["WARRIOR"]["Arms"] table,
- translates them into understandable stats ("INT" becomes "Intellect", as you see) and
- creates this multiline output by concatenating the stats with a newline "\n" in between.

Now to the set function of my input field. The set function
- splits the content of the input field at the "\n" newlines into a table "rawInput",
- removes wrong inputs like "bla bla bla",
- retranslates the understandable stats back into the shortened version and
- after all that, the addondb["WARRIOR"]["Arms"] table gets overwritten.

For clarification, the overwrite happens like this:

Code:
wipe(addondb.["WARRIOR"]["Arms"])
addondb.["WARRIOR"]["Arms"] = CopyTable(rawInput)
Up to this point, everything works perfectly fine. And here comes the tricky part, where weird behaviour happens.

See the above picture I posted. When I delete the last element ("Resilience Rating" here) from the input field, everything works out just well. I printed the final content of the subtables of "rawInput" and "addondb" via

Code:
table.foreach("tableName",print)
and it shows, that addondb["WARRIOR"]["Arms"] now has correctly "RES" (= "Resilience Rating", as you might guess) removed. However, reloading or restarting the game does not safe this at all. In fact, there is no change shown in the WTF/.../SavedVariables/.lua file as well.

And it gets even more weird. When I delete "Parry Rating", then again, before reloading or exiting the game, my debug prints show, that "PAR" has been successfully removed from the addondb table. But when reloading the game or exiting, the following gets saved in my WTF/.../SavedVariables/.lua file:

Code:
["failstats"] = {
   ["WARRIOR"] = {
      ["Arms"] = {
         [7] = "RES",
      }
   }
}
And now the text input field shows indeed, that the 7th element isn't "Parry Rating" anymore. It's "Resilience Rating". However, there is still the 8th element, former "Resilience Rating". This did not get deleted. Here a picture of this:



tl;dr. In conclusion, long stories short. It seems like the game only saves changes from the default settings in my table, but missing elements just get auto-filled with defaults? Am I missing something regarding table handling in the SavedVariables database?

I hope you got the idea of my code without me posting everything. Any basic ideas on what WoW does to my subtable when reloading? And why it does this weird stuff?

Here are a few more things regarding variables that get loaded. Maybe this helps.
In my .toc-file, I load everything in this order:

1. embeds.xml (embeding the Ace Libraries)
2. profiles.lua (containing the definition of the default settings)
3. Data.lua (containing global addon data, like dictionaries to translate between shorts to long stats, e.g. "INT" -> "Intellect")
4. config.lua (where actually my config dialog gets build)
5. Core.lua (with all the important stuff for my addon)

Thanks for any help. Have a great week!
Srzm

Last edited by Srzm : 06-15-21 at 06:20 PM.
  Reply With Quote
06-15-21, 05:48 PM   #2
Kanegasi
A Molten Giant
 
Kanegasi's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2007
Posts: 666
Originally Posted by Srzm View Post
I hope you got the idea of my code without me posting everything.
Post your code. All of it. We appreciate the very detailed explanation of how your code works, but we can't debug code without looking at the code. It's a waste of time to try and do anything when the problem is most likely your code.
  Reply With Quote
06-15-21, 06:11 PM   #3
Srzm
A Murloc Raider
Join Date: Jun 2021
Posts: 4
Alright then. I made a minimum version of the config where you can reproduce the problem. Just checked it. To open the config, you have to type "/test".
Here is the code.

The config.lua.

Code:
UITestAddOn = LibStub("AceAddon-3.0"):NewAddon("UITestAddOn", "AceEvent-3.0","AceConsole-3.0","AceTimer-3.0")
local AceConfigDialog = LibStub("AceConfigDialog-3.0")
local AceConfig = LibStub("AceConfig-3.0")
local self , UITestAddOn = UITestAddOn , UITestAddOn
local UITestAddOndb



local ShortStats = {
	["Strength"] = "STR",
	["Agility"] = "AGI",
	["Stamina"] = "STA",
	["Intellect"] = "INT",
	["Spirit"] = "SPI",
	["Attack Power"] = "ATK",
	["Armor Penetration"] = "ARP",
	["Expertise"] = "EXP",
	["Spell Power"] = "SPW",
	["Mana per 5s"] = "MP5",
	["Haste Rating"] = "HST",
	["Crit Rating"] = "CRI",
	["Hit Rating"] = "HIT",
	["Defense Rating"] = "DEF",
	["Dodge Rating"] = "DDG",
	["Parry Rating"] = "PAR",
	["Resilience Rating"] = "RES",
	["Armor"] = "ARM",
}

local LongStats = {
	["STR"] = "Strength",
	["AGI"] = "Agility",
	["STA"] = "Stamina",
	["INT"] = "Intellect",
	["SPI"] = "Spirit",
	["ATK"] = "Attack Power",
	["ARP"] = "Armor Penetration",
	["EXP"] = "Expertise",
	["SPW"] = "Spell Power",
	["MP5"] = "Mana per 5s",
	["HST"] = "Haste Rating",
	["CRI"] = "Crit Rating",
	["HIT"] = "Hit Rating",
	["DEF"] = "Defense Rating",
	["DDG"] = "Dodge Rating",
	["PAR"] = "Parry Rating",
	["RES"] = "Resilience Rating",
	["ARM"] = "Armor",
}

local function splitStringToTable (inputstr, seperator)
        if seperator == nil then
                seperator = "%s"
        end
        local t={}
        for str in string.gmatch(inputstr, "([^"..seperator.."]+)") do
                table.insert(t, str)
        end
        return t
end

function UITestAddOn:OnInitialize()
	self.db = LibStub("AceDB-3.0"):New("UITestAddOndb",UITestAddOn_DBdefaults, "Default");
	self.db.RegisterCallback(self, "OnProfileChanged", "ChangeProfile")
	self.db.RegisterCallback(self, "OnProfileCopied", "ChangeProfile")
	self.db.RegisterCallback(self, "OnProfileReset", "ChangeProfile")
	UITestAddOndb = self.db.profile;
	
	self:RegisterChatCommand("test", "ShowConfig")
	
	UITestAddOn.options =
	{
		name = "UITestAddOn",
		desc = "Learning to code GUI",
		type = 'group',
		args = {},
	}
	
	local bliz_options = CopyTable(UITestAddOn.options)
	bliz_options.args.load =
	{
		name = "Load configuration",
		desc = "Load configuration options",
		type = 'execute',
		func = "ShowConfig",
		handler = UITestAddOn,
	}
	
	LibStub("AceConfig-3.0"):RegisterOptionsTable("UITestAddOn_bliz", bliz_options)
	AceConfigDialog:AddToBlizOptions("UITestAddOn_bliz", "UITestAddOn")
end

local function initOptions()
	if UITestAddOn.options.args["Analysis Options"] then
		return
	end
	
	UITestAddOn:OnOptionsCreate()
	
	for k, v in UITestAddOn:IterateModules() do
		if type(v.OnOptionsCreate) == "function" then
			v:OnOptionsCreate()
		end
	end
	AceConfig:RegisterOptionsTable("UITestAddOn", UITestAddOn.options)
end

function UITestAddOn:ShowConfig()
	initOptions()
	AceConfigDialog:Open("UITestAddOn")
end

function UITestAddOn:ChangeProfile()
	UITestAddOndb = self.db.profile
	for k,v in UITestAddOn:IterateModules() do
		if type(v.ChangeProfile) == 'function' then
			v:ChangeProfile()
		end
	end
end

function UITestAddOn:AddOption(key, table)
	self.options.args[key] = table
end

function UITestAddOn:OnOptionsCreate()
	self:AddOption("profiles", LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db))
	self.options.args.profiles.order = -1
	
	self:AddOption('Analysis Options', {
		type = 'group',
		name = "Analysis Options",
		desc = "Options for Character Analysis",
		order = 1,
		args = 
		{
			header =
			{
				type = "header",
				name = "Analysis Options",
				order = 1,
				width = "full",
			},
			optionsArea = 
			{
				type = 'group',
				inline = true,
				name = "",
				args =
				{
					failstats = 
					{
						type = "input",
						name = "Enter Failstats to add / remove.\nA new line for each fail stat.",
						set = function(info,value)
									local inputRaw = splitStringToTable(value,"\n");
									
									for k,v in pairs(inputRaw) do
										if not (ShortStats[v]) then -- Check if player entered nonsence. And remove it.
											DEFAULT_CHAT_FRAME:AddMessage(v .. "is not a stat, removing.")
											table.remove(inputRaw,k)
										else
											inputRaw[k] = ShortStats[v] -- Transform the nice stats into short upper stats.
										end
									end
									
									wipe(UITestAddOndb["failstats"]["WARRIOR"]["Arms"])
									UITestAddOndb["failstats"]["WARRIOR"]["Arms"] = CopyTable(inputRaw);
									
								end,
						get = function(info)
									local failStatsString = "";
									for k,v in pairs(UITestAddOndb["failstats"]["WARRIOR"]["Arms"]) do
										failStatsString = failStatsString .. LongStats[UITestAddOndb["failstats"]["WARRIOR"]["Arms"][k]] .. "\n"
									end
									return failStatsString;
								end,
						multiline = 15,
						order = 2,
					},
				},
			},
		}
	})
end

The profiles.lua

Code:
UITestAddOn_DBdefaults = {
	profile = {
		failstats = { -- Failstats on Equipment
			["WARRIOR"] = {
				["Arms"] = { "INT", "SPI", "SPW", "MP5", "DEF", "DDG", "PAR", "RES" }, -- Arms
			},
		},
	}
}

And the .toc-file:

Code:
## Interface: 20500
## Title: UITestAddOn
## Notes: Just some goofing to learn UI.
## Author: Srzm
## Version: 0.1
## SavedVariables: UITestAddOndb

embeds.xml
profiles.lua
config.lua
And the embeds.xml:

Code:
<Ui xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://www.blizzard.com/wow/ui/  ..\FrameXML\UI.xsd" xmlns="http://www.blizzard.com/wow/ui/">
	<Include file="libs\AceAddon-3.0\AceAddon-3.0.xml" />
	<Include file="libs\AceEvent-3.0\AceEvent-3.0.xml" />
	<Include file="libs\AceTimer-3.0\AceTimer-3.0.xml" />
	<Include file="libs\AceHook-3.0\AceHook-3.0.xml" />
	<Include file="libs\AceDB-3.0\AceDB-3.0.xml" />
	<Include file="libs\AceDBOptions-3.0\AceDBOptions-3.0.xml" />
	<Include file="libs\AceLocale-3.0\AceLocale-3.0.xml" />
	<Include file="libs\AceConsole-3.0\AceConsole-3.0.xml" />
	<Include file="libs\AceGUI-3.0\AceGUI-3.0.xml" />
	<Include file="libs\AceConfig-3.0\AceConfig-3.0.xml" />
	<Include file="libs\LibSharedMedia-3.0\lib.xml" />
	<Include file="libs\AceGUI-3.0-SharedMediaWidgets\widget.xml" />
	<Include file="libs\LibDeformat-3.0\lib.xml" />
	<Script file="libs\LibStub\LibStub.lua" />
	<Script file="libs\CallbackHandler-1.0\CallbackHandler-1.0.lua" />
	<Script file="libs\LibDataBroker-1.1\LibDataBroker-1.1.lua" />
</Ui>

One last note: I develop this addon for patch TBC Classic 2.5.1.
Greetz and thanks!
Srzm

Last edited by Srzm : 06-16-21 at 02:40 AM.
  Reply With Quote
06-15-21, 06:46 PM   #4
Xrystal
nUI Maintainer
 
Xrystal's Avatar
Premium Member
AddOn Author - Click to view addons
Join Date: Feb 2006
Posts: 5,892
There is no 3.3.5a Patch for Wow.

Live - 9.0.5
Burning Crusade Classic - 2.5.1
World of Warcraft Classic - 1.13.7

Unless you are talking about the patch of a particular addon. So maybe telling us which addon it is and which of the official game versions it is designed for.

If it isn't for one of the above official servers then we cannot help you as what you are doing falls under number 4 of the terms of use on this site.

'4. Don't break WoW EULA or ToU. If you come here and post that you are selling your WoW account for real world money, offering or asking for power-levelling, in-game items or gold for real life cash, private servers or any other post that breaks WoW EULA or ToU, your post will be deleted and you will (at minimum) warned not to do it again. Repeated offenses will lead to banning from the site.'


Originally Posted by Srzm View Post
Alright then. I made a minimum version of the config where you can reproduce the problem. Just checked it. To open the config, you have to type "/test".
Here is the code.

.. Code Snipped ..

One last note: I develop this addon for patch 3.3.5a.
Greetz and thanks!
Srzm
__________________

Last edited by Xrystal : 06-15-21 at 06:49 PM.
  Reply With Quote
06-16-21, 02:40 AM   #5
Srzm
A Murloc Raider
Join Date: Jun 2021
Posts: 4
Thank you for your reply.

Originally Posted by Xrystal View Post
There is no 3.3.5a Patch for Wow.

Live - 9.0.5
Burning Crusade Classic - 2.5.1
World of Warcraft Classic - 1.13.7
That's right. Im hoping that after TBC Classic now WotLk Classic then will follow. And when it launches, I want my addon to be ready.

However, I can fully understand if that hurts the rules. I edited the TOC file, the minimal addon reproducing the problem posted above should be compatible with TBC Classic since I'm not using methods of the WOW API and the stats are all there in TBC as well.

Hope that sets things right without hurting any rules.

Thanks in advance!
Srzm
  Reply With Quote
06-16-21, 04:30 AM   #6
Xrystal
nUI Maintainer
 
Xrystal's Avatar
Premium Member
AddOn Author - Click to view addons
Join Date: Feb 2006
Posts: 5,892
Good forward planning. However, based on the api differences between Classic and TBC Classic, the addon may not be 'ready' when the next Classic version comes out as that will likely have a different API base.

If memory serves ...
Classic > Legion 7.x API with Classic related changes
TBC Classic > BfA 8.x API with TBC related changes

Meaning the likely chance that ..
WotLK Classic > Shadowlands 9.x API with WotLK related changes
Cataclysm Classic > 10.x API ...
etc


As to your problem at hand, I unfortunately cannot help with that as I don't use any of the Ace libraries.

However, my understanding of Defaults would be that the active table would first be filled with the default values and then your code would make any necessary changes .. ie, if they deleted option 7 then move option 8 to be option 7. If they set option 7 to a specific value, handle the list as a whole so that it contained the items expected in the right order. Ace may not have these actions set up as how they need to be handled may be different per addon. They likely just set up the ability to action things as they change.

Originally Posted by Srzm View Post
Thank you for your reply.



That's right. Im hoping that after TBC Classic now WotLk Classic then will follow. And when it launches, I want my addon to be ready.

However, I can fully understand if that hurts the rules. I edited the TOC file, the minimal addon reproducing the problem posted above should be compatible with TBC Classic since I'm not using methods of the WOW API and the stats are all there in TBC as well.

Hope that sets things right without hurting any rules.

Thanks in advance!
Srzm
__________________
  Reply With Quote
06-16-21, 02:00 PM   #7
elcius
A Cliff Giant
AddOn Author - Click to view addons
Join Date: Sep 2011
Posts: 75
most likely the problem is this part:
Code:
local inputRaw = splitStringToTable(value,"\n");
									
for k,v in pairs(inputRaw) do
	if not (ShortStats[v]) then -- Check if player entered nonsence. And remove it.
line breaks on windows systems are \r\n, by splitting the list by just \n and not removing the carriage returns, you will not get an exact match against your ShortStats table (other than on the last line which has no line break).
you need to trim each line before you test it:
Code:
v = v:match("^%s*(.-)%s*$") or '';

Last edited by elcius : 06-16-21 at 02:03 PM.
  Reply With Quote
06-18-21, 04:34 AM   #8
Srzm
A Murloc Raider
Join Date: Jun 2021
Posts: 4
Originally Posted by elcius View Post
most likely the problem is this part:

you need to trim each line before you test it:
Code:
v = v:match("^%s*(.-)%s*$") or '';
Thanks for your reply, elcius. I tried this fix but it didn't change anything.

I don't think that my enterfield string has any carriage returns, since it gets build with "\n" line breaks only, here:

Code:
failStatsString = failStatsString .. LongStats[UITestAddOndb["failstats"]["WARRIOR"]["Arms"][k]] .. "\n"
Or does WoW / Windows add them by default by themselves? Checking both my inputRaw and my db table with table.foreach() didn't yield any differences. Is there a chance to print a string with "\n" and "\r" without them being used as line breaks to check this?

I think at this point I'll script the whole profile management on my own, with an own SavedVariables table and without the AceDB. I did it without AceDB in a smaller addon earlier and I had absolutely no problems with set / get from subtables there.

Regardless, thanks for all your help, elcius, Xrystal, Kanegasi. From my point of view, this topic can be closed.

Greetz.
- Srzm
  Reply With Quote

WoWInterface » Developer Discussions » General Authoring Discussion » Saved Variables DB with a subtable: weird behaviour

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off