View Full Version : TableSaverLoader, save/load tables of arbitrary nesting structure to SavedGameDB


Pazyryk
Oct 06, 2011, 05:28 PM
Current version: 0.12

Changes:
0.11 (Sep 23, 2012) fixed a bug in 0.10 that caused a crash when a variable changed type.
0.12 (May 1, 2013) adds TableSave to LuaEvents so it can be called from another state.

The idea behind TableSaverLoader is that you do all game logic in Lua tables, then reference those tables in a master table for "preservation" through game exit/reload. TableSaverLoader (hooked up properly as shown in attached "ExampleMain.lua" below) will save tables of any arbitrary size* and nesting structure to SavedGameDB, then restore them on game reload. There is no issue about when you save/exit because it intercepts all 4 gamesave types: cntl-S, save from menu, F11 quicksave and endturn for autosave. The code for the gamesave interception is actually in the ExampleMain.lua file attached below.

Download here: http://forums.civfanatics.com/downloads.php?do=file&id=17824 (See How to Hook Up section below.)

(*No limit to size as far as I know, though there are always time considerations. For 16739 table elements, TableSave takes 22 sec to save the first time and about 13 seconds thereafter when all or most elements don't change, and TableLoad 0.175 seconds to load this data. That's a bit of a wait at turn end ... hopefully you won't have 16739 things to save. This is on my 5-yr-old computer running a Standard map.)

TableSave(gT, "MyMod") will save gT and all nested values and tables to a single table called "MyMod_Data" in SavedGameDB. A second table called "MyMod_Info" keeps save/load info including a checksum used to test data integrity on load.

TableLoad(gT, "MyMod") will rebuild gT after a game load.

I've included two files. One is "TableSaverLoader" with the two functions to call. The second is "ExampleMain" that calls TableSave before any manual game saves (it intercepts quicksave, cntr-s and save from menu; this code taken from Gedemon) and at turn end (so that autosave is updated), and calls TableLoad after loading a game.

My ExampleMain sets up a global table ("gT") and "preloads" it with two other tables:

gValues = {}
gPlots = {}
gT = { gValues, gPlots } --my global table that holds everything to be saved

To illustrate, here's an interactive FireTuner session using TableSaverLoader and ExampleMain to drive it:

START A NEW GAME, PRESS "Begin Journey"
--prints TableSave time: 0.36800000000005, checksum: 99, inserts: 2, updates: 0, unchanged: 0, deletes: 0

print(gT) -- prints table: 25EDF9C0
print(gValues) -- prints table: 25EDF858
print(gT[1]) -- prints table: 25EDF858

END TURN
--prints TableSave time: 0.095000000000027, checksum: 99, inserts: 0, updates: 0, unchanged: 2, deletes: 0

a_table = {x=1,y=2,z=3}
a_nested_table = {'a',{'b',{a_table,{{{{{'hello there'}}}}}}}}

gValues.bBoolean = true
gValues.a_table = a_table
gValues.a_nested_table = a_nested_table

print(gValues.bBoolean) --prints true
print(gValues.a_nested_table[2][2][2][1][1][1][1][1]) --prints hello there

print(a_table) --prints table: 266B1EF0
print(gValues.a_table) --prints table: 266B1EF0
print(gValues.a_nested_table[2][2][1]) --prints table: 266B1EF0

print(gValues.a_table.y) --prints 2
print(gValues.a_nested_table[2][2][1].y) --prints 2


F11 (QUICKSAVE)
--prints TableSave time: 0.14399999999978, checksum: 1243, inserts: 17, updates: 0, unchanged: 2, deletes: 0


EXIT TO MAIN MENU AND LOAD QUICKSAVE, PRESS "Continue your journey"
--prints TableLoad ran without error; checksum: 1243

print(gValues.bBoolean) --prints true
print(gValues.a_nested_table[2][2][2][1][1][1][1][1]) --prints hello there

print(a_table) --prints nil
print(gValues.a_table) --prints table: 155CB858
print(gValues.a_nested_table[2][2][1]) --prints table: 155CB858
--note that a_table wasn't saved (it isn't in gT)
--however, two "paths" to the same table in gT point to the same table (this was a devil to make work!)

print(gValues.a_table.y) --prints 2
print(gValues.a_nested_table[2][2][1].y) --prints 2


DebugFillgPlots() --this function gets plotyType, featureType and improvementType for each plot, and puts them in gPlots
--prints found 4160 plots

F11 (QUICKSAVE)
--prints Getting DB info for TableSave (this is the first save after a game load)...
--prints TableSave time: 21.983, checksum: 841967, inserts: 16720, updates: 0, unchanged: 19, deletes: 0
--(MyMod_Data now has 16739 rows)

print(gPlots[64][50].plotType) --prints 3
print(gPlots[64][50].featureType) --prints 0
print(gPlots[64][50].improvementType) --prints -1

NEXT TURN
--prints TableSave time: 13.491, checksum: 841967, inserts: 0, updates: 0, unchanged: 16739, deletes: 0
-- (its a bit faster now because it doesn't update unchanged values)

EXIT TO MAIN MENU AND LOAD AutoSave_0001, PRESS "Continue your journey"
--prints TableLoad ran without error; checksum: 841967

print(gPlots[64][50].plotType) --prints 3
print(gPlots[64][50].featureType) --prints 0
print(gPlots[64][50].improvementType) --prints -1


Below is MyMod_Info after the session above. One line is added for each TableSave. When TableLoad runs, it reads the last line (to get checksum at last save) and writes info to the last 3 columns of this line (if called >1 time, it will keep reading and writing on this same line).
http://forums.civfanatics.com/attachment.php?attachmentid=303664&stc=1&d=1317942815

And MyMod_Data (1st page only). You don't need to understand this, but it may be interesting if you want to know the underlying logic.
http://forums.civfanatics.com/attachment.php?attachmentid=303663&stc=1&d=1317942815

Compatibility between mods
There should be no problem as long as you use a unique string (your mod name for example) in your calls to TableSave and TableLoad.

Sharing between mods
I have not tried this, so I'm explaining from a purely theoretical perspective. In theory, two mods should be able to "read" the same DB table using TableLoad without any problems. However, you can't have two mods modifying the same table. Each one keeps its own representation of the DB table in memory (this is part of TableSave, not TableLoad). This is so TableSave knows which values to update, and which are unchanged and do not need an update. All hell will break loose if two mods TableSave to the same table.

Does this replace SaveUtils and ShareUtils?
I don't know. I built this for my own needs, which do not include sharing between mods. Primarily, I need to have many tables that maintain content through save/load. I don't want to write a separate save function for each table, and I don't want to have to worry about making a structure that is optimal for DB save (which is not necessarily optimal for complex Lua logic). Also, my tables are somewhat large (though not 16000 elements thank goodness!) so I needed a good understanding of speed and how many table elements I can safely work with (I don't know the speed or capacity of SaveUtils ... it may or may not be better than TableSaverLoader).

Note for initial release: don't try to save/load two different global tables with 2 calls to TableSave, e.g., TableSave(gT1, "MyMod1") and TableSave(gT2, "MyMod2"). This is intended to work in the next version, but won't work now.

!!!!Warning: if you keep the DB open in SQLite Manager through a game exit/load cycle, bad things will happen!!!! [Edit: I'm not 100% sure but I think that this warning is no longer an issue after one of the patches.]

----------------------------------------------------------------------------------

How to Hook Up

You could potentially run TableSave every time something changes. But this will be very slow because each save involves at least one database transaction (http://www.sqlite.org/faq.html#q19), and these have to wait for your hard drive to rotate around at least twice (assuming you have a rotating HD). TableSave is fast because it packs up to 600 updates or 300 inserts into a single transaction. It is also smart enough to know what has changed and what has not, so only updates DB data that has actually changed. The best way to use this is to run TableSave immediately before each game save. Unfortunately, Firaxis did not provide us with a gamesave event. So you will have to do a little coding to do this. In the example below, all of the tables I want to save are nested in gT, which is a field in MapModData (I create a local version of gT in all files that use it). "Ea" is the name of my mod so I use that as a unique string identifier.

To intercept saves from the normal Game Save menu, I added this bit of code in SaveMenu.lua:
--Paz add (somewhere near the top)
MapModData.gT = MapModData.gT or {}
local gT = MapModData.gT
--end Paz add

...snip...

function OnSave()
--Paz add
LuaEvents.TableSaverLoaderSave(gT, "Ea")
--end Paz add
if(g_SelectedEntry == nil) then
...
Note that I added TableSave to LuaEvents in version 0.12, so you will need the new version for this to work. The bit of code at the top exposes my target table to this file (now pointed to by the local variable gT). The LuaEvents call will fire TableSave when the player clicks Save (even if the player clicks No on the following dialog box -- but it doesn't matter if we have an extra DB save).

To intercept quicksaves from either player clicking F11 or Quick Save from the Main menu, add this code:
function InputHandler( uiMsg, wParam, lParam )
if uiMsg == KeyEvents.KeyDown then
if wParam == Keys.VK_F11 then
TableSave(gT, "Ea")
print("Quicksaving...")
UI.QuickSave()
return true
end
end
end

function OnQuickSaveClicked()
print("QuickSaveGame clicked")
TableSave(gT, "Ea")
UI.QuickSave()
end

function OnEnterGame() --Runs when Begin or Countinue Your Journey pressed
ContextPtr:SetInputHandler(InputHandler)
local QuickSaveButton = ContextPtr:LookUpControl("/InGame/GameMenu/QuickSaveButton")
QuickSaveButton:RegisterCallback( Mouse.eLClick, OnQuickSaveClicked )
Events.LoadScreenClose.Add(OnEnterGame)


Then you have to worry about autosaves. This is rather tricky due to timing of the autosave. You really want to run TableSave as close as possible (but before) the autosave. This is the best I think you can do:
local BARB_PLAYER_ID = GameDefines.MAX_PLAYERS - 1
local function OnEveryPlayerTurn(iPlayer)
if iPlayer == BARB_PLAYER_ID then
TableSave(gT, "Ea")
end
end
GameEvents.PlayerDoTurn.Add(OnEveryPlayerTurn)
There is a flaw here. If anything changes during the barb turn it will be reflected in the autosave but not the SaveGameDB. But that's the best you can do as far as I've been able to determine. (Hooking it up to OnActivePlayerTurnStart or OnActivePlayerTurnEnd is very far from the autosave.)


You only ever need TableLoad when loading a game from a save file. I have this code in my init function:

local bNewGame = true
local DBQuery = Modding.OpenSaveData().Query
for row in DBQuery("SELECT name FROM sqlite_master WHERE name='Ea_Info'") do
if row.name then bNewGame = false end -- presence of Ea_Info tells us that game already in session
end

if bNewGame then
print("Initializiing for new game...")
else
print("Initializing for loaded game...")
TableLoad(gT, "Ea")
endThe code above figures out whether this is a new game or not by the presence of Ea_Info in SavedGameDB (this is one of two tables created by TableSave). If you used TableSave(gT, "MyMod"), then this file would be MyMod_Info. If this file is present, then this must be a loaded game. If file not present, then this must be a new game.


What not to do:
Don't try to include TableSaverLoader.lua from multiple Lua states. The file creates a Lua representation of your SavedGameDB data and uses this to know what parts to update and what parts are unchanged. If you include from two states, you are basically duplicating this info; if something changes in state #1, then it will no longer be accurate in state #2. (This is why I use a LuaEvents above to call from SaveMenu.lua.

Pazyryk
Nov 18, 2011, 09:17 AM
This seems to be bug free. I've been using it in a lot of test script for several weeks (sadly, not a lot of other people seem to be using it). So I don't consider it a beta any more. I have one change that I intend to do that will allow the player to save to multiple tables in SaveGameDB (currently, multiple mods can save to different tables, but one mod can save only to one table). That's a very specialized need, though, so I probably won't get around to this until mid-December.

THE ONLY
Dec 06, 2011, 03:49 PM
I seem to understand the prospect,however, I am lost as how to add to the game. Set some examples. Never ever get discouraged. I have multiple mods which I play with and few times does the game crash to desktop. Is the latest version able to stop this. Also, will it disrupt save replace SaveUtils and ShareUtils. Not that many have posted on thread. Man keep us in mind. Pretty soon dll to modify.

Pazyryk
Dec 08, 2011, 11:23 AM
@The only,

This is really only useful if you are building your own mod.

All of the very advanced mods have some need for memory, but all of them have implemented their own solutions (or used SaveUtils). I personally feel that TableSaverLoader is a more elegant solution, but that is a biased opinion. It's certainly more generic and up-to-date. (By up-to-date I mean that it uses the new SavedGameDB rather than the once-killed-then-resurrected script functions.)

Pazyryk
Jan 10, 2012, 01:00 PM
Not much traffic here but I hope some modders are finding this useful.

I'll note that I've been running this for several months now during a lot of experimental modding (sometimes with several thousand table elements with a lot of table deletion/creation). I have not yet ever seen a failed checksum (except when testing the checksum test) so it appears to be entirely stable. Also, I've seen at least one advanced modder claim that SavedGameDB is not entirely stable across game save/reloads. My experience contradicts this.

Moriboe
Jan 21, 2012, 02:44 PM
Yes, I do want to use this. But my lua skills are limited it seems. I've tried what I could think of and failed, so I'm trying to get my facts straight (every statement is a question mark for me by now):

I have TableSaverLoader.lua imported into the VFS; ExampleMain.lua into VFS and added to the content as InGameUIAddin. Next I want to use gT in other lua files so I import ExampleMain.lua in said files.
But it doesn't work. I got past the "gT is nil" stage, but now I'm stuck in "gT.gPlots is nil" (or in full: attempt to index field 'gPlots' (a nil value)). I've tried putting in some values in them right after the declaration in ExampleMain.lua, but doesn't help. I also get an error in InGame.lua now:

[43170.593] Runtime Error: [string "Assets/UI/InGame/InGame.lua"]:1076: attempt to index local 'addinFile' (a nil value)

Also, a handful of imports in files' global scope results in many more executions of your files, which I doubt is supposed to happen:

[43169.922] CityStateGreetingPopup: loaded VarSaverLoader.lua
[43169.922] CityStateDiploPopup: loaded VarSaverLoader.lua
[43169.938] TechAwardPopup: loaded VarSaverLoader.lua
[43169.953] WonderPopup: loaded VarSaverLoader.lua
[43170.047] DiploRelationships: loaded VarSaverLoader.lua
[43170.421] ChooseFreeItem: loaded VarSaverLoader.lua
[43170.437] ProductionPopup: loaded VarSaverLoader.lua
[43170.453] LoadMenu: loaded VarSaverLoader.lua
[43170.468] SaveMenu: loaded VarSaverLoader.lua
[43170.531] SaveMenu: loaded VarSaverLoader.lua


In short: I am in need of the basic basics of using the lua files you provided: how to activate, make available, call, ... I have written lua without saving functionality without problems, but somehow I utterly fail at using both this and SaveUtils.

Pazyryk
Jan 21, 2012, 04:37 PM
Funny, I just made a comment here that may address your problem (see post #4): http://forums.civfanatics.com/showthread.php?t=451968. Basically, you're adding these to your mod twice. Only ExampleMain should be an InGameUIAddin (with VFS=False). TableSaverLoader is added to the mod by VFS=True (that's all!, don't do something else to try to add it again).

ExampleMain is only given here to show you how to link TableSaverLoader to your existing "main" lua file, with all of the needed "save intercepts". But if you're just getting started with Lua, then this file may be a good "template" to use as your main Lua file that includes all other Lua files.


I'm not sure this is the source of all of your problems, but its certainly the source of many problems. Try fixing that and try again.

Moriboe
Jan 22, 2012, 01:18 PM
Everything is working smoothly now, thanks for the advice! I also found Onni's UI modding guide extremely enlightening, I should have checked it earlier. There were other problems, including stupid stuff like syntax errors, but my bad organization obfuscated things.

I will continue to use TableSaverLoader; with the example main you provided and some basic lua threading knowledge (which I didn't have up till now), it's basically plug and play. Great job! :goodjob:

Pazyryk
Feb 17, 2012, 04:43 PM
If anyone is interested in trying this, I think that this will work:

TableSave(MapModData, "MyMod")
TableLoad(MapModData, "MyMod")

Now any tables or values that MapModData points to will be "preserved" on game exit/reload (or more accurately, they will die and be resurrected whole again).

I realized based on some conversation in Spatz's subforum that I didn't do a very good job of explaining the overall thinking behind TableSaverLoader. I explain what it does but not how to use it or why it exists in the first place.

Here's the thing, since I released this in October 2011, I've coded about 3000 lines of Lua for my Éa mod. I have about 500 variables that can change at any time and need to be preserved through game exit/reload. In all these 4 months of coding and debugging, I have spent exactly 0 minutes thinking about game exit/reload issues. I am not exaggerating: 0 minutes coding save/load functions; 0 minutes troubleshooting save/load issues; 0 minutes thinking about it. In fact, I'd have to review my code here to remember how to access SavedGameDB. I have just simply forgotten about SaveGameDB, SaveUtils, or any issue related to saving data. I use Lua tables for all game logic and saving/loading happens automatically without any extra coding or thought. That is the reason for TableSaverLoader.

Spatzimaus
Feb 17, 2012, 05:17 PM
If anyone is interested in trying this, I think that this will work:
TableSave(MapModData, "MyMod")
TableLoad(MapModData, "MyMod")

I'm not sure if that works, given the seemingly undefined nature of the base MapModData structure, but I KNOW that adding an additional level, like
TableSave(MapModData.Mythology, "Mythology")
works fine (as does the corresponding TableLoad, of course), where my structure variables are things like MapModData.Mythology.PlayerFavor() and other variables on that same level. It's probably safer to do it this way to begin with, to avoid potential conflicts with other mods (or at least the potentially massive overhead of saving the same structures multiple times), and all it costs you is a single line of declarations before any Load is done (where you set "MapModData.Mythology = {}").

Pazyryk
Feb 17, 2012, 05:28 PM
The reason I think that will work is that TableSave is just a function call, and all arguments (even MapModData) will be turned into local references in the function itself. You just end up with a local table that points to everything that MapModData used to point to. Of course, you will be saving everything in MapModData, which you may or may not desire.

(or at least the potentially massive overhead of saving the same structures multiple times),

TableSave won't do this. If you have 5 pointers to one table (due to weird table nesting) it will only save the table once (though it will preserve your weird nesting with the 5 pointers to this table).

Spatzimaus
Feb 17, 2012, 06:35 PM
TableSave won't do this. If you have 5 pointers to one table (due to weird table nesting) it will only save the table once (though it will preserve your weird nesting with the 5 pointers to this table).

What I mean is the following. Let's say, hypothetically, that I follow your earlier example in my mod and go

TableSave(MapModData, "Mythology")
where all of my data is actually inside the MapModData.Mythology substructure, as I've mentioned before.

Then, someone else makes a mod, and does
TableSave(MapModData, "Empires")
where all of their data is inside the MapModData.Empires substructure.

Then, a third person does
TableSave(MapModData, "Ascension")
where all of THEIR data is inside MapModData.Ascension.

Wouldn't the script attempt to save three copies of the full MapModData structure (within Mythology_Data/Mythology_Info, Empires_Data/Empires_Info, etc.), each of which is much larger than the size it would be for each individual mod? That is, wouldn't Mythology_Data also include the MapModData entries added by the Empires and Ascension mods, since it'd have no way to identify which variables were specific to that particular mod? Even if it recognized the structure pointers were identical for all three and refused to duplicate the whole superstructure, this'd still seem to have some inefficiencies. And if one of the mods in question didn't use your component, and found some other way to store their MapModData information (or maybe they don't even WANT to store it, and are only using it as temporary storage), then you couldn't even do that sort of redundancy check.

Anyway, that's why I suggested only passing the specific substructure of MapModData used by your particular mod.

Pazyryk
Feb 17, 2012, 06:37 PM
Yeh, that's bad.

Though if you could coordinate so that only one mod did all of the TableSave/TableLoad calls, it could still work. Everyone would have access to the same data.

Of course, it's not necessary at all. I do essentially the same as you. I call TableSave(gT, "Ea") but then in my init lua and in each UI state I have the lines:

if not MapModData.gT then
MapModData.gT = {}
end
local gT = MapModData.gT

so that all of my mod data (in gT) is available anywhere.

Spatzimaus
Feb 17, 2012, 06:57 PM
Though if you could coordinate so that only one mod did all of the TableSave/TableLoad calls, it could still work. Everyone would have access to the same data.

Sure. And obviously, the three mod names I used are all my own, so it's not hard for me to do that sort of sharing if I wanted to. But I was using the hypothetical case of unconnected mods; since MapModData is just a really, really useful way to pass data around, I expect it to be used by a lot of mods in the future (and as you noted, you use it yourself, so that's at least two of us). So, just for safety's sake, I'll only ever do the explicit reads of MapModData.Mythology and such, to make sure I'm not grabbing anyone else's data. Assuming no one else uses "Mythology", "Empires", or "Ascension", that is; if they did, things could get really awkward.

Pazyryk
Feb 17, 2012, 07:16 PM
Anyone who runs two "Mythology" mods (thinking something good will come of it) deserves a CTD.

Spatzimaus
Feb 17, 2012, 07:35 PM
Yeah, I've got dibs on that name, so BACK OFF!!!!

More seriously, though, it's just like anything else: whenever possible you should try to steer clear of generic names, to avoid even the possibility of a naming conflict. I really should change that structure name to MapModData.AoM_Mythology to reduce the risk, but honestly, there aren't a whole lot of folks making Civ5 mods right now, and they're all here, so it shouldn't be much of an issue.

Pazyryk
Sep 23, 2012, 02:06 PM
Well, it's been a year and I finally found a bug.

Version 0.10 crashes if you have a variable that changes type. It's not a good idea to do this anyway, which is why I didn't run into the bug until now. But the updated version (0.11) will now handle this situation properly.

FramedArchitect
Apr 02, 2013, 11:08 PM
I've been using the component for a few days and it works great. Thank you!

I have run into a problem. I have a global function that iterates over one of my saved global tables and returns a boolean; I use this boolean to switch on a new status icon in CityBannerManager.

The global function returns proper values in all contexts except CityBannerManager, however, which apparently cannot access the global table (this is both before and after TableSave).

I have tried a method using saveutils and sharedata, which does expose the global table to CityBannerManager but this somehow intercepts TSL and squashes its functionality.

I realize you don't use sharedata, but I was wondering how you may have solved similar issue in your own mods. I, too, am not worried about cross-mod compatibility.

Pazyryk
Apr 03, 2013, 10:04 AM
@FramedArchitect

I'm glad someone else is using this.

CityBannerManager (and every other UI) runs in its own state, and so it doesn't have access to any "globals" defined in your mod. If I understand correctly there aren't really any "globals" in Lua, despite the use of that term: everything has some scope that is limited.

Fortunately, there is a table called MapModData this is a "super global". It's seen by all Lua states. So in my mod I have this
MapModData.gT = MapModData.gT or {}
local gT = MapModData.gT
at the beginning of darn near every file including both UI and files in my mod's main state. It's written so that run order doesn't matter. Whichever file happens to run first creates the gT table and a pointer to it contained in MapModData. The second line creates a local variable in every file that has a pointer to gT. Then I put all my tables I want saved into gT.

Pazyryk
Apr 03, 2013, 10:17 AM
Btw: I did find one more mild issue (I don't think it deserves to be called "bug"):

If you have string variables that contain international characters like É, then the checksum won't be correct. It'll print an error to Live Tuner and you can see it if you open the MyMod_Data table in the database. However, the international characters seem to load just fine despite the error message. International charaters use variable numbers of bytes, and there is something inconsistent about the way my checksum counts these for save versus load. I'll fix this someday...

FramedArchitect
Apr 05, 2013, 09:19 AM
@FramedArchitect
Fortunately, there is a table called MapModData this is a "super global". It's seen by all Lua states. So in my mod I have this
MapModData.gT = MapModData.gT or {}
local gT = MapModData.gT
at the beginning of darn near every file including both UI and files in my mod's main state. It's written so that run order doesn't matter. Whichever file happens to run first creates the gT table and a pointer to it contained in MapModData. The second line creates a local variable in every file that has a pointer to gT. Then I put all my tables I want saved into gT.

Hi Pazyryk, thanks for this method. It has not worked for me, even though I've placed this pointer on every lua file in project (even CityBannerManager, and the main...). I suspect I am just loading up my project in the wrong way. I may look at your project for some ideas there.

Thanks again for TSL!

Pazyryk
Apr 06, 2013, 09:50 AM
Is your mod not "seeing" MapModData? I can't imagine how this would happen, but you can test it easily in Live Tuner. Just try printing MapModData after selecting different Lua states. You should always see a table (the same table) from any state.

FramedArchitect
Apr 09, 2013, 11:16 PM
Is your mod not "seeing" MapModData? I can't imagine how this would happen, but you can test it easily in Live Tuner. Just try printing MapModData after selecting different Lua states. You should always see a table (the same table) from any state.
Interesting.... I had never really messed with Firetuner in this way....

So, while my mod is running this returns same table for all states EXCEPT the main state, which returns nil. My mods main and CityBannerManager return same table value... what's the issue there?

Pazyryk
Apr 09, 2013, 11:51 PM
"Main" state doesn't matter. Basically, if you can print it in the fire tuner in a given state, then the name is a "global" that should be visible anywhere in that state (unless you do something like local MapModData = nil, which won't hurt MapModData at all but will effectively sandbox it).

Given that you can access MapModData, I can't see why the code above doesn't work for you.

FramedArchitect
Apr 10, 2013, 04:02 PM
Given that you can access MapModData, I can't see why the code above doesn't work for you.

*EDIT: Having given this a bit more thought, I realized I needed to set up a local in each context for table ... something like

local t = gT.g_PlagueCities


Which indeed was part of the problem. Also in CBM state I had to call local to define MapModData.

Overall I find TSL extremely easy to use, much more so that saveutils/sharedata. So thanks again.

Pazyryk
Apr 11, 2013, 09:50 AM
Which indeed was part of the problem. Also in CBM state I had to call local to define MapModData. I'm still not following you here. MapModData (like most everything in Lua including function names) is just a name. Names can be redefined in any scope (or undefined with name=nil). But MapModData should be a global in any Lua scope that already holds a pointer to one single table (the same table pointed to by all those different MapModData's). You shouldn't need to do anything with MapModData itself. You can (optionally) create a local variable in a given file (or even narrower scope) that points to this table with "local MapModData = MapModData". That should never be necessary but can speed things along if you have many accesses to that table.

If you type the line "MapModData.gT = {}" you are really doing three things: 1) Creating a table (the "{}" part) which, like any data in Lua, is fundamentally nameless. 2) Creating an element in the table MapModData called gT (table elements do have names). 3) Putting into that table element a pointer to the table you created. You can create a new pointer to the same table by "local bunnies = MapModData.gT". Neither "MapModData.gT" nor "bunnies" really is the table. Both hold pointers to the same table, though one happens to be a local variable and the other an element of a table. If you remove all pointers to a table, then Lua will quietly (without you telling it to) garbage collect that table since it is now eternally inaccessible to you.

The logic behind "MapModData.gT = MapModData.gT or {}" is as follows:
If MapModData.gT does not exist yet, then MapModData.gT (the left side of the "or") evaluates to nil and Lua executes the stuff to the right of "or". I.e., it creates a new table and assigns a pointer to the new table to the just-now created element gT in MapModData.
If MapModData.gT already exists (because the code ran from some other file or state before this) then nothing happens. The table element is unchanged and Lua does not even look at the right side of the "or" (so no new table is inited). (Due to some Lua optimization this kind of construction is faster than an "if then" construction.)After this, MapModData.gT now holds a pointer to one single table (the same table) from any scope, regardless of file run order. After this, it is now safe to create additional (local) pointers to this table from many different scopes with something like "local gT = MapModData.gT".

Perhaps it's confusing to use the same name for a local variable and a table element or a global variable, but this is common in Lua. E.g., "local MapModData = MapModData" or "local print = print" or "local sort = table.sort". This is very convenient because you can do it at the top of any scope without having to change the code below (well, the 3rd example requires some re-coding). But it has a major impact on the compiled code and resulting execution time since local variables are easier to access than global.

The last piece of the puzzle that may be causing confusion here is to understand that "globals" in Lua aren't really global. They are themselves local to the particular state. In Civ5, each UIAddIn (with all its included files) is in it's own state. "Gobals" can be redefined in any state without affecting that name in other states. There are a bunch of globals already defined for you in all of these different states, including, for examples, "print" and "MapModData" (the former added by the base Lua package and the latter kindly provided by Firaxis). But you can redefine any of these in any state or undefine it with a nil assignment. You can use the Fire Tuner to see what particular globals are defined in each state (http://forums.civfanatics.com/showthread.php?t=487598). As you observed, "MapModData" is not defined in Main state, though it is in any "mod-accessible" state. Conversely, "_G" and "io" are defined in Main state but have been sandboxed by Firaxis in all other states so aren't available to modders. Even more tricky is a case like "os", which seems to exist in all states but actually (I suspect) points to a different table in Main versus other states, so modders can't do something like os.remove(<your harddrive>) from a mod but they can use os.clock() (or it could be the same table with restricted access to elements -- I'm not sure how they sandbox individual table elements like this).

Not sure if any of this helps, but your language makes me think that something is missing in your "Lua think" that is leading to an error.

Overall I find TSL extremely easy to use, much more so that saveutils/sharedata. So thanks again.Yes, I agree, though I didn't want to blow my own horn too much :). saveutils/sharedata was created before we were given SaveGameDB. What's nice about TSL is that I don't have to think about it at all. Tables are just magically restored with their prior contents after game load. (The bad thing is the awkward way we have to intercept gamesaves since we still lack an Events for this.)