Thread Tools Display Modes
08/03/16, 08:35 AM   #1
Ayantir
 
Ayantir's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 1,019
SavedVariables, ZO_SavedVars, AccountWide, per character and name changes

Hello,

A little tutorial and things to known on savedvars.



First, what is savedvars ?

A savedVariable is a Lua variable which is dumped into a text file.
A savedVariable MUST be a table.
Elements in the table can be number, string, boolean, or table.
Functions cannot be dumped, nil values are stripped.



Why using savedvariables ?

As long as we cannot save almost nothing on TESO DB on the server, the settings or other data which should persists must be stored somewhere, so here are the SavedVars.



Where are they ?

There are in Elder Scrolls Online\{environnement}\SavedVariables.
Basically, it's Documents\Elder Scrolls Online\live(eu)\SavedVariables
More info is on the Wiki : http://wiki.esoui.com/UserFolder



Which file and what name ?

The file have the name of your Addon metafile
If your metafile is Something.txt, the Savedvariable file wille be Something.lua



How can I enable a variable to be saved ?

You can add this to the metafile
Code:
## SavedVariables: var1 var2 var3 var4 var5


I want my settings have a default value, be set by character, or accunt wide

ZOS give us an object to play more easily with saved vars.
Please note this is totally optionnal.

If in your code you do
Lua Code:
  1. var1 = "foo"
without anything else than the line in metafile, the data will be saved as is.

But if you need some help to manage your SV, you should use the ZO_SavedVars object.



How ?

1) your defaults array. it will contain the defaults values. If an user don't have SV, the whole content will be sent as "template". If you just added one subvariable, the new one will be pushed in and the existing ones won't be changed.

Lua Code:
  1. local defaults = {
  2.     something = true,
  3.     somethingElse = false,
  4. }

A first try will be :

Lua Code:
  1. local db
  2. local ADDON_NAME = "DummyAddon"
  3.  
  4. -- Initialises the settings and menu
  5. local function onAddonLoaded(_, addonName)
  6.  
  7.     --Protect
  8.     if addonName == ADDON_NAME then
  9.        
  10.         --Default values for the SavedVariables
  11.         local defaults = {
  12.             keepAmount      = 0,
  13.             fillUpAmount    = 0,
  14.         }
  15.  
  16.         -- Fetch the saved variables
  17.         db = ZO_SavedVars:NewAccountWide("THE_NAME_OF_THE_VAR_IN_METAFILE", SV_VERSION_NAME, nil, defaults)
  18.  
  19.                  -- some code

This code will "link" the local db variable to the Savedvariable THE_NAME_OF_THE_VAR_IN_METAFILE.
If a value doesn't exist in THE_NAME_OF_THE_VAR_IN_METAFILE, the constructor will fetch it from the defaults variable.

please note if db and default should remain "local", THE_NAME_OF_THE_VAR_IN_METAFILE is leaked to _G (you can inspect it).

changing db will change THE_NAME_OF_THE_VAR_IN_METAFILE.



Methods
  1. ZO_SavedVars:NewAccountWide will save the data in an account wide array. It don't support account name changes.
    The key is GetDisplayName()

    ex, the Roomba addon :

    Lua Code:
    1. ROOMBA_OPTS =
    2. {
    3.     ["Default"] =
    4.     {
    5.         ["@Ayantir"] =
    6.         {
    7.             ["$AccountWide"] =
    8.             {
    9.                 ["RoombaPosition"] = 3,
    10.                 ["RoombaAtBank"] = true,
    11.                 ["RoombaAtGBank"] = true,
    12.                 ["version"] = 1,
    13.                 ["RoombaInBag"] = true,
    14.             },
    15.         },
    16.     },
    17. }

  2. ZO_SavedVars:New will save the data in an character wide array. It don't support name changes.
    The key is GetUnitName("player")
  3. ZO_SavedVars:NewCharacterNameSettings will save the data in an character wide array. It support name changes.
    The key is GetUnitName("player") This key will change after a character rename.

    ex, the RollingStones addon :

    Lua Code:
    1. ROLLINGSTONES =
    2. {
    3.     ["Default"] =
    4.     {
    5.         ["@Ayantir"] =
    6.         {
    7.             ["Ayantir"] =
    8.             {
    9.                 ["version"] = 1.2000000000,
    10.                 ["keepAmount"] = 0,
    11.                 ["fillUpAmount"] = 0,
    12.             },
    13.         },
    14.     },
    15. }
  4. ZO_SavedVars:NewCharacterIdSettings will save the data in an character wide array. It support name changes.
    The key is GetCurrentCharacterId()

    Lua Code:
    1. ROLLINGSTONES =
    2. {
    3.     ["Default"] =
    4.     {
    5.         ["@Ayantir"] =
    6.         {
    7.             ["8798292046256355"] =
    8.             {
    9.                 ["$LastCharacterName"] = "Ayantir",
    10.                 ["version"] = 1.2000000000,
    11.                 ["keepAmount"] = 0,
    12.                 ["fillUpAmount"] = 0,
    13.             },
    14.         },
    15.     },
    16. }



SavedVars versioning

the 2nd parameter, version is a key (string or number) to push the defaults array even if an old value existed before. It erase old data.

Avoid if possible changing version. You should only change version when there is an absolute need. (addon rewrited entirely, api bump).

This action lead to reset all user config.

Ex :

Before :

Lua Code:
  1. savedVarsVersion    = 1.2,
  2.         local defaults = {
  3.             keepAmount      = 0,
  4.             fillUpAmount    = 0,
  5.         }
  6.  
  7. ROLLINGSTONES =
  8. {
  9.     ["Default"] =
  10.     {
  11.         ["@Ayantir"] =
  12.         {
  13.             ["8798292046256355"] =
  14.             {
  15.                 ["$LastCharacterName"] = "Ayantir",
  16.                 ["keepAmount"] = 0,
  17.                 ["fillUpAmount"] = 0,
  18.                 ["version"] = 1.2000000000,
  19.             },
  20.         },
  21.     },
  22. }


After :

Lua Code:
  1. savedVarsVersion    = 1.3,
  2.         --Default values for the SavedVariables
  3.         local defaults = {
  4.             keepAmount      = 10,
  5.         }
  6.  
  7. ROLLINGSTONES =
  8. {
  9.     ["Default"] =
  10.     {
  11.         ["@Ayantir"] =
  12.         {
  13.             ["8798292046256355"] =
  14.             {
  15.                 ["$LastCharacterName"] = "Ayantir",
  16.                 ["keepAmount"] = 10,
  17.                 ["version"] = 1.3000000000,
  18.             },
  19.         },
  20.     },
  21. }


Limitations

32bits client has the inherent limitations of the 32Bits allocation memory.
  • Max file = 4GB (consider that a HUGE SV is 1MB+)
  • Max values stored = Avoid more than 10k entries per level. Avoid files of more than 30MB. If you addon dump more than this, data can be corrupted.

Both 32 & 64 bits :

Max length of string variable : 1000 characters. if your string variable is more than 1000 bytes, it won't be saved


When is the file written ?

The fle is written when the UI Reloads.

Lua ReloadUI()
Lua SetCVar() -- ONLY if it trigger a reload UI (a loading screen).
Lua LogOut()
Lua Quit()
Windows Alt+F4
Clicking on the red X of the ESO window
When Game kicks you on the loading screen



Killing Process -> DATA NOT SAVED
CRASH -> DATA NOT SAVED




Bugs

Having multiple Vars set in a same metafile is dangerous.

If you want to have multiple SV please never do assignment between the variables (no var1 = var2 or any subkey of any variables)

With

Code:
## SavedVariables: var1 var2
if you do :

Lua Code:
  1. var1 = {key1 = "something", key2 = "something else"}
  2. var2 = "foo"
  3. var1.key2 = var2

var2 will be nil in SV. (var1.key2 steal the value).

If you want to do this, consider a table which embed both tables.



Recursivity, 4GB file and crashs

As in a lot of languages, Lua accept recursive calls, but if you try to save a var which contain a recursive call, it will crash your game.

For this, you must :
  • kill the recursivity before saving the var
  • or build a symetric var without recursivity <- Consider THIS as the best solution

If you play with ZOS datalists, you may consider the dataEntry recursive call. here are 2 screenshots of var which shouldn't be saved in SV.




Last edited by Ayantir : 12/10/16 at 08:42 PM.
  Reply With Quote
08/03/16, 11:01 AM   #2
sirinsidiator
 
sirinsidiator's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 1,566
Nice summary of everything you need to know about saved variables.
A few things though:
  • I created this wiki page on how to find the user folder (addon/saved variables), so we do not have to write yet another explanation that won't ever be updated again when something changes. So please link to it instead of writing that it is simply located in the documents folder, because sometimes it isn't.
  • Closing the client with Alt+F4 or the [x] won't save your data as it just kills the process. At least it worked like that when I last checked.
  • You should also mention that recursive references are really bad as they will fill up your disk and then crash the game.
  Reply With Quote
08/03/16, 11:30 AM   #3
Ayantir
 
Ayantir's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 1,019
for alt+f4 and the X, I tested when wrting the guide, and the data was saved.
for path, I'm agree, and added. and modified your post too.
I'll add something for recursive call, and the .dataEntry
  Reply With Quote
08/04/16, 06:18 AM   #4
QuadroTony
Banned
AddOn Author - Click to view addons
Join Date: Apr 2014
Posts: 828
when crashed - data will not save
when kicked for inactivity, disconnect, internet loss - looks like data will save
  Reply With Quote
09/28/16, 06:24 PM   #5
Ayantir
 
Ayantir's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 1,019
A little something that ZOS added with name changing.

Case : You're using NewAccountWide.
You have a table per character inside your db.




How to handle a name change ?

Lua Code:
  1. -- Load the saved variables
  2. db = ZO_SavedVars:NewAccountWide("MYADDON_SAVEDVARS", 1, nil, defaults)
  3.        
  4. -- db.char contains all my characters
  5. if NAME_CHANGE:DidNameChange() then
  6.     db.char[GetUnitName("player")] = db.char[NAME_CHANGE:GetOldCharacterName()]
  7.     db.char[NAME_CHANGE:GetOldCharacterName()] = nil
  8. end

Like this, the data of the concerned character will automatically be moved to the new name.




Now, how to handle a deleted char ?

If you change the name of a character, and log another character, the old name will still be here, name change will be "on hold".


Multiple answers : Here's mine !
  • New addon : Save the ID of your characters. ALWAYS. An Id inside the table will be the best choice for you to handle the name change. Look at id, if correct, just rename key. if id is not in loop, it's a deleted char.
  • Old addon: Be strong and clean the data. it should auto rebuild at next login. and you'll have same process as a new addon. auto cleaning, no user input.


How to do ?

Lua Code:
  1. -- Character renamed
  2. if NAME_CHANGE:DidNameChange() then
  3.     db.char[GetUnitName("player")] = db.char[NAME_CHANGE:GetOldCharacterName()]
  4.     db.char[NAME_CHANGE:GetOldCharacterName()] = nil
  5. end
  6.  
  7. -- Add id to protect
  8. for i = 1, GetNumCharacters() do
  9.     local name, _, _, _, _, _, characterId = GetCharacterInfo(i)
  10.     name = zo_strformat(SI_UNIT_NAME, name)
  11.     if db.char[name] and not db.char[name].id then
  12.         db.char[name].id = characterId
  13.     end
  14. end
  15.  
  16. -- No id = Char deleted
  17. for charName, charData in pairs(db.char) do
  18.     if charName ~= GetUnitName("player") and not charData.id then
  19.         db.char[charName] = nil
  20.     end
  21. end

Now your addon is namechange proof and delete entries itself if user delete a char.
  Reply With Quote
09/30/16, 04:55 AM   #6
Baertram
Super Moderator
 
Baertram's Avatar
WoWInterface Super Mod
AddOn Author - Click to view addons
Join Date: Mar 2014
Posts: 4,912
Thanks for the update Ayantir.

Question:
Is "db.char" always given in the savedavrs if we have used ZO_SavedVars:NewAccountWide or ZO_SavedVars:New in the past?
I mean: If my savedvars structure was just using a default values array without a sub-array named "char", will mySavedvarsArray always contain a sub-array "char" where the data of the characters is saved?

Or was it only an example?
  Reply With Quote
10/07/16, 10:27 PM   #7
Ayantir
 
Ayantir's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 1,019
With the launch of OneTamriel, one of side effect is that now Addons and SavedVariables folders are shared between EU & NA.

Even if it's rare, I would recommend to any new addon to move from :New & :NewCharacterNameSettings to :NewCharacterIdSettings to avoid issues

I personally have 2 chars with same name, one on EU, one on NA, and everything is shared.

:NewAccountWide is same
  Reply With Quote
10/10/16, 02:26 AM   #8
SteveCampsOut
 
SteveCampsOut's Avatar
Join Date: Apr 2014
Posts: 38
Lightbulb

Originally Posted by Ayantir View Post
With the launch of OneTamriel, one of side effect is that now Addons and SavedVariables folders are shared between EU & NA.

Even if it's rare, I would recommend to any new addon to move from :New & :NewCharacterNameSettings to :NewCharacterIdSettings to avoid issues

I personally have 2 chars with same name, one on EU, one on NA, and everything is shared.

:NewAccountWide is same
As of tonight, because of the Same Character name on EU and NA, I had to delete my saved Variables folder completely and rebuild it or I couldn't even log into the game. This needs to be addressed by addon makers.
  Reply With Quote
10/10/16, 03:11 PM   #9
tomtomhotep
 
tomtomhotep's Avatar
AddOn Author - Click to view addons
Join Date: Sep 2015
Posts: 21
Making Statements that require assumptions

Originally Posted by Ayantir View Post

Both 32 & 64 bits :

Max length of string variable : 1000 characters
You almost gave me a heart attack when I read that!!

After experimenting with:

Lua Code:
  1. local s = string.rep("1234567890", 50)
  2.   local t = string.rep("1234567890", 50)
  3.  
  4.   for i = 1,1000 do
  5.     s = s .. t
  6.   end
  7.  
  8.   d(string.len(s))     -- displayed 500500 in chat! :)

I assume you mean "In SavedVars files, the maximum length of a string value is 1000 characters."?

But that is hardly what you actually said.
  Reply With Quote
10/10/16, 03:59 PM   #10
Ayantir
 
Ayantir's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2014
Posts: 1,019
yes, it's in savedvars. sorry.
  Reply With Quote

ESOUI » Developer Discussions » Tutorials & Other Helpful Info » SavedVariables, ZO_SavedVars, AccountWide, per character and name changes

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