Saving/Loading simple tables with a game

Gedemon

Modder
Super Moderator
Joined
Oct 4, 2004
Messages
12,863
Location
France
Not fully tested, so use it carefully, I've no idea of the size limit and performance on large table.

I tested it by saving/loading a table with all plots coordinates of a small map and some data for each plot.

In a lua file loaded as an <UserInterface> component (as GameConfiguration.SetValue doesn't exist in scripts contexts):
Code:
-----------------------------------------------------------------------------------------
--    FILE:     SaveLoad.lua
--  Gedemon (2017)
--  Loading/Saving simple tables with the game file using Pickle Table for serialization
--  http://lua-users.org/wiki/PickleTable
-----------------------------------------------------------------------------------------

----------------------------------------------
-- Pickle.lua
-- A table serialization utility for lua
-- Steve Dekorte, http://www.dekorte.com, Apr 2000
-- Freeware
----------------------------------------------

function pickle(t)
  return Pickle:clone():pickle_(t)
end

Pickle = {
  clone = function (t) local nt={}; for i, v in pairs(t) do nt[i]=v end return nt end
}

function Pickle:pickle_(root)
  if type(root) ~= "table" then
    error("can only pickle tables, not ".. type(root).."s")
  end
  self._tableToRef = {}
  self._refToTable = {}
  local savecount = 0
  self:ref_(root)
  local s = ""

  while #(self._refToTable) > savecount do
    savecount = savecount + 1
    local t = self._refToTable[savecount]
    s = s.."{\n"
    for i, v in pairs(t) do
        s = string.format("%s[%s]=%s,\n", s, self:value_(i), self:value_(v))
    end
    s = s.."},\n"
  end

  return string.format("{%s}", s)
end

function Pickle:value_(v)
  local vtype = type(v)
  if     vtype == "string" then return string.format("%q", v)
  elseif vtype == "number" then return v
  elseif vtype == "boolean" then return tostring(v)
  elseif vtype == "table" then return "{"..self:ref_(v).."}"
  else --error("pickle a "..type(v).." is not supported")
  end
end

function Pickle:ref_(t)
  local ref = self._tableToRef[t]
  if not ref then
    if t == self then error("can't pickle the pickle class") end
    table.insert(self._refToTable, t)
    ref = #(self._refToTable)
    self._tableToRef[t] = ref
  end
  return ref
end

----------------------------------------------
-- unpickle
----------------------------------------------

function unpickle(s)
  if type(s) ~= "string" then
    error("can't unpickle a "..type(s)..", only strings")
  end
  local gentables = loadstring("return "..s)
  local tables = gentables()

  for tnum = 1, #(tables) do
    local t = tables[tnum]
    local tcopy = {}; for i, v in pairs(t) do tcopy[i] = v end
    for i, v in pairs(tcopy) do
      local ni, nv
      if type(i) == "table" then ni = tables[i[1]] else ni = i end
      if type(v) == "table" then nv = tables[v[1]] else nv = v end
      t[i] = nil
      t[ni] = nv
    end
  end
  return tables[1]
end



-----------------------------------------------------------------------------------------
-- Load / Save
-- Using Civ6 GameConfiguration
-----------------------------------------------------------------------------------------

-- save
function SaveTableToSlot(t, sSlotName)
    local s = pickle(t)
    GameConfiguration.SetValue(sSlotName, s)
end

-- load
function LoadTableFromSlot(sSlotName)
    local s = GameConfiguration.GetValue(sSlotName)
    if s then
        local t = unpickle(s)
        return t
    else
        print("ERROR: can't load table from ".. tostring(sSlotName))
    end
end

function Initialize()
    ExposedMembers.SaveTableToSlot = SaveTableToSlot
    ExposedMembers.LoadTableFromSlot = LoadTableFromSlot
end
Initialize()

usage (from any context) :
> ExposedMembers.SaveTableToSlot(t, "myTable")
> t = ExposedMembers.LoadTableFromSlot("myTable")


When to save if you can't do it at specific times ?

Code:
-- this Lua event is called when listing files on the save/load menu
function SaveMyTable()
    ExposedMembers.SaveTableToSlot(myTable, "myTable")
end
LuaEvents.FileListQueryComplete.Add( SaveMyTable )

-- could get the quicksave key here
function OnInputHandler( pInputStruct:table )
    local uiMsg = pInputStruct:GetMessageType();
    if uiMsg == KeyEvents.KeyDown then
        if pInputStruct:GetKey() == Keys.VK_F5 then -- but the binding can be changed...
            ExposedMembers.SaveTableToSlot(myTable, "myTable")
        end
    end
    -- pInputStruct:IsShiftDown() and pInputStruct:IsAltDown()
end
ContextPtr:SetInputHandler( OnInputHandler, true )

-- Or on this event for quick save
function OnInputAction( actionID )
    if actionID == Input.GetActionId("QuickSave") then
        ExposedMembers.SaveTableToSlot(myTable, "myTable")
    end
end
Events.InputActionTriggered.Add( OnInputAction )
 
Last edited:
Yes, this is a very valuable tool for modders. The code works well, but you have forgot to put "Add" in the last line. So it should be
Code:
Events.InputActionTriggered.Add( OnInputAction )
to catch quicksave-events.
I guess forgetting the "Add"-statement is a common mistake for Lua-modders. :D
 
Yes, common, thanks, edited.
 
OH MY GOD I NEEDED THIS. I've been trying and looking EVERYWHERE and it was right under my nose! This is perfect! Thank you Gedemon!
 
there is an official method to save/load table, the serializer from Firaxis is faster as it runs in gamecore
Code:
-- ===========================================================================
--    Serialize custom data in the custom data table.
--    key        must be a string
--    value    can be anything
-- ===========================================================================
function WriteCustomData( key:string, value )
    local pParameters :table = UI.GetGameParameters():Add("CustomData");
    if pParameters ~= nil then
        pParameters:Remove( key );
        local pData:table = pParameters:Add( key );
        pData:AppendValue( value );
    else
        UI.DataError("Could not write CustomData: ",key,value);
    end
end

-- ===========================================================================
--    Read back custom data, returns NIL if not found.
--    key        must be a string
--    RETURNS: all values from the associated key (or nil if key isn't found)
-- ===========================================================================
function ReadCustomData( key:string )
    local pParameters    :table = UI.GetGameParameters():Get("CustomData");
    local kReturn        :table = {};
    if pParameters ~= nil then
        local pValues:table = pParameters:Get( key );       
        -- No key or empty key?  Return nil...
        if pValues == nil then
            return nil;
        end
        local count:number = pValues:GetCount();
        if count == 0 then
            return nil;
        end
        for i = 1, count, 1 do
            local value = pValues:GetValueAt(i-1);
            table.insert(kReturn, value);
        end
    else
        return nil;
    end
    return unpack(kReturn);
end
 
Back
Top Bottom