Saved Variables and LUA

So I had saved variables working fine but then decided to change how they were stored in order to clean them up and make the code better… and broke them completely.

So I decided to create a second saved variable for “stats” and moved several variables out of the “settings” into it. Cut down versions of my ADDON_LOADED code here;

if event == "ADDON_LOADED" and arg1 == "CarnAddon" then
	if CarnSettings == nil then
		CarnSettings = {Theme = "Light", Scale = 1}
		CarnStats = {Values = {[1]=0, [2]=0, [3]=0}, Names = {[1]="Bill", [2]="Ben", [3]="Carn"}}
	end
	MyTheme = CarnSettings.Theme
	MyScale = CarnSettings.Scale
end

Where it’s been giving me grief is retrieving the stats. I’ve been getting errors whenever I try to retrieve the values or save them. I just stopped trying to assign them to my variables and didn’t touch them. That allowed the game to at least save the variables sucessfully. But they show up as the following (I cut the names section out for brevity):

CarnSettings = {
	["Scale"] = 1,
	["Theme"] = "Light",
}
CarnStats = {
	["Values"] = {
		0, -- [1]
		0, -- [2]
		0, -- [3]
	},
}

The thinking was the “Values” entries are dynamic. There could be a bunch of them so it’d be better to reference them in the form (wrapped in loops obviously):

MyValues[i] = CarnStats.Values[i]

And similarly saving the values on logout would be something like

CarnStats.Values[i] = MyValues[i]

So what am I doing wrong? Is the syntax wrong? Should it be CarnStats.Values.i? (I thought I tried that unsuccessfully).

1 Like

Did you reset (delete) the SavedVariables file (should be done when completely out of the game). Otherwise, if you’ve use CarnSettings or CarnStats previously they will exist as whatever they were before and not get re-created from defaults.

Is MyValues at table? Otherwise MyValues[i] will cause an error.

1 Like

Yes I logged out, deleted the file then logged back in so it’d create a fresh one. The Settings and Stats from above are literally copied from the new savedvariables file. The old one only had CarnSettings and it had Value1 = 0, Value2=3 etc. under CarnSettings.

I would have expected the CarnStats to look like :

CarnStats = {
	["Values"] = {
		[1] = 0,
		[2] = 0,
		[3] = 0,
	}

But maybe a table with numeric values isn’t meant to look like one with strings.

The --[1], --[2] is just the way it’s stored in the file. Each entry is implied to be a numeric in order. You could have written the default table setup as:

if event == "ADDON_LOADED" and arg1 == "CarnAddon" then
    CarnStats = {Values = {0, 0, 0}, ...),
end

If you wanted to skip 4 then you would need to use [5]=2 for example

Different ways of doing the same thing, you just haven’t found them all yet :wink:

1 Like

That can be rewritten as this:

CarnStats = {["Values"] = {0, 0, 0}}

If your intention is to have that a sequentially indexed table, it’s generally safer not to explicitly include the indexes (you might miss one and make a complete mess in debug).

1 Like

I’ll chalk that one up to late night coding too. The array was indeed not declared beforehand. Well it was declared but after the code that used it sigh. With that change it appears to work as expected.

I’ve declared the table that way so I could see what was happening. It’ll get generated dynamically as needed now that I know the darned variables are actually working. I just wanted to be able to hardcode value 1 as 3 or 42 and know that I was getting the right value while testing.

This would be so much easier if I could debug without having to load WoW and rely on bugsack :slight_smile:

You can, sort of.

If you’re using Notepad++ you can run Lua right from the code window.

If you create stub functions for the API stuff, you can test code structure (looking for exactly that sort of thing) easily.

1 Like

Waitaminute… there’s a code window in Notepad++? I’ve been using it for a decade and never seen that.

Well, the editing window.

There’s an “EXEC” plug-in you can get so you can run code from it.

It’s “NppExec” - takes a bit to get configured correctly, but once it is, you can run almost ANY code directly from the editing window.

Ah I see. I’ll take a look. Thanks for the tip.

I’ve taken to moving almost ALL of my generic Lua code into one module and my WoW API code into another.

I have (or create) stub functions for the WoW API calls that I use in structural testing (testing to see if the basic flow of the code works).

All you really have to have for those stubs is something that takes the same input and produces output that matches the output format of the API call.

It’s a PITA to set up, but for complex programs it’s almost necessary for me.

I also kind of hacked the Lua definition file for Notepad++ and added all the API functions I could find to the list of Lua functions so they show up colored as if they were known keywords, but I don’t recommend that for the faint of heart.

Nice. I’m not sure I can go to that extent but I might have a tinker and see what I need and how much work it’d be.

Being used to Visual Studio I miss that sort of IDE and integrated debugging. I was really hoping the Addon Studio app would take off and get more development done on it.

I patterned what I did to NP++ (the abbreviation for Notepad++) off a rough description of Addon Studio that’s I’d seen somewhere.

I’m big on making my own tools.

It helps me understand what I’m doing better.

1 Like

Honestly savedvariables are driving me crazy… I added another table to my savedvariables and it’s not working. Oh and yes I definitely checked that the spelling and capitalisation is the same between the .toc file and in the .lua files. Looks like this;

	-- In ADDON_LOADED
	if CarnResult == nil then
		print ("Result = nil")
		CarnResult = {
			[1] = {[1] = "A", [2] = "A", [3] = "A", [4] = "A", [5] = "A"},
			[2] = {[1] = "A", [2] = "B", [3] = "A", [4] = "C", [5] = "A"},
			[3] = {[1] = "B", [2] = "B", [3] = "A", [4] = "C", [5] = "C"},
			[4] = {[1] = "C", [2] = "C", [3] = "C", [4] = "C", [5] = "C"}
			}
	end
	NS.LastResult = {}
	for i = 1, #CarnResult, 1
	do
		NS.LastResult[i] = CarnResult[i]
	end

Values in CarnResult are just for testing so I can see values in and out.

	-- In PLAYER_LOGOUT
	for i = 1, #NS.LastResult,1
	do
		CarnResult[i] = NS.LastResult[i]
	end	

I can see CarnResult in my savedvariables file and it looks like;

CarnResult = nil

The print statement in ADDON_LOADED backs that up. It proves the addon is detecting that and should be setting populating CarnResult. And in turn NS.LastResult should be getting populated too. But it falls over at;

	for i = 1, #CarnResult, 1

Every time it comes up with

1x CarnTest\CarnTest-1.1.lua:41: attempt to get length of global 'CarnResult' (a nil value)

Is this purely because you can’t create an indexed table if the values are themselves tables? Because I use the same logic on a different table variable but it’s storing strings, not tables and it works fine. In which case the only way to count is to use “for pairs”?

edit: And after quickly testing using “for pairs” instead that doesn’t work either. It apparently still considers “CarnResult” to be nil. So why is my CarnTest = {a bunch of data} apparently not creating a table?

second edit: I simplified the variable down to exactly the same as what I had working for another variable (just changed the names)… and it still doesn’t work.

	if CarnResult == nil then
		print ("Result = nil")
		CarnResult = {Results = {[1]="A", [2]="B", [3]="C"}}
	end
	NS.LastResult = {}
		
	for i = 1, 3, 1
	do
		NS.LastResult[i] = CarnResult.Results[i]
	end

What I find really weird is looking at bugsack…

7x CarnTest\CarnTest-1.1.lua:48: attempt to index global 'CarnTestResult' (a nil value)
[string "@CarnTest\CarnTest-1.1.lua"]:48: in function <CarnTest\CarnTest.lua:12>

Locals:
self = <unnamed> {
 0 = <userdata>
}
event = "ADDON_LOADED"
arg1 = "CarnTest"
(for index) = 1
(for limit) = 4
(for step) = 1
i = 1
(*temporary) = <table> {
}

Shouldn’t the for limit value = 3, that’s what my latest hard coded testing is set for?

Well, I scrapped all the code relating to the new saved variable and started it over from scratch copy pasting the other variable and renaming values. That finally worked. So I’m going through gradually making it more complicated one step at a time trying to figure out where I managed to break it…

If I manage to get all the way back to nested tables and it works well I dunno… I’ll have to assume there was a typo in there somewhere that didn’t throw an error but broke something.

It may be better to user the PLAYER_LOGIN event as it fires only once after all initial addons have loaded (including their Saved Variables) and before PLAYER_ENTERING_WORLD so you don’t need the addon name check…

Why use an intermendiate table NS.xxx? You can read/write directly to your SavedVariables table.

This is not necessarily his answer or a good answer, but I tend to write assuming there will be no saved variables.

I use the namespace for everything.

When/if I decide that I need saved variables, I alter the table of contents file and use the PLAYER_LOGIN, PLAYER_ENTERING_WORLD and PLAYER_LEAVING_WORLD events to move data to/from saved variables.

It’s a pattern I picked up from copying some code in my first few addons.

For me, what it does is saves me accidentally using a namespace variable for saved data and losing the data on saves. If I build with the assumption that every time I COULD save or retrieve data I do so, that feels like a more consistent model for me.

Keeping all the work in one place and the persistent storage in another seemed less prone to breakage.

But again, that’s just me.

Looking at the error report, CarnTestResult is not mentioned in your posted code so typo, missing dot or both (CarnTest seems to be the name of the addon but CarnResult
seems to be the name of your SavedVariable)

It’s kinda pointless (nothing wrong with using the savedvar directly), but if you want to use your namespace for savedvars, here’s a fun use of __index and __newindex metamethods:

local _,myaddon = ...

-- savedvar initialized for first run (but will be overwritten in future logins)
MyAddonSettings = {}

-- default values for settings
local defaults = {
    Anchor = "BOTTOMLEFT",
    LockWindow = false,
    WindowMode = 0,
    Stuff = {{1,2,3},{"a","b","c"}}
}

-- __index gets savedvar rather than value from settings table
local function getter(self,key)
    -- if variable undefined, set it to a default value
    if MyAddonSettings[key]==nil then
        if defaults[key] and type(defaults[key])=="table" then
            MyAddonSettings[key] = CopyTable(defaults[key]) -- deep copy table from default
        else
            MyAddonSettings[key] = defaults[key] -- save non-table default
        end
    end
    return MyAddonSettings[key]
end

-- __newindex saves to savedvar rather than this table
local function setter(self,key,value)
    MyAddonSettings[key] = value
end

-- define getter/setter for savedvar
myaddon.settings = setmetatable({},{__index=getter,__newindex=setter})

In more traditional __index or __newindex you’d rawget or rawset; but here it uses the global savedvar to get/set. So the key will always be undefined and the __index or __newindex will always be called.

A trivial perk this has is when PLAYER_LOGIN happens and the global savedvars are detroyed/recreated, the reference won’t get lost.

Sorry for the late response, I shouldn’t post then go to sleep.

That was posted after multiple edits to the code including changes to variable names. Because I couldn’t see anything “wrong” with the code it made me think maybe I’d accidentally used a reserved word as a variable. So I tried renaming them with a couple different variations. Pretty sure at that point I was testing with that variable name.

As for not just using the savedvariables, I figured I’d rather leave them “intact” and only update them on exit. That way if there is a bad shutdown (game crashes for example) they should remain ok. Even without the possibility of a crash, I prefer to work on an interim variable until I (the user) decide to commit the change. Mostly because I come from the era of a “Save settings” button not the modern approach where settings are applied and saved the moment you change them.

I might take a closer look at that and see whether to change the approach.