1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

SaveUtils.lua -- Itemized Data Storage Across Mods

Discussion in 'Civ5 - SDK / LUA' started by Whys, Oct 20, 2010.

  1. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Version 8 (build: 2011.02.13.0000)

    Allows concurrent mods to share the ScriptData save slot on all Player, Plot, and Unit objects, makes it possible to save complex tables or any data type except function, userdata, and thread, and provides unlimited save slots per target object. Includes cache functionality for improved performance and shares cache across all lua states and concurrent mods when ShareData.lua is loaded.

    Test Example: (InGameUIAddin)
    Outputs an increasing integer to the lua console.
    Code:
    include( "SaveUtils" ); MY_MOD_NAME = "myMod";
    
    function test()
      local pPlayer = Players[Game.GetActivePlayer()];
      local data = load( pPlayer, "data" ) or 0;
      data = data +1;
      print( "data: "..tostring( data ) );
      save( pPlayer, "data", data );
    end
    Events.ActivePlayerTurnStart.Add( test );
    
    SaveUtils.lua
    Spoiler :

    Code:
    -- vymdt.08.2011.02.13.0000
    -- Created by: Ryan F. Mercer -Open source
    --===========================================================================
    -- SaveUtils.lua
    --===========================================================================
    --[[ Special Thanks: killmeplease, Thalassicus, Afforess.
    
    *Nothing in this file should be changed.
    
    Allows concurrent mods to share the ScriptData save slot on all Player, Plot,
    and Unit objects, makes it possible to save complex tables or any data type
    except function, userdata, and thread, and provides unlimited save slots per
    target object.  Includes cache functionality for improved performance and
    shares cache and cache-state as super-globals accessible to all lua states
    (requires ShareData.lua).
    
    To use this file, place the following line of code in your modded file's
    global scope and set your mod's unique name.
    
      include( "SaveUtils" ); MY_MOD_NAME = "MyMod";
    
    Automatically shares cache and cache-state upon Events.LoadScreenClose().
    Call share_SaveUtils() explicitly to share cache and cache-state immediately.
    Explicit call necessary when shared data operations performed from global
    scope or any point prior to Events.LoadScreenClose().  Avoid explicit call
    whenever possible for greater interoperability of concurrent mods.
    
    -----------------------------------------------------------------------------
    Basic Functionality
    -----------------------------------------------------------------------------
      
      save( pPlayer, key1, "this" ); --ONLY string and integer keys recommended.
      save( pPlayer, key2, "200" );  --loads as string.
      save( pPlayer, key3, 300 );    --loads as number.
      save( pPlayer, key4, true );   --loads as boolean.
      save( pPlayer, nil,  "that" ); --automatic integer index.
      save( pPlayer, key1, table, "otherMod" ); --optional fourth parameter.
      save( pPlot,   ...,  ...,   ... ); --same as above.
      save( pUnit,   ...,  ...,   ... ); --same as above.
    
      delete( pPlayer, key1 );             --deletes key and value from table.
      delete( pPlayer, key1, "otherMod" ); --optional third parameter.
    
      data = load( pPlayer, key1 );
      data = load( pPlayer, key1, "otherMod" ); --optional third parameter.
      data = load( pPlot,   ...,  ... ); --same as above.
      data = load( pUnit,   ...,  ... ); --same as above.
    
      data = load( pPlayer );                  --entire mod table for pPlayer.
      data = load( pPlayer, nil, "otherMod" ); --entire mod table for pPlayer.
    
      data = load( pPlayer, nil, false ); --entire save table for pPlayer.
      data = load( pPlot,   nil, false ); --entire save table for pPlot.
      data = load( pUnit,   nil, false ); --entire save table for pUnit.
    
    Invalid data types: function, userdata, thread.  Also be aware that while it
    is technically possible to use any other data type except nil for a key,
    using a table for reference, ie: load(pPlayer,table), will produce
    undesirable results.  It will appear to work normally while still in cache,
    only to then fail across saved games.  This is not a limitation of SaveUtils,
    but rather is an inherent limitation of all serialized data.  That said, the
    following will always work as expected.
    
      save(pPlayer,{"table key"},"value");
      for k,v in pairs(load(pPlayer)) do
    
    -----------------------------------------------------------------------------
    Cache Functionality
    -----------------------------------------------------------------------------
    
    Data caching can be set to one of three states per mod:
    0 = no cache, 1 = serialize on save(), 2 = serialize on sync().  Default: 1.
    
      setCacheState( 0 ); --no cache.
      setCacheState( 0, "otherMod" ); --optional second parameter.
    
      clear();          --error.
      clear( true );    --clears entire cache.
      clear( pPlayer ); --clears pPlayer cache.
    
      sync();           --error.
      sync( true );     --serializes entire cache.
      sync( pPlayer );  --serializes pPlayer cache.
    
    When using serialize on sync(), it is the modders responsibility to call
    global function sync() to serialize cache data, otherwise game data will not
    persist across saved games.  Also be aware that multiple lua states may read
    from or write to the serialized data at any time.  Lua states that act on the
    serialized data should first inspect the cache to avoid desynchronization
    (requires ShareData.lua).
    
    It is also possible to override the cache-state on any particular call to
    save() and load() by giving the desired cache-state as an optional last
    argument.  This can be particularly useful when retrieving the entire save
    table, ie: load(pPlayer, nil, false, 0), because logically mod false can not
    have a cache-state and thus otherwise uses the cache-state for MY_MOD_NAME.
    
    -----------------------------------------------------------------------------
    Share Functionality
    -----------------------------------------------------------------------------
    
    Requires ShareData.lua
    
    Shared data can be of any type, including an object or function reference.
    Data shared as super-global requires key be unique for all lua states. Data
    shared as context-global only requires key be unique for that context.  Share
    data as super-global when you want all lua states to act on that same data,
    and share data as context-global when you want to make it accessible to any
    lua state that specifies your context (mostly for read only).  The context
    should be the name of the LuaContext entry or UIAddin file name without file
    extension.
    
    It is recommended that data be shared upon Events.LoadScreenClose() to ensure
    synchronized order of operations across concurrent mods.  For example,
    SaveUtils automatically shares cache and cache-state in the following manner.
    --------
    g_cacheState, g_pCache = {}, Cache:new();
    
    function share_SaveUtils()
      g_cacheState  = share( "SaveUtils.g_cacheState",  g_cacheState  );
      g_pCache      = share( "SaveUtils.g_pCache",      g_pCache      );
    end
    Events.LoadScreenClose.Add( share_SaveUtils );
    --------
    
    Share References:
    
      data = share( key, data or default );           --super-global.
      data = share( key, data or default, context );  --context-global.
    
    Once a reference is shared, any actions on that reference by any lua state
    take immediate affect for all lua states, as expected for a reference.  Thus
    only share() is needed upon instatiation and perhaps rmvShared() on clean-up.
    
    Share Primitives:
      
      --super-global.
      old  = setShared( key, data );
      data = getShared( key );
      case = hasShared( key );
    
      --context-global.
      old  = setShared( key, data, context );
      data = getShared( key, context );
      case = hasShared( key, context );
      
      --share with automatic integer index.
      setShared( nil, data, context ); --ONLY context-globals.
    
    Primitives (number, string, boolean) are copies not references, thus while it
    is possible to share a primitive upon Events.LoadScreenClose, getShared() and
    setShared() must still be called later to keep copies syncronized.  For this
    reason it may be preferable to assign primitives to a shared table for
    reference with key.
    
    Unshare:
    
      --remove primitive or reference.
      data = rmvShared( key );           --super-global.
      data = rmvShared( key, context );  --context-global.
    
    Be aware that removing a reference from shared data can not eliminate any
    additional references created while data was shared.
    
    Other:
    
      data = getShared();                --entire table of super-globals.
      data = getShared( nil, context );  --entire table of context-globals.
      data = getShared( nil, true );     --entire table of context tables.
    
      --supress 'not shared' warnings.
      WARN_NOT_SHARED = false; include( "SaveUtils" ); MY_MOD_NAME = "MyMod";
    ]]
    --===========================================================================
    -- Save Class
    --===========================================================================
    --[[
    Allows SetScriptData() and GetScriptData() to behave as a hash-table of
    itemized data, while maintaining separation of individual mod data.
    ]]
    Save = {};
    function Save:new( o )
      o = o or {};
      if type( o ) == "string" and o == "" then o = {}; end
      if type( o ) ~= "table" then o = {o}; end
      setmetatable( o, self );
      self.__index = self;
      return o;
    end
    --===========================================================================
    --[[
    Sets the given value to the given key for the given mod.
    ]]
    function Save:set( mod, key, value )
      self[mod] = self[mod] or {};
      if key ~= nil then self[mod][key] = value;
      else table.insert( self[mod], value );
      end
      local i=0; for k,v in pairs( self[mod] ) do i=1; break; end
      if i == 0 then self[mod] = nil; end
    end
    --===========================================================================
    --[[
    Returns value for given mod and key. Returns copy of self when mod not given.
    Returns mod table when key not given.
    ]]
    function Save:get( mod, key )
      
      local function copy( object, meta ) --deep copy.
        local copied = {};
        local function sub( object )
          if type( object ) ~= "table" then return object; end
          if copied[object] then return copied[object]; end
          local new_table = {};
          copied[object] = new_table;
          for k,v in pairs( object ) do new_table[sub( k )] = sub( v ); end
          local new_meta = getmetatable( object );
          if meta == true then new_meta = sub( new_meta ); end
          return setmetatable( new_table, new_meta );
        end
        return sub( object );
      end
    
      local r = nil;
      if mod ~= nil then self[mod] = self[mod] or {}; r = self[mod];
        if key ~= nil then r = self[mod][key]; end
      else r = copy( self );
      end
      return r;
    end
    --===========================================================================
    --END Save Class
    --===========================================================================
    --===========================================================================
    -- Cache Class
    --===========================================================================
    --[[
    Manages object keys to unserialized object data.
    ]]
    Cache = {};
    function Cache:new( o )
      o = o or {};
      setmetatable( o, self );
      self.__index = self;
      return o;
    end
    --===========================================================================
    --[[
    Returns boolean state for given target.
    ]]
    function Cache:has( target )
      r = false;
      if self[target] then r = true; end
      return r;
    end
    --===========================================================================
    --[[
    Sets given Save for given target.
    ]]
    function Cache:add( target, save )
      if target ~= nil and save ~= nil then self[target] = save; end
    end
    --===========================================================================
    --[[
    Returns Save for given target.  Returns copy of self when target not given.
    ]]
    function Cache:get( target )
    
      local function copy( object, meta ) --deep copy.
        local copied = {};
        local function sub( object )
          if type( object ) ~= "table" then return object; end
          if copied[object] then return copied[object]; end
          local new_table = {};
          copied[object] = new_table;
          for k,v in pairs( object ) do new_table[sub( k )] = sub( v ); end
          local new_meta = getmetatable( object );
          if meta == true then new_meta = sub( new_meta ); end
          return setmetatable( new_table, new_meta );
        end
        return sub( object );
      end
    
      local r = self[target];
      if r == nil then r = copy( self ); end
      return r;
    end
    --===========================================================================
    --[[
    Removes and returns Save for given target.
    ]]
    function Cache:rmv( target )
      return table.remove( self, target );
    end
    --===========================================================================
    --END Cache Class
    --===========================================================================
    --===========================================================================
    --[[
    Sets cache-state for given mod to given state. Uses global MY_MOD_NAME If mod
    not given.
    ]]
    function setCacheState( state, mod )
      local t = type( state );
      if not t == "number" then
        print( "setCacheState(): Invalid first argument of type "..t
            ..", expected number." );
        return false; --error.
      end
      if mod == nil then mod = MY_MOD_NAME; end
      t = type( mod );
      if not (t == "number" or t == "string") then
        print( "setCacheState(): Invalid second argument of type "..t
            ..", expected number, or string." );
        return false; --error.
      end
      g_cacheState[mod] = state;
    end
    --===========================================================================
    --[[
    Returns cache-state for given mod.  Uses global MY_MOD_NAME If mod not given.
    ]]
    function getCacheState( mod )
      if mod == nil then mod = MY_MOD_NAME; end
      local t = type( mod );
      if not (t == "number" or t == "string") then
        print( "getCacheState(): Invalid first argument of type "..t
            ..", expected number, or string." );
        return false; --error.
      end
      return g_cacheState[mod];
    end
    --===========================================================================
    --[[
    Removes cache data for given target, or all data when target is boolean true.
    ]]
    function clear( target )
      local t = type( target );
      if not (t == "table" or target == true) then
        print( "clear(): Invalid first argument of type "..t
            ..", expected table, or boolean true." );
        return false; --error.
      end
      if WARN_NOT_SHARED ~= false and
          not hasShared( "SaveUtils.g_pCache" ) then
        print( "Warning: cache not shared." ); --warning.
      end
      if target == true then
        for k,pSave in pairs( g_pCache ) do g_pCache:rmv( k ); end
      else g_pCache:rmv( target );
      end
    end
    --===========================================================================
    --[[
    Serializes cache data for given target, or all cache data when target is
    boolean true.
    ]]
    function sync( target )
      local t = type( target );
      if not (t == "table" or target == true) then
        print( "sync(): Invalid first argument of type "..t
            ..", expected table, or boolean true." );
        return false; --error.
      end
      if WARN_NOT_SHARED ~= false and
          not hasShared( "SaveUtils.g_pCache" ) then
        print( "Warning: cache not shared." ); --warning.
      end
      if target == true then
        for k,pSave in pairs( g_pCache ) do
          k:SetScriptData(serialize( pSave ));
        end
      else
        local pSave = g_pCache:get( target );
        if pSave == nil then
          print( "sync(): Target not found." ); return false; --error.
        else target:SetScriptData(serialize( pSave ));
        end
      end
    end
    --===========================================================================
    --[[
    Saves the given value to the given key for the given target of the given mod.
    Invalid data types: function, userdata, thread.  Uses global MY_MOD_NAME If
    mod not given.  Can optionally override the cache-state for any particular
    call.  This can be particularly useful when mod is boolean false.
    ]]
    function save( target, key, value, mod, cacheState )
      local t = type( target );
      if not t == "table" then
        print( "save(): Invalid first argument of type "..t
            ..", expected table." );
        return false; --error.
      end  
      if mod == nil then mod = MY_MOD_NAME; end
      t = type( mod );
      if not (t == "number" or t == "string") then
        print( "save(): Invalid fourth argument of type "..t
            ..", expected number, or string." );
        return false; --error.
      end
      t = type( cacheState );
      if not (cacheState == nil or t == "number") then
        print( "save(): Invalid fifth argument of type "..t
            ..", expected nil, or number." );
        return false; --error.
      end
      local pSave = nil;
      local iCache = DEFAULT_CACHE_STATE;
      if cacheState ~= nil then iCache = cacheState;
      else
        if getCacheState( mod ) == nil then setCacheState( iCache, mod );
        else iCache = getCacheState( mod );
        end
      end
      pSave = load( target, nil, false, iCache );
      pSave:set( mod, key, value );
      if iCache <= 1 then target:SetScriptData(serialize( pSave )); end
      if iCache >= 1 then g_pCache:add( target, pSave ); end
    end
    --===========================================================================
    --[[
    Loads the value for the given key for the given target of the given mod.
    Returns entire mod table for the given target when key is nil.  Returns
    entire save table for the given target when mod is boolean false.  Uses
    global MY_MOD_NAME If mod not given.  Can optionally override the cache-state
    for any particular call.  This can be particularly useful when mod is boolean
    false, because logically mod false can not have a cache-state and thus
    otherwise uses default cache-state or cache-state for MY_MOD_NAME when set.
    ]]
    function load( target, key, mod, cacheState )
      local t = type( target );
      if not t == "table" then
        print( "load(): Invalid first argument of type "..t
            ..", expected table." );
        return nil; --error.
      end    
      if mod == nil then mod = MY_MOD_NAME; end
      t = type( mod );
      if not (t == "number" or t == "string" or mod == false) then
        print( "load(): Invalid third argument of type "..t
            ..", expected number, string, or boolean false." );
        return nil; --error.
      end
      if mod == false then mod = nil; end
      t = type( cacheState );
      if not (cacheState == nil or t == "number") then
        print( "load(): Invalid fourth argument of type "..t
            ..", expected nil, or number." );
        return nil; --error.
      end
      local pSave = nil;
      local iCache = DEFAULT_CACHE_STATE;
      if cacheState ~= nil then iCache = cacheState;
      else
        if mod == nil then
          if getCacheState( MY_MOD_NAME ) ~= nil then
            iCache = getCacheState( MY_MOD_NAME );
          end
        else
          if getCacheState( mod ) == nil then setCacheState( iCache, mod );
          else iCache = getCacheState( mod );
          end
        end
      end
      if iCache < 1 then pSave = Save:new(deserialize( target:GetScriptData() ));
      elseif iCache > 0 then
        if WARN_NOT_SHARED ~= false then
          local warned = false;
          if not hasShared( "SaveUtils.g_pCache" ) then
            print( "Warning: cache not shared." ); --warning.
            warned = true;
          end
          if not warned and not hasShared( "SaveUtils.g_cacheState" ) then
            print( "Warning: cache-state not shared." ); --warning.
          end
        end
        if g_pCache:has( target ) then pSave = g_pCache:get( target );
        else
          pSave = Save:new(deserialize( target:GetScriptData() ));
          g_pCache:add( target, pSave );
        end
      end
      return pSave:get( mod, key );
    end
    --===========================================================================
    --[[
    Removes both the key and value for the given target of the given mod
    with the given key. Alias for function save( target, key, nil, mod ).
    ]]
    function delete( target, key, mod )
      save( target, key, nil, mod );
    end
    --===========================================================================
    --[[
    Serializes given data and returns result string.  Invalid data types:
    function, userdata, thread.
    ]]
    function serialize( p )
      
      local r = ""; local t = type( p );
      if t == "function" or t == "userdata" or t == "thread" then
        print( "serialize(): Invalid type: "..t ); --error.
      elseif p ~= nil then
        if t ~= "table" then
          if p == nil or p == true or p == false
            or t == "number" then r = tostring( p );
          elseif t == "string" then
            if p:lower() == "true" or p:lower() == "false"
                or tonumber( p ) ~= nil then r = '"'..p..'"';
            else r = p;
            end
          end
          r = r:gsub( "{", "\[LCB\]" );
          r = r:gsub( "}", "\[RCB\]" );
          r = r:gsub( "=", "\[EQL\]" );
          r = r:gsub( ",", "\[COM\]" );
        else
          r = "{"; local b = false;
          for k,v in pairs( p ) do
            if b then r = r..","; end
            r = r..serialize( k ).."="..serialize( v );
            b = true;
          end
          r = r.."}"
        end
      end
      return r;
    end
    --===========================================================================
    --[[
    Deserializes given string and returns result data.
    ]]
    function deserialize( str )
    
      local findToken = function( str, int )
        if int == nil then int = 1; end
        local s, e, c = str:find( "({)" ,int);
        if s == int then --table.
          local len = str:len();
          local i = 1; --open brace.
          while i > 0 and s ~= nil and e <= len do --find close.
            s, e, c = str:find( "([{}])" ,e+1);
            if     c == "{" then i = i+1;
            elseif c == "}" then i = i-1;
            end
          end
          if i == 0 then c = str:sub(int,e);
          else print( "deserialize(): Malformed table." ); --error.
          end
        else s, e, c = str:find( "([^=,]*)" ,int); --primitive.
        end
        return s, e, c, str:sub( e+1, e+1 );
      end
    
      local r = nil; local s, c, d;
      if str ~= nil then
        local sT, eT, cT = str:find( "{(.*)}" );
        if sT == 1 then
          r = {}; local len = cT:len(); local e = 1;
          if cT ~= "" then
            repeat
              local t1, t2; local more = false;
              s, e, c, d = findToken( cT, e );
              if s ~= nil then t1 = deserialize( c ); end
              if d == "=" then --key.
                s, e, c, d = findToken( cT, e+2 );
                if s ~= nil then t2 = deserialize( c ); end
              end
              if d == "," then e = e+2; more = true; end --one more.
              if t2 ~= nil then r[t1] = t2;
              else table.insert( r, t1 );
              end
            until e >= len and not more;
          end
        elseif tonumber(str) ~= nil then r = tonumber(str);
        elseif str == "true"  then r = true;
        elseif str == "false" then r = false;
        else
          s, e, c = str:find( '"(.*)"' );
          if s == 1 and e == str:len() then
            if c == "true" or c == "false" or tonumber( c ) ~= nil then
              str = c;
            end
          end
          r = str;
          r = r:gsub( "%[LCB%]", "{" );
          r = r:gsub( "%[RCB%]", "}" );
          r = r:gsub( "%[EQL%]", "=" );
          r = r:gsub( "%[COM%]", "," );
        end
      end
      return r;
    end
    --===========================================================================
    --[[
    Makes given data accessible to all lua states.  Intended for use upon data
    instatiation, ie: local data = share( key, data or default, context );
    Does not "set" shared data unless not already set, otherwise just returns
    already set data.  Requires ShareData.lua.
    ]]
    function share( key, data, context )
      local t = type( key );
      if not (t == "number" or (t == "string" and key ~= "")) then
        print( "share(): Invalid first argument of type "..t
            ..", expected number, or unempty-string." );
        return data; --error.
      end
      local t = type( context );
      if not (t == "nil" or (t == "string" and context ~= "")) then
        print( "share(): Invalid third argument of type "..t
            ..", expected nil, or unempty-string." );
        return data; --error.
      end
      local r, t = data, {}; LuaEvents.HasShared( key, t, context );
      if not t[1] then LuaEvents.SetShared( key, data, context );
      else t = {}; LuaEvents.GetShared( key, t, context ); r = t[key];
      end return r;
    end
    --===========================================================================
    --[[
    Returns boolean case of shared context-global for key or boolean case of
    shared super-global for key when context not given.  Requires ShareData.lua.
    ]]
    function hasShared( key, context )
      local r = {}; LuaEvents.HasShared( key, r, context ); return r[1] or false;
    end
    --===========================================================================
    --[[
    Sets given data to given key as shared context-global or shared super-global
    when context not given.  Sets given data to automatic integer-key when key
    not given.  Must give key when context not given.  Returns previous data for
    given key.  Requires ShareData.lua.
    ]]
    function setShared( key, data, context )
      if key == nil and context == nil then
        print( "setShared(): Invalid first and third argument combination of "
            .."types "..type( key ).." and "..type( context )
            ..", expected any type except nil for key when context nil." );
        return nil; --error.
      end
      local r = {}; LuaEvents.SetShared( key, data, context, r );
      if key ~= nil then r = r[key]; else r = nil; end return r;
    end
    --===========================================================================
    --[[
    Returns shared context-global for key or shared super-global for key when
    context not given.  Returns entire table of context-globals for given context
    when key not given.  Returns entire table of super-globals when both key and
    context not given.  Returns entire table of context tables when key not given
    and given context boolean true.  Requires ShareData.lua.
    ]]
    function getShared( key, context )
      local r = {}; LuaEvents.GetShared( key, r, context );
      if key ~= nil then r = r[key]; end return r;
    end
    --===========================================================================
    --[[
    Removes and returns shared context-global for key or shared super-global for
    key when context not given.  Alias for function setShared(key, nil, context).
    Requires ShareData.lua.
    ]]
    function rmvShared( key, context )
      return setShared( key, nil, context );
    end
    --===========================================================================
    --[[
    Global Variables.
    
    Cache and cache-state automatically shared as super-globals upon
    Events.LoadScreenClose().
    ]]
    MY_MOD_NAME, DEFAULT_CACHE_STATE = nil, 1;
    g_cacheState, g_pCache = {}, Cache:new();
    --===========================================================================
    --[[
    Shares cache and cache-state as super-globals.
    ]]
    function share_SaveUtils()
      g_cacheState  = share( "SaveUtils.g_cacheState",  g_cacheState  );
      g_pCache      = share( "SaveUtils.g_pCache",      g_pCache      );
    end
    Events.LoadScreenClose.Add( share_SaveUtils );
    --===========================================================================
    --END SaveUtils.lua
    --===========================================================================
    -- Created by: Ryan F. Mercer -Open source
    
     
  2. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Version 7: (deprecated)
    Spoiler :

    Version 7 (build: 2010.11.30.0000)

    Allows concurrent mods to share the ScriptData save slot on all Player, Plot, and Unit objects, makes it possible to save complex tables or any data type except function, userdata, and thread, and provides unlimited save slots per target object. Includes cache functionality for improved performance and shares cache across all lua states and concurrent mods when ShareData.lua is loaded.

    SaveUtils.lua
    Spoiler :
    Code:
    -- vymdt.07.2010.11.30.0000
    -- Created by: Ryan F. Mercer -Open source
    --===========================================================================
    -- SaveUtils.lua
    --===========================================================================
    --[[ Special Thanks: killmeplease, Thalassicus, Afforess.
    
    *Nothing in this file should be changed.
    
    Allows concurrent mods to share the ScriptData save slot on all Player, Plot,
    and Unit objects, makes it possible to save complex tables or any data type
    except function, userdata, and thread, and provides unlimited save slots per
    target object.  Includes cache functionality for improved performance and
    shares cache and cache-state as super-globals accessible to all lua states
    (requires ShareData.lua).
    
    To use this file, place the following line of code in your modded file's
    global scope and set your mod's unique name.
    
      include( "SaveUtils" ); MY_MOD_NAME = "MyMod";
    
    Automatically shares cache and cache-state upon Events.LoadScreenClose().
    Call share_SaveUtils() explicitly to share cache and cache-state immediately.
    Explicit call necessary when shared data operations performed from global
    scope or any point prior to Events.LoadScreenClose().  Avoid explicit call
    whenever possible for greater interoperability of concurrent mods.
    
    -----------------------------------------------------------------------------
    Basic Functionality
    -----------------------------------------------------------------------------
      
      save( pPlayer, key1, "this" ); --ONLY string and integer keys recommended.
      save( pPlayer, key2, "200" );  --loads as string.
      save( pPlayer, key3, 300 );    --loads as number.
      save( pPlayer, key4, true );   --loads as boolean.
      save( pPlayer, nil,  "that" ); --automatic integer index.
      save( pPlayer, key1, table, "otherMod" ); --optional fourth parameter.
      save( pPlot,   ...,  ...,   ... ); --same as above.
      save( pUnit,   ...,  ...,   ... ); --same as above.
    
      delete( pPlayer, key1 );             --deletes key and value from table.
      delete( pPlayer, key1, "otherMod" ); --optional third parameter.
    
      data = load( pPlayer, key1 );
      data = load( pPlayer, key1, "otherMod" ); --optional third parameter.
      data = load( pPlot,   ...,  ... ); --same as above.
      data = load( pUnit,   ...,  ... ); --same as above.
    
      data = load( pPlayer );                  --entire mod table for pPlayer.
      data = load( pPlayer, nil, "otherMod" ); --entire mod table for pPlayer.
    
      data = load( pPlayer, nil, false ); --entire save table for pPlayer.
      data = load( pPlot,   nil, false ); --entire save table for pPlot.
      data = load( pUnit,   nil, false ); --entire save table for pUnit.
    
    Invalid data types: function, userdata, thread.  Also be aware that while it
    is technically possible to use any other data type except nil for a key,
    using a table for reference, ie: load(pPlayer,table), will produce
    undesirable results.  It will appear to work normally while still in cache,
    only to then fail across saved games.  This is not a limitation of SaveUtils,
    but rather is an inherent limitation of all serialized data.  That said, the
    following will always work as expected.
    
      save(pPlayer,{"table key"},"value");
      for k,v in pairs(load(pPlayer)) do
    
    -----------------------------------------------------------------------------
    Cache Functionality
    -----------------------------------------------------------------------------
    
    Data caching can be set to one of three states per mod:
    0 = no cache, 1 = serialize on save(), 2 = serialize on sync().  Default: 1.
    
      setCacheState( 0 ); --no cache.
      setCacheState( 0, "otherMod" ); --optional second parameter.
    
      clear();          --error.
      clear( true );    --clears entire cache.
      clear( pPlayer ); --clears pPlayer cache.
    
      sync();           --error.
      sync( true );     --serializes entire cache.
      sync( pPlayer );  --serializes pPlayer cache.
    
    When using serialize on sync(), it is the modders responsibility to call
    global function sync() to serialize cache data, otherwise game data will not
    persist across saved games.  Also be aware that multiple lua states may read
    from or write to the serialized data at any time.  Lua states that act on the
    serialized data should first inspect the cache to avoid desynchronization
    (requires ShareData.lua).
    
    It is also possible to override the cache-state on any particular call to
    save() and load() by giving the desired cache-state as an optional last
    argument.  This can be particularly useful when retrieving the entire save
    table, ie: load(pPlayer, nil, false, 0), because logically mod false can not
    have a cache-state and thus otherwise uses the cache-state for MY_MOD_NAME.
    
    -----------------------------------------------------------------------------
    Share Functionality
    -----------------------------------------------------------------------------
    
    Requires ShareData.lua
    
    Shared data can be of any type, including an object or function reference.
    Data shared as super-global requires key be unique for all lua states. Data
    shared as context-global only requires key be unique for that context.  Share
    data as super-global when you want all lua states to act on that same data,
    and share data as context-global when you want to make it accessible to any
    lua state that specifies your context (mostly for read only).  The context
    should be the name of the LuaContext entry or UIAddin file name without file
    extension.
    
    It is recommended that data be shared upon Events.LoadScreenClose() to ensure
    synchronized order of operations across concurrent mods.  For example,
    SaveUtils automatically shares cache and cache-state in the following manner.
    --------
    g_cacheState, g_pCache = {}, Cache:new();
    
    function share_SaveUtils()
      g_cacheState  = share( "SaveUtils.g_cacheState",  g_cacheState  );
      g_pCache      = share( "SaveUtils.g_pCache",      g_pCache      );
    end
    Events.LoadScreenClose.Add( share_SaveUtils );
    --------
    
    Share References:
    
      data = share( key, data or default );           --super-global.
      data = share( key, data or default, context );  --context-global.
    
    Once a reference is shared, any actions on that reference by any lua state
    take immediate affect for all lua states, as expected for a reference.  Thus
    only share() is needed upon instatiation and perhaps rmvShared() on clean-up.
    
    Share Primitives:
      
      --super-global.
      old  = setShared( key, data );
      data = getShared( key );
      case = hasShared( key );
    
      --context-global.
      old  = setShared( key, data, context );
      data = getShared( key, context );
      case = hasShared( key, context );
      
      --share with automatic integer index.
      setShared( nil, data, context ); --ONLY context-globals.
    
    Primitives (number, string, boolean) are copies not references, thus while it
    is possible to share a primitive upon Events.LoadScreenClose, getShared() and
    setShared() must still be called later to keep copies syncronized.  For this
    reason it may be preferable to assign primitives to a shared table for
    reference with key.
    
    Unshare:
    
      --remove primitive or reference.
      data = rmvShared( key );           --super-global.
      data = rmvShared( key, context );  --context-global.
    
    Be aware that removing a reference from shared data can not eliminate any
    additional references created while data was shared.
    
    Other:
    
      data = getShared();                --entire table of super-globals.
      data = getShared( nil, context );  --entire table of context-globals.
      data = getShared( nil, true );     --entire table of context tables.
    
      --supress 'not shared' warnings.
      WARN_NOT_SHARED = false; include( "SaveUtils" ); MY_MOD_NAME = "MyMod";
    ]]
    --===========================================================================
    -- Save Class
    --===========================================================================
    --[[
    Allows SetScriptData() and GetScriptData() to behave as a hash-table of
    itemized data, while maintaining separation of individual mod data.
    ]]
    Save = {};
    function Save:new( o )
      o = o or {};
      if type( o ) == "string" and o == "" then o = {}; end
      if type( o ) ~= "table" then o = {o}; end
      setmetatable( o, self );
      self.__index = self;
    
      --deep assignment of keyed values.
      --required for duplication of table data assigned by key.
      local sub; --allows recursive call to sub-routine.
      sub = function( o )
        for k,v in pairs( o ) do self[k] = v;
          if type( v ) == "table" then sub( v ); end
        end
      end
    
      sub( o );
      return o;
    end
    --===========================================================================
    --[[
    Sets the given value to the given key for the given mod.
    ]]
    function Save:set( mod, key, value )
      self[mod] = self[mod] or {};
      if key ~= nil then
        self[mod][key] = value;
      else
        table.insert( self[mod], value );
      end
      local i = 0;
      for k,v in pairs( self[mod] ) do
        i = 1; break;
      end
      if i == 0 then self[mod] = nil; end
    end
    --===========================================================================
    --[[
    Returns value for given mod and key. Returns copy of self when mod not given.
    Returns mod table when key not given.
    ]]
    function Save:get( mod, key )
      
      local copy = function( object ) --deep copy.
        local lookup_table = {};
        local function _copy( object )
          if type( object ) ~= "table" then
            return object;
          elseif lookup_table[object] then
            return lookup_table[object];
          end
          local new_table = {};
          lookup_table[object] = new_table;
          for index,value in pairs( object ) do
            new_table[_copy( index )] = _copy( value );
          end
          return setmetatable( new_table, getmetatable( object ) );
        end
        return _copy( object );
      end
      
      local r = nil;
      if mod ~= nil then self[mod] = self[mod] or {}; r = self[mod];
        if key ~= nil then r = self[mod][key]; end
      else r = copy( self );
      end
      return r;
    end
    --===========================================================================
    --END Save Class
    --===========================================================================
    --===========================================================================
    -- Cache Class
    --===========================================================================
    --[[
    Manages object keys to unserialized object data.
    ]]
    Cache = {};
    function Cache:new( o )
      o = o or {};
      setmetatable( o, self );
      self.__index = self;
    
      --deep assignment of keyed values.
      --required for duplication of table data assigned by key.
      local sub; --allows recursive call to sub-routine.
      sub = function( o )
        for k,v in pairs( o ) do self[k] = v;
          if type( v ) == "table" then sub( v ); end
        end
      end
    
      sub( o );
      return o;
    end
    --===========================================================================
    --[[
    Returns boolean state for given target.
    ]]
    function Cache:has( target )
      r = false;
      if self[target] then r = true; end
      return r;
    end
    --===========================================================================
    --[[
    Sets given Save for given target.
    ]]
    function Cache:add( target, save )
      if target ~= nil and save ~= nil then self[target] = save; end
    end
    --===========================================================================
    --[[
    Returns Save for given target.  Returns self when target not given.
    ]]
    function Cache:get( target )
      local r = self;
      if self:has( target ) then r = self[target]; end
      return r;
    end
    --===========================================================================
    --[[
    Removes and returns Save for given target.
    ]]
    function Cache:rmv( target )
      return table.remove( self, target );
    end
    --===========================================================================
    --END Cache Class
    --===========================================================================
    --===========================================================================
    --[[
    Sets cache-state for given mod to given state. Uses global MY_MOD_NAME If mod
    not given.
    ]]
    function setCacheState( state, mod )
      local t = type( state );
      if not t == "number" then
        print( "setCacheState(): Invalid first argument of type "..t
            ..", expected number." );
        return false; --error.
      end
      if mod == nil then mod = MY_MOD_NAME; end
      t = type( mod );
      if not (t == "number" or t == "string") then
        print( "setCacheState(): Invalid second argument of type "..t
            ..", expected number, or string." );
        return false; --error.
      end
      g_cacheState[mod] = state;
    end
    --===========================================================================
    --[[
    Returns cache-state for given mod.  Uses global MY_MOD_NAME If mod not given.
    ]]
    function getCacheState( mod )
      if mod == nil then mod = MY_MOD_NAME; end
      local t = type( mod );
      if not (t == "number" or t == "string") then
        print( "getCacheState(): Invalid first argument of type "..t
            ..", expected number, or string." );
        return false; --error.
      end
      return g_cacheState[mod];
    end
    --===========================================================================
    --[[
    Removes cache data for given target, or all data when target is boolean true.
    ]]
    function clear( target )
      local t = type( target );
      if not (t == "table" or target == true) then
        print( "clear(): Invalid first argument of type "..t
            ..", expected table, or boolean true." );
        return false; --error.
      end
      if WARN_NOT_SHARED ~= false and
          not hasShared( "SaveUtils.g_pCache" ) then
        print( "Warning: cache not shared." ); --warning.
      end
      if target == true then
        for k,pSave in pairs( g_pCache ) do g_pCache:rmv( k ); end
      else g_pCache:rmv( target );
      end
    end
    --===========================================================================
    --[[
    Serializes cache data for given target, or all cache data when target is
    boolean true.
    ]]
    function sync( target )
      local t = type( target );
      if not (t == "table" or target == true) then
        print( "sync(): Invalid first argument of type "..t
            ..", expected table, or boolean true." );
        return false; --error.
      end
      if WARN_NOT_SHARED ~= false and
          not hasShared( "SaveUtils.g_pCache" ) then
        print( "Warning: cache not shared." ); --warning.
      end
      if target == true then
        for k,pSave in pairs( g_pCache ) do
          k:SetScriptData(serialize( pSave ));
        end
      else
        local pSave = g_pCache:get( target );
        if pSave == nil then
          print( "sync(): Target not found." ); return false; --error.
        else target:SetScriptData(serialize( pSave ));
        end
      end
    end
    --===========================================================================
    --[[
    Saves the given value to the given key for the given target of the given mod.
    Invalid data types: function, userdata, thread.  Uses global MY_MOD_NAME If
    mod not given.  Can optionally override the cache-state for any particular
    call.  This can be particularly useful when mod is boolean false.
    ]]
    function save( target, key, value, mod, cacheState )
      local t = type( target );
      if not t == "table" then
        print( "save(): Invalid first argument of type "..t
            ..", expected table." );
        return false; --error.
      end  
      if mod == nil then mod = MY_MOD_NAME; end
      t = type( mod );
      if not (t == "number" or t == "string") then
        print( "save(): Invalid fourth argument of type "..t
            ..", expected number, or string." );
        return false; --error.
      end
      t = type( cacheState );
      if not (cacheState == nil or t == "number") then
        print( "save(): Invalid fifth argument of type "..t
            ..", expected nil, or number." );
        return false; --error.
      end
      local pSave = nil;
      local iCache = DEFAULT_CACHE_STATE;
      if cacheState ~= nil then iCache = cacheState;
      else
        if getCacheState( mod ) == nil then setCacheState( iCache, mod );
        else iCache = getCacheState( mod );
        end
      end
      pSave = load( target, nil, false, iCache );
      pSave:set( mod, key, value );
      if iCache <= 1 then target:SetScriptData(serialize( pSave )); end
      if iCache >= 1 then g_pCache:add( target, pSave ); end
    end
    --===========================================================================
    --[[
    Loads the value for the given key for the given target of the given mod.
    Returns entire mod table for the given target when key is nil.  Returns
    entire save table for the given target when mod is boolean false.  Uses
    global MY_MOD_NAME If mod not given.  Can optionally override the cache-state
    for any particular call.  This can be particularly useful when mod is boolean
    false, because logically mod false can not have a cache-state and thus
    otherwise uses default cache-state or cache-state for MY_MOD_NAME when set.
    ]]
    function load( target, key, mod, cacheState )
      local t = type( target );
      if not t == "table" then
        print( "load(): Invalid first argument of type "..t
            ..", expected table." );
        return nil; --error.
      end    
      if mod == nil then mod = MY_MOD_NAME; end
      t = type( mod );
      if not (t == "number" or t == "string" or mod == false) then
        print( "load(): Invalid third argument of type "..t
            ..", expected number, string, or boolean false." );
        return nil; --error.
      end
      if mod == false then mod = nil; end
      t = type( cacheState );
      if not (cacheState == nil or t == "number") then
        print( "load(): Invalid fourth argument of type "..t
            ..", expected nil, or number." );
        return nil; --error.
      end
      local pSave = nil;
      local iCache = DEFAULT_CACHE_STATE;
      if cacheState ~= nil then iCache = cacheState;
      else
        if mod == nil then
          if getCacheState( MY_MOD_NAME ) ~= nil then
            iCache = getCacheState( MY_MOD_NAME );
          end
        else
          if getCacheState( mod ) == nil then setCacheState( iCache, mod );
          else iCache = getCacheState( mod );
          end
        end
      end
      if iCache < 1 then pSave = Save:new(deserialize( target:GetScriptData() ));
      elseif iCache > 0 then
        if WARN_NOT_SHARED ~= false then
          local warned = false;
          if not hasShared( "SaveUtils.g_pCache" ) then
            print( "Warning: cache not shared." ); --warning.
            warned = true;
          end
          if not warned and not hasShared( "SaveUtils.g_cacheState" ) then
            print( "Warning: cache-state not shared." ); --warning.
          end
        end
        if g_pCache:has( target ) then pSave = g_pCache:get( target );
        else
          pSave = Save:new(deserialize( target:GetScriptData() ));
          g_pCache:add( target, pSave );
        end
      end
      return pSave:get( mod, key );
    end
    --===========================================================================
    --[[
    Removes both the key and value for the given target of the given mod
    with the given key. Alias for function save( target, key, nil, mod ).
    ]]
    function delete( target, key, mod )
      save( target, key, nil, mod );
    end
    --===========================================================================
    --[[
    Serializes given data and returns result string.  Invalid data types:
    function, userdata, thread.
    ]]
    function serialize( p )
      
      local r = ""; local t = type( p );
      if t == "function" or t == "userdata" or t == "thread" then
        print( "serialize(): Invalid type: "..t ); --error.
      elseif p ~= nil then
        if t ~= "table" then
          if p == nil or p == true or p == false
            or t == "number" then r = tostring( p );
          elseif t == "string" then
            if p:lower() == "true" or p:lower() == "false"
                or tonumber( p ) ~= nil then r = '"'..p..'"';
            else r = p;
            end
          end
          r = r:gsub( "{", "\[LCB\]" );
          r = r:gsub( "}", "\[RCB\]" );
          r = r:gsub( "=", "\[EQL\]" );
          r = r:gsub( ",", "\[COM\]" );
        else
          r = "{"; local b = false;
          for k,v in pairs( p ) do
            if b then r = r..","; end
            r = r..serialize( k ).."="..serialize( v );
            b = true;
          end
          r = r.."}"
        end
      end
      return r;
    end
    --===========================================================================
    --[[
    Deserializes given string and returns result data.
    ]]
    function deserialize( str )
    
      local findToken = function( str, int )
        if int == nil then int = 1; end
        local s, e, c = str:find( "({)" ,int);
        if s == int then --table.
          local len = str:len();
          local i = 1; --open brace.
          while i > 0 and s ~= nil and e <= len do --find close.
            s, e, c = str:find( "([{}])" ,e+1);
            if     c == "{" then i = i+1;
            elseif c == "}" then i = i-1;
            end
          end
          if i == 0 then c = str:sub(int,e);
          else print( "deserialize(): Malformed table." ); --error.
          end
        else s, e, c = str:find( "([^=,]*)" ,int); --primitive.
        end
        return s, e, c, str:sub( e+1, e+1 );
      end
    
      local r = nil; local s, c, d;
      if str ~= nil then
        local sT, eT, cT = str:find( "{(.*)}" );
        if sT == 1 then
          r = {}; local len = cT:len(); local e = 1;
          if cT ~= "" then
            repeat
              local t1, t2; local more = false;
              s, e, c, d = findToken( cT, e );
              if s ~= nil then t1 = deserialize( c ); end
              if d == "=" then --key.
                s, e, c, d = findToken( cT, e+2 );
                if s ~= nil then t2 = deserialize( c ); end
              end
              if d == "," then e = e+2; more = true; end --one more.
              if t2 ~= nil then r[t1] = t2;
              else table.insert( r, t1 );
              end
            until e >= len and not more;
          end
        elseif tonumber(str) ~= nil then r = tonumber(str);
        elseif str == "true"  then r = true;
        elseif str == "false" then r = false;
        else
          s, e, c = str:find( '"(.*)"' );
          if s == 1 and e == str:len() then
            if c == "true" or c == "false" or tonumber( c ) ~= nil then
              str = c;
            end
          end
          r = str;
          r = r:gsub( "%[LCB%]", "{" );
          r = r:gsub( "%[RCB%]", "}" );
          r = r:gsub( "%[EQL%]", "=" );
          r = r:gsub( "%[COM%]", "," );
        end
      end
      return r;
    end
    --===========================================================================
    --[[
    Makes given data accessible to all lua states.  Intended for use upon data
    instatiation, ie: local data = share( key, data or default, context );
    Does not "set" shared data unless not already set, otherwise just returns
    already set data.  Requires ShareData.lua.
    ]]
    function share( key, data, context )
      local t = type( key );
      if not (t == "number" or (t == "string" and key ~= "")) then
        print( "share(): Invalid first argument of type "..t
            ..", expected number, or unempty-string." );
        return data; --error.
      end
      local t = type( context );
      if not (t == "nil" or (t == "string" and context ~= "")) then
        print( "share(): Invalid third argument of type "..t
            ..", expected nil, or unempty-string." );
        return data; --error.
      end
      local r, t = data, {}; LuaEvents.HasShared( key, t, context );
      if not t[1] then LuaEvents.SetShared( key, data, context );
      else t = {}; LuaEvents.GetShared( key, t, context ); r = t[key];
      end return r;
    end
    --===========================================================================
    --[[
    Returns boolean case of shared context-global for key or boolean case of
    shared super-global for key when context not given.  Requires ShareData.lua.
    ]]
    function hasShared( key, context )
      local r = {}; LuaEvents.HasShared( key, r, context ); return r[1] or false;
    end
    --===========================================================================
    --[[
    Sets given data to given key as shared context-global or shared super-global
    when context not given.  Sets given data to automatic integer-key when key
    not given.  Must give key when context not given.  Returns previous data for
    given key.  Requires ShareData.lua.
    ]]
    function setShared( key, data, context )
      if key == nil and context == nil then
        print( "setShared(): Invalid first and third argument combination of "
            .."types "..type( key ).." and "..type( context )
            ..", expected any type except nil for key when context nil." );
        return nil; --error.
      end
      local r = {}; LuaEvents.SetShared( key, data, context, r );
      if key ~= nil then r = r[key]; else r = nil; end return r;
    end
    --===========================================================================
    --[[
    Returns shared context-global for key or shared super-global for key when
    context not given.  Returns entire table of context-globals for given context
    when key not given.  Returns entire table of super-globals when both key and
    context not given.  Returns entire table of context tables when key not given
    and given context boolean true.  Requires ShareData.lua.
    ]]
    function getShared( key, context )
      local r = {}; LuaEvents.GetShared( key, r, context );
      if key ~= nil then r = r[key]; end return r;
    end
    --===========================================================================
    --[[
    Removes and returns shared context-global for key or shared super-global for
    key when context not given.  Alias for function setShared(key, nil, context).
    Requires ShareData.lua.
    ]]
    function rmvShared( key, context )
      return setShared( key, nil, context );
    end
    --===========================================================================
    --[[
    Global Variables.
    
    Cache and cache-state automatically shared as super-globals upon
    Events.LoadScreenClose().
    ]]
    MY_MOD_NAME, DEFAULT_CACHE_STATE = nil, 1;
    g_cacheState, g_pCache = {}, Cache:new();
    --===========================================================================
    --[[
    Shares cache and cache-state as super-globals.
    ]]
    function share_SaveUtils()
      g_cacheState  = share( "SaveUtils.g_cacheState",  g_cacheState  );
      g_pCache      = share( "SaveUtils.g_pCache",      g_pCache      );
    end
    Events.LoadScreenClose.Add( share_SaveUtils );
    --===========================================================================
    --END SaveUtils.lua
    --===========================================================================
    -- Created by: Ryan F. Mercer -Open source
    

     
  3. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Version 6: (deprecated)
    Spoiler :
    Version 6 (build: 2010.11.26.0000)

    Allows concurrent mods to share the ScriptData save slot on all Player, Plot, and Unit objects, makes it possible to save complex tables or any data type except function, userdata, and thread, and provides unlimited save slots per target object. Includes cache functionality for improved performance and shares cache across all lua states and concurrent mods when ShareData.lua is loaded.

    SaveUtils.lua
    Spoiler :
    Code:
    -- vymdt.06.2010.11.26.0000
    -- Created by: Ryan F. Mercer -Open source
    --===========================================================================
    -- SaveUtils.lua
    --===========================================================================
    --[[ Special Thanks: killmeplease, Thalassicus, Afforess.
    
    *Nothing in this file should be changed.
    
    Allows concurrent mods to share the ScriptData save slot on all Player, Plot,
    and Unit objects, makes it possible to save complex tables or any data type
    except function, userdata, and thread, and provides unlimited save slots per
    target object.  Includes cache functionality for improved performance and
    shares cache and cache state as super-globals accessible to all lua states
    (requires ShareData.lua).
    
    To use this file, place the following line of code in your modded file's
    global scope and set your mod's unique name.
    
      include( "SaveUtils" ); MY_MOD_NAME = "myMod";
    
    -----------------------------------------------------------------------------
    Basic Functionality
    -----------------------------------------------------------------------------
      
      save( pPlayer, key1, "this" ); --ONLY string and integer keys recommended.
      save( pPlayer, key2, "200" );  --loads as string.
      save( pPlayer, key3, 300 );    --loads as number.
      save( pPlayer, key4, true );   --loads as boolean.
      save( pPlayer, nil,  "that" ); --automatic integer index.
      save( pPlayer, key1, table, "otherMod" ); --optional fourth parameter.
      save( pPlot,   ...,  ...,   ... ); --same as above.
      save( pUnit,   ...,  ...,   ... ); --same as above.
    
      delete( pPlayer, key1 );             --deletes key and value from table.
      delete( pPlayer, key1, "otherMod" ); --optional third parameter.
    
      data = load( pPlayer, key1 );
      data = load( pPlayer, key1, "otherMod" ); --optional third parameter.
      data = load( pPlot,   ...,  ... ); --same as above.
      data = load( pUnit,   ...,  ... ); --same as above.
    
      data = load( pPlayer );                  --entire mod table for pPlayer.
      data = load( pPlayer, nil, "otherMod" ); --entire mod table for pPlayer.
    
      data = load( pPlayer, nil, false ); --entire save table for pPlayer.
      data = load( pPlot,   nil, false ); --entire save table for pPlot.
      data = load( pUnit,   nil, false ); --entire save table for pUnit.
    
    Invalid data types: function, userdata, thread.  Also be aware that while it
    is technically possible to use any other data type except nil for a key,
    using a table for reference, ie: load(pPlayer,table), will produce
    undesirable results.  It will appear to work normally while still in cache,
    only to then fail across saved games.  This is not a limitation of SaveUtils,
    but rather is an inherent limitation of all serialized data.  That said, the
    following will always work as expected.
    
      save(pPlayer,{"table key"},"value");
      for k,v in pairs(load(pPlayer)) do
    
    -----------------------------------------------------------------------------
    Cache Functionality
    -----------------------------------------------------------------------------
    
    Data caching can be set to one of three states per mod:
    0 = no cache, 1 = serialize on save(), 2 = serialize on sync().  Default: 1.
    
      setCacheState( 0 ); --no cache.
      setCacheState( 0, "otherMod" ); --optional second parameter.
    
      clear();          --error.
      clear( true );    --clears entire cache.
      clear( pPlayer ); --clears pPlayer cache.
    
      sync();           --error.
      sync( true );     --serializes entire cache.
      sync( pPlayer );  --serializes pPlayer cache.
    
    When using serialize on sync(), it is the modders responsibility to call
    global function sync() to serialize cache data, otherwise game data will not
    persist across saved games.  Also be aware that multiple lua states may read
    from or write to the serialized data at any time.  Lua states that act on the
    serialized data should first inspect the cache to avoid desynchronization
    (requires ShareData.lua).
    
    It is also possible to override the cache state on any particular call to
    save() and load() by giving the desired cache state as an optional last
    argument.  This can be particularly useful when retrieving the entire save
    table, ie: load(pPlayer, nil, false, 0), because logically mod false can not
    have a cache state and thus otherwise uses the cache state for MY_MOD_NAME.
    
    -----------------------------------------------------------------------------
    Share Functionality
    -----------------------------------------------------------------------------
    
    Requires ShareData.lua
    
    Shared data can be of any type, including an object or function reference.
    Data shared as super-global requires key be unique for all lua states. Data
    shared as context-global only requires key be unique for that context.  Share
    data as super-global when you want all lua states to act on that same data,
    and share data as context-global when you want to make it accessible to any
    lua state that specifies your context (mostly for read only).  The context
    should be the name of the LuaContext entry or UIAddin file name without file
    extension.
    
    It is recommended that data be shared upon Events.LoadScreenClose.  This will
    ensure proper execution order of operations across concurrent mods.  For
    example, SaveUtils shares its cacheState and pCache globals in the following
    manner.
    
    --------
    cacheState  = nil; 
    pCache      = nil;
    
    function shareData()
      cacheState  = share( "SaveUtils.cacheState",  cacheState or {} );
      pCache      = share( "SaveUtils.pCache",      pCache or Cache:new() );
    end
    Events.LoadScreenClose.Add( shareData );
    --------
    
    Share References:
    
      --share upon Events.LoadScreenClose.
      data = share( key, data or default );           --super-global.
      data = share( key, data or default, context );  --context-global.
    
    Once a reference is shared, any actions on that reference by any lua state
    take immediate affect for all lua states, as expected for a reference.  Thus
    only share() is needed upon instatiation and perhaps rmvShared() on clean-up.
    
    Share Primitives:
      
      --super-global.
      old  = setShared( key, data );
      data = getShared( key );
      case = hasShared( key );
    
      --context-global.
      old  = setShared( key, data, context );
      data = getShared( key, context );
      case = hasShared( key, context );
      
      --share with automatic integer index.
      setShared( nil, data, context ); --ONLY context-globals.
    
    Primitives (number, string, boolean) are copies not references, thus while it
    is possible to share a primitive upon Events.LoadScreenClose, getShared() and
    setShared() must still be called later to keep copies syncronized.  For this
    reason it may be preferable to assign primitives to a shared table for
    reference with key.
    
    Unshare:
    
      --remove primitive or reference.
      data = rmvShared( key );           --super-global.
      data = rmvShared( key, context );  --context-global.
    
    Be aware that removing a reference from shared data can not eliminate any
    additional references created while data was shared.
    
    Other:
    
      data = getShared();                --entire table of super-globals.
      data = getShared( nil, context );  --entire table of context-globals.
      data = getShared( nil, true );     --entire table of context tables.
    ]]
    --===========================================================================
    --[[
    Global Variables.
    ]]
    MY_MOD_NAME, DEFAULT_CACHE_STATE = nil, 1;
    --shared as super-globals on Events.LoadScreenClose().
    cacheState  = nil; 
    pCache      = nil;
    --===========================================================================
    --[[
    Shares cache and cache state as super-globals.
    ]]
    function shareData()
      cacheState  = share( "SaveUtils.cacheState",  cacheState or {} );
      pCache      = share( "SaveUtils.pCache",      pCache or Cache:new() );
    end
    Events.LoadScreenClose.Add( shareData );
    --===========================================================================
    --[[
    Makes given data accessible to all lua states.  Intended for use upon data
    instatiation, ie: local data = share( key, data or default, context );
    Does not "set" shared data unless not already set, otherwise just returns
    already set data.  Requires ShareData.lua.
    ]]
    function share( key, data, context )
      local t = type( key );
      if not (t == "number" or (t == "string" and key ~= "")) then
        print( "share(): Invalid first argument of type "..t
            ..", expected number, or unempty-string." );
        return data; --error.
      end
      local t = type( context );
      if not (t == "nil" or (t == "string" and context ~= "")) then
        print( "share(): Invalid third argument of type "..t
            ..", expected nil, or unempty-string." );
        return data; --error.
      end
      local r, t = data, {}; LuaEvents.HasShared( key, t, context );
      if not t[1] then LuaEvents.SetShared( key, data, context );
      else t = {}; LuaEvents.GetShared( key, t, context ); r = t[key];
      end return r;
    end
    --===========================================================================
    --[[
    Returns boolean case of shared context-global for key or boolean case of
    shared super-global for key when context not given.  Requires ShareData.lua.
    ]]
    function hasShared( key, context )
      local r = {}; LuaEvents.HasShared( key, r, context ); return r[1];
    end
    --===========================================================================
    --[[
    Sets given data to given key as shared context-global or shared super-global
    when context not given.  Sets given data to automatic integer-key when key
    not given.  Must give key when context not given.  Returns previous data for
    given key.  Requires ShareData.lua.
    ]]
    function setShared( key, data, context )
      if key == nil and context == nil then
        print( "setShared(): Invalid first and third argument combination of "
            .."types "..type( key ).." and "..type( context )
            ..", expected any type except nil for key when context nil." );
        return nil; --error.
      end
      local r = {}; LuaEvents.SetShared( key, data, context, r );
      if key ~= nil then r = r[key]; else r = nil; end return r;
    end
    --===========================================================================
    --[[
    Returns shared context-global for key or shared super-global for key when
    context not given.  Returns entire table of context-globals for given context
    when key not given.  Returns entire table of super-globals when both key and
    context not given.  Returns entire table of context tables when key not given
    and given context boolean true.  Requires ShareData.lua.
    ]]
    function getShared( key, context )
      local r = {}; LuaEvents.GetShared( key, r, context );
      if key ~= nil then r = r[key]; end return r;
    end
    --===========================================================================
    --[[
    Removes and returns shared context-global for key or shared super-global for
    key when context not given.  Alias for function setShared(key, nil, context).
    Requires ShareData.lua.
    ]]
    function rmvShared( key, context )
      return setShared( key, nil, context );
    end
    --===========================================================================
    -- Save Class
    --===========================================================================
    --[[
    Allows SetScriptData() and GetScriptData() to behave as a hash-table of
    itemized data, while maintaining separation of individual mod data.
    ]]
    Save = {};
    function Save:new( o )
      o = o or {};
      if type( o ) == "string" and o == "" then o = {}; end
      if type( o ) ~= "table" then o = {o}; end
      setmetatable( o, self );
      self.__index = self;
    
      --deep assignment of keyed values.
      --required for duplication of table data assigned by key.
      local sub; --allows recursive call to sub-routine.
      sub = function( o )
        for k,v in pairs( o ) do self[k] = v;
          if type( v ) == "table" then sub( v ); end
        end
      end
    
      sub( o );
      return o;
    end
    --===========================================================================
    --[[
    Sets the given value to the given key for the given mod.
    ]]
    function Save:set( mod, key, value )
      self[mod] = self[mod] or {};
      if key ~= nil then
        self[mod][key] = value;
      else
        table.insert( self[mod], value );
      end
      local i = 0;
      for k,v in pairs( self[mod] ) do
        i = 1; break;
      end
      if i == 0 then self[mod] = nil; end
    end
    --===========================================================================
    --[[
    Returns value for given mod and key. Returns copy of self when mod not given.
    Returns mod table when key not given.
    ]]
    function Save:get( mod, key )
      
      local copy = function( object ) --deep copy.
        local lookup_table = {};
        local function _copy( object )
          if type( object ) ~= "table" then
            return object;
          elseif lookup_table[object] then
            return lookup_table[object];
          end
          local new_table = {};
          lookup_table[object] = new_table;
          for index,value in pairs( object ) do
            new_table[_copy( index )] = _copy( value );
          end
          return setmetatable( new_table, getmetatable( object ) );
        end
        return _copy( object );
      end
      
      local r = nil;
      if mod ~= nil then self[mod] = self[mod] or {}; r = self[mod];
        if key ~= nil then r = self[mod][key]; end
      else r = copy( self );
      end
      return r;
    end
    --===========================================================================
    --END Save Class
    --===========================================================================
    --===========================================================================
    -- Cache Class
    --===========================================================================
    --[[
    Manages object keys to unserialized object data.
    ]]
    Cache = {};
    function Cache:new( o )
      o = o or {};
      setmetatable( o, self );
      self.__index = self;
    
      --deep assignment of keyed values.
      --required for duplication of table data assigned by key.
      local sub; --allows recursive call to sub-routine.
      sub = function( o )
        for k,v in pairs( o ) do self[k] = v;
          if type( v ) == "table" then sub( v ); end
        end
      end
    
      sub( o );
      return o;
    end
    --===========================================================================
    --[[
    Returns boolean state for given target.
    ]]
    function Cache:has( target )
      r = false;
      if self[target] then r = true; end
      return r;
    end
    --===========================================================================
    --[[
    Sets given Save for given target.
    ]]
    function Cache:add( target, save )
      if target ~= nil and save ~= nil then self[target] = save; end
    end
    --===========================================================================
    --[[
    Returns Save for given target.  Returns self when target not given.
    ]]
    function Cache:get( target )
      local r = self;
      if self:has( target ) then r = self[target]; end
      return r;
    end
    --===========================================================================
    --[[
    Removes and returns Save for given target.
    ]]
    function Cache:rmv( target )
      return table.remove( self, target );
    end
    --===========================================================================
    --END Cache Class
    --===========================================================================
    --===========================================================================
    --[[
    Sets cache state for given mod to given state. Uses global MY_MOD_NAME If mod
    not given.
    ]]
    function setCacheState( state, mod )
      local t = type( state );
      if not t == "number" then
        print( "setCacheState(): Invalid first argument of type "..t
            ..", expected number." );
        return false; --error.
      end
      if mod == nil then mod = MY_MOD_NAME; end
      t = type( mod );
      if not (t == "number" or t == "string") then
        print( "setCacheState(): Invalid second argument of type "..t
            ..", expected number, or string." );
        return false; --error.
      end
      cacheState[mod] = state;
    end
    --===========================================================================
    --[[
    Returns cache state for given mod.  Uses global MY_MOD_NAME If mod not given.
    ]]
    function getCacheState( mod )
      if mod == nil then mod = MY_MOD_NAME; end
      local t = type( mod );
      if not (t == "number" or t == "string") then
        print( "getCacheState(): Invalid first argument of type "..t
            ..", expected number, or string." );
        return false; --error.
      end
      return cacheState[mod];
    end
    --===========================================================================
    --[[
    Removes cache data for given target, or all data when target is boolean true.
    ]]
    function clear( target )
      local t = type( target );
      if not (t == "table" or target == true) then
        print( "clear(): Invalid first argument of type "..t
            ..", expected table, or boolean true." );
        return false; --error.
      end
      if target == true then
        for k,pSave in pairs( pCache ) do pCache:rmv( k ); end
      else pCache:rmv( target );
      end
    end
    --===========================================================================
    --[[
    Serializes cache data for given target, or all cache data when target is
    boolean true.
    ]]
    function sync( target )
      local t = type( target );
      if not (t == "table" or target == true) then
        print( "sync(): Invalid first argument of type "..t
            ..", expected table, or boolean true." );
        return false; --error.
      end
      if target == true then
        for k,pSave in pairs( pCache ) do
          k:SetScriptData(serialize( pSave ));
        end
      else
        local pSave = pCache:get( target );
        if pSave == nil then
          print( "sync(): Target not found." ); return false; --error.
        else target:SetScriptData(serialize( pSave ));
        end
      end
    end
    --===========================================================================
    --[[
    Saves the given value to the given key for the given target of the given mod.
    Invalid data types: function, userdata, thread.  Uses global MY_MOD_NAME If
    mod not given.  Can optionally override the cache state for any particular
    call.  This can be particularly useful when mod is boolean false.
    ]]
    function save( target, key, value, mod, cacheState )
      local t = type( target );
      if not t == "table" then
        print( "save(): Invalid first argument of type "..t
            ..", expected table." );
        return false; --error.
      end  
      if mod == nil then mod = MY_MOD_NAME; end
      t = type( mod );
      if not (t == "number" or t == "string") then
        print( "save(): Invalid fourth argument of type "..t
            ..", expected number, or string." );
        return false; --error.
      end
      t = type( cacheState );
      if not (cacheState == nil or t == "number") then
        print( "save(): Invalid fifth argument of type "..t
            ..", expected nil, or number." );
        return false; --error.
      end
      local pSave = nil;
      local iCache = DEFAULT_CACHE_STATE;
      if cacheState ~= nil then iCache = cacheState;
      else
        if getCacheState( mod ) == nil then setCacheState( iCache, mod );
        else iCache = getCacheState( mod );
        end
      end
      pSave = load( target, nil, false, iCache );
      pSave:set( mod, key, value );
      if iCache <= 1 then target:SetScriptData(serialize( pSave )); end
      if iCache >= 1 then pCache:add( target, pSave ); end
    end
    --===========================================================================
    --[[
    Loads the value for the given key for the given target of the given mod.
    Returns entire mod table for the given target when key is nil.  Returns
    entire save table for the given target when mod is boolean false.  Uses
    global MY_MOD_NAME If mod not given.  Can optionally override the cache state
    for any particular call.  This can be particularly useful when mod is boolean
    false, because logically mod false can not have a cache state and thus
    otherwise uses default cache state or cache state for MY_MOD_NAME when set.
    ]]
    function load( target, key, mod, cacheState )
      local t = type( target );
      if not t == "table" then
        print( "load(): Invalid first argument of type "..t
            ..", expected table." );
        return nil; --error.
      end    
      if mod == nil then mod = MY_MOD_NAME; end
      t = type( mod );
      if not (t == "number" or t == "string" or mod == false) then
        print( "load(): Invalid third argument of type "..t
            ..", expected number, string, or boolean false." );
        return nil; --error.
      end
      if mod == false then mod = nil; end
      t = type( cacheState );
      if not (cacheState == nil or t == "number") then
        print( "load(): Invalid fourth argument of type "..t
            ..", expected nil, or number." );
        return nil; --error.
      end
      local pSave = nil;
      local iCache = DEFAULT_CACHE_STATE;
      if cacheState ~= nil then iCache = cacheState;
      else
        if mod == nil then
          if getCacheState( MY_MOD_NAME ) ~= nil then
            iCache = getCacheState( MY_MOD_NAME );
          end
        else
          if getCacheState( mod ) == nil then setCacheState( iCache, mod );
          else iCache = getCacheState( mod );
          end
        end
      end
      if iCache < 1 then pSave = Save:new(deserialize( target:GetScriptData() ));
      elseif iCache > 0 then
        if pCache:has( target ) then pSave = pCache:get( target );
        else
          pSave = Save:new(deserialize( target:GetScriptData() ));
          pCache:add( target, pSave );
        end
      end
      return pSave:get( mod, key );
    end
    --===========================================================================
    --[[
    Removes both the key and value for the given target of the given mod
    with the given key. Alias for function save( target, key, nil, mod ).
    ]]
    function delete( target, key, mod )
      save( target, key, nil, mod );
    end
    --===========================================================================
    --[[
    Serializes given data and returns result string.  Invalid data types:
    function, userdata, thread.
    ]]
    function serialize( p )
      
      local r = ""; local t = type( p );
      if t == "function" or t == "userdata" or t == "thread" then
        print( "serialize(): Invalid type: "..t ); --error.
      elseif p ~= nil then
        if t ~= "table" then
          if p == nil or p == true or p == false
            or t == "number" then r = tostring( p );
          elseif t == "string" then
            if p:lower() == "true" or p:lower() == "false"
                or tonumber( p ) ~= nil then r = '"'..p..'"';
            else r = p;
            end
          end
          r = r:gsub( "{", "\[LCB\]" );
          r = r:gsub( "}", "\[RCB\]" );
          r = r:gsub( "=", "\[EQL\]" );
          r = r:gsub( ",", "\[COM\]" );
        else
          r = "{"; local b = false;
          for k,v in pairs( p ) do
            if b then r = r..","; end
            r = r..serialize( k ).."="..serialize( v );
            b = true;
          end
          r = r.."}"
        end
      end
      return r;
    end
    --===========================================================================
    --[[
    Deserializes given string and returns result data.
    ]]
    function deserialize( str )
    
      local findToken = function( str, int )
        if int == nil then int = 1; end
        local s, e, c = str:find( "({)" ,int);
        if s == int then --table.
          local len = str:len();
          local i = 1; --open brace.
          while i > 0 and s ~= nil and e <= len do --find close.
            s, e, c = str:find( "([{}])" ,e+1);
            if     c == "{" then i = i+1;
            elseif c == "}" then i = i-1;
            end
          end
          if i == 0 then c = str:sub(int,e);
          else print( "deserialize(): Malformed table." ); --error.
          end
        else s, e, c = str:find( "([^=,]*)" ,int); --primitive.
        end
        return s, e, c, str:sub( e+1, e+1 );
      end
    
      local r = nil; local s, c, d;
      if str ~= nil then
        local sT, eT, cT = str:find( "{(.*)}" );
        if sT == 1 then
          r = {}; local len = cT:len(); local e = 1;
          if cT ~= "" then
            repeat
              local t1, t2; local more = false;
              s, e, c, d = findToken( cT, e );
              if s ~= nil then t1 = deserialize( c ); end
              if d == "=" then --key.
                s, e, c, d = findToken( cT, e+2 );
                if s ~= nil then t2 = deserialize( c ); end
              end
              if d == "," then e = e+2; more = true; end --one more.
              if t2 ~= nil then r[t1] = t2;
              else table.insert( r, t1 );
              end
            until e >= len and not more;
          end
        elseif tonumber(str) ~= nil then r = tonumber(str);
        elseif str == "true"  then r = true;
        elseif str == "false" then r = false;
        else
          s, e, c = str:find( '"(.*)"' );
          if s == 1 and e == str:len() then
            if c == "true" or c == "false" or tonumber( c ) ~= nil then
              str = c;
            end
          end
          r = str;
          r = r:gsub( "%[LCB%]", "{" );
          r = r:gsub( "%[RCB%]", "}" );
          r = r:gsub( "%[EQL%]", "=" );
          r = r:gsub( "%[COM%]", "," );
        end
      end
      return r;
    end
    --===========================================================================
    --END SaveUtils.lua
    --===========================================================================
    -- Created by: Ryan F. Mercer -Open source
    
     
  4. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    The version appears at the very top of the file in this format:
    formatkey.vv.yyyy.mm.dd.tttt

    formatkey = vymdt
    v = version.
    y = year.
    m = month.
    d = day.
    t = military time.

    The full version identifier includes the format key at the beginning of the string, so this...

    vymdt.01.2010.10.21.1800 means this...

    version 1, build: October 21, 2010 at 6:00pm.

    Conceptual constructs such as "alpha", "beta", or "gold" are not technically part of the identifier, but can be appended to the end as such...

    vymdt.01.2010.10.21.1800--alpha

    If you wish to make your own version, please append the identifier with the following suffix followed by what ever format you like:

    vymdt.01.2010.10.21.1800--MyVersion.01

    This ensures ideal sorting of identifiers.
     
  5. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    With regards to the build number, I'm on PST.
     
  6. Duha

    Duha Chieftain

    Joined:
    Jul 13, 2010
    Messages:
    18
    Location:
    Saint-Petersburg, Russia
    Test

    Hi, you should add negative test.
    any type can be stored as table key or value.
    Code:
    test_table = {
            [tostring] = "function", -- function as key
            a= tostring,  -- function as value
            ["b"] = "b",   --same as b="b"
            ["b b"]  = "b b", -- using key with spaces
            d = "", --empty string
            e = {1,2,3}, -- table as value
            [{1,2}] = 1, -- table as key
            [true] = "t", --boolean as key
            true = "true", -- using true as string 
            [false] = "f", --boolean as key
            false = "false", -- using false as string 
    }
    
    IMHO invalid keys should be ignored
     
  7. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    I've now implemented caching. Please test it out. Thank you. :)
     
  8. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    I agree, but I would prefere to keep it as open to types as possible. This might require some changes to the serialize and deserialize functions, which I myself did not write, so I'll have to give it some thought.

    Thanks for the input.
     
  9. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Does anyone know what the concurrent mods data scope is? Do they have access to each others globals, or are they each run in their own environment? I suspect the latter, which begs the question. Is there a way for one mod to gain access to another mods globals, or would it require merging the actual scripts?
     
  10. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    By the way, as a personal bias I just want to say this right now. While it is technically possible for a mod to intentionally overwrite the save data of another concurrent mod, this IMNSHO, violates all programming etiquette.

    (deprecated)
    Spoiler :

    As courtesy, I recommend placing the following data after the include in your own code, set the MY_MOD variable to your mod's name, then use these methods to save, load, and delete.

    Code:
    include("SaveUtils").
    
    MY_MOD = "myMod";
    -------------------------------------------------
    -- saveMy()
    -------------------------------------------------
    --[[
    Saves the given value to the given key for the
    given target.
    ]]
    function saveMy( target, key, value )
    	save( MY_MOD, target, key, value );
    end
    -------------------------------------------------
    -- loadMy()
    -------------------------------------------------
    --[[
    Loads the value for the given key for the given
    target.
    ]]
    function loadMy( target, key, value )
    	return load( MY_MOD, target, key, value );
    end
    -------------------------------------------------
    -- deleteMy()
    -------------------------------------------------
    --[[
    Removes both the key and value for the given
    target with the given key.
    ]]
    function deleteMy( target, key, value )
    	save( MY_MOD, target, key, nil );
    end
    
     
  11. Duha

    Duha Chieftain

    Joined:
    Jul 13, 2010
    Messages:
    18
    Location:
    Saint-Petersburg, Russia
    MY_MOD..mod_ver - unique mod key, generated by modbuddy
    + mod version

    I think this data are accessible via API.

    Can you add to wiki info about save mechanism?

    When game saves user data?
    Is any pre/post save events?
    or game just dumps all data that inside my_mod.db and moder should contain it always actual?
     
  12. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Version 2. :)

    The function calls are cleaner and now load() can return the specified mod table.
    The courtesy functions mentioned in my previous post no longer apply for version 2.
     
  13. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    I'm not entirely sure what you are asking. I don't know exactly when the game saves user data. I assume it does it when you save your game from the in game menu. I also don't know event order or what the events are. SaveUtils simply makes use of the game's built in SetScriptData() and GetScriptData() functions made available to modders for saving and retrieving custom data attached to players, plots, and units. The backend is as much a mystery to me as it is to you.
     
  14. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Version 3. :)

    I've rewritten the serialize() and deserialize() functions from scratch. For this reason version 3 is not backwards compatible. But true to Lua, you can now use any data type except nil for a key. Attempting "save( target, nil, value );" results in an automatic integer index.

    Oh, and you can't use functions, userdata, or thread as data types either. I don't even know if any of those are possible. I might add negative checks for them at some point. Or maybe not.

    If you can think of any additional get functions that might be useful for gathering different collections of data across targets, let me know. If you write one of your own, please share it here.
     
  15. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Ooops, my bad. Just realized that the first time you try to load data from a target that hasn't had anything writen to it yet will end up returning this...

    {1=""}

    That's not really what I intended and will try to resolves this immediately.
     
  16. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Okay, fixed it. Now a target with empty data will return an empty table, or more specifically, an empty Save object...

    {}

    The issue was that SaveUtils is prepared to deal with improper data written to a target by a non-compliant concurrent mod, so that the script can then inspect the improper data. But an empty target will always return an empty string, which appeared to SaveUtils as improper data in need of inspection. Anyway, all better now.

    I also went ahead and added the negative checks for data types function, userdata, and thread. They will now be treated as an empty string and generate an error message in the Lua console upon call to serialize(). A similar change was made to the non-essential functions out() and len().
     
  17. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Something I've noticed that I'm currently looking into is some unexpected behavior when reusing a loaded pointer after a save. For example...

    Code:
    local target = GetPlayer();
    local t = tostring(target);
    local pointer = load( target, "key" );
    print("1: "..out( pointer ));
    save( target, "key", {["new table"]=t} );
    print("2: "..out( pointer ));
    
    The first print statement shows an empty table. The second print statement shows the entire Save table, mod names and all. This is the result of returning the Save object reference on call to load(). I have a fix and am implementing it now. Instead, load() will return a *copy* of the Save object, not the reference.

    Okay, better now. :)
     
  18. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Version 4. Nothing significant, tho I did split off the out() and len() functions into a separate file. They are handy, but not specifically used by SaveUtils. Here it is if you still want them.

    WhysUtils.lua
    Code:
    -- vymdt.01.2010.11.12.0000
    -- Created by: Whys Alives -Open source
    --===========================================================================
    -- WhysUtils.lua
    --===========================================================================
    --[[
    Global Functions: len(), out().
    ]]
    --===========================================================================
    --[[
    Returns length of both tables and strings.  Returns false when given boolean,
    function, userdata, or thread.
    ]]
    function len( p )
    	local r = 0; local t = type( p );
    	if t == "boolean" or t == "function" or t == "userdata" or t == "thread"
    			then r = false; print( "len(): Invalid type: "..t ); --error.
    	elseif t == "table"  then for k,v in pairs( p ) do r = r +1; end
    	elseif t == "string" then r = p:len();
    	end
    	return r;
    end
    --===========================================================================
    --[[
    Returns a string representation of any given data type.
    ]]
    function out( p )
    	local r = ""; local t = type( p );
    	if p ~= nil then
    		if t ~= "table" then
    			if t == "boolean" or t == "number" or t == "function"
    					or t == "userdata"	or t == "thread" then
    				r = tostring( p );
    			else r = '"'..p..'"';
    			end
    		else
    			r = "{"; local b = false;
    			for k,v in pairs( p ) do
    				if b then r = r..","; end
    				r = r..out( k ).."="..out( v );
    				b = true;
    			end
    			r = r.."}"
    		end
    	end
    	return r;
    end
    --===========================================================================
    --END WhysUtils.lua
    --===========================================================================
    -- Created by: Whys Alives -Open source
    
     
  19. robk

    robk Chieftain

    Joined:
    Sep 8, 2006
    Messages:
    210
    Location:
    Santa Monica, CA
    I just started playing around with this and it appears that len() is used at line 121 of the main library (in Save:set()). I got an error trying to save since I didn't include the supplemental functions at first.
     
  20. Whys

    Whys Between the Lines

    Joined:
    Oct 20, 2007
    Messages:
    456
    Oops! Thanks for catching that. I'm always including WhysUtils, so didn't notice. My bad. :]

    Anyway, fixed it. SaveUtils no longer requires len().
     

Share This Page