Guest User

autosave.lua

a guest
Feb 27th, 2014
297
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 3.97 KB | None | 0 0
  1. --[[ This script automatically saves the global environment periodically
  2.      and restores it when this script is run. Recommended usage is with
  3.      the standalone Lua interpreter like this:
  4.  
  5.         lua -i autosave.lua
  6.  
  7.      It was coded for and tested with Eris (https://github.com/fnuecke/eris).
  8.  
  9.      This approach is quite limited and could be improved in a number of ways:
  10.      - Userdata cannot be persisted automatically, so if, for example, some
  11.        files are open the session cannot be saved / restored.
  12.      - There's the (unlikely) possibility of the script trying to save while
  13.        already doing so, if the save takes longer than `autosaveInterval`
  14.        and the garbage collector is triggered in the save logic.
  15.      - I could not figure out a way to check whether Lua is shutting down in
  16.        the autosave() function. It gets called when Lua closes, but it may
  17.        decide not to save if the last save was earlier than the set interval.
  18. ]]
  19.  
  20. -- [[ Config ]]
  21.  
  22. -- The name of the file to persist the global state to.
  23. local stateFile = ".lua.eris"
  24.  
  25. -- The interval in seconds in which to save the global state. Avoids
  26. -- excessive saves if the garbage collector hyperventilates.
  27. local autosaveInterval = 10
  28.  
  29. -- [[ General persistence bootstrapping ]]
  30.  
  31. -- Stuff we cannot persist directly. Basically all of the built-in
  32. -- C functions and userdata. We need a mapping to and from 'permanent'
  33. -- values, that act as placeholders for these values.
  34. local perms, uperms = {}, {}
  35. do
  36.   local processed = {} -- list of seen tables, avoids cycles.
  37.   local function flattenAndStore(path, t)
  38.     -- Get the list of keys so we can sort it. This is needed because
  39.     -- permanent values have to be unique, so we need to pick a name for
  40.     -- them in a deterministic fashion (which pairs isn't).
  41.     local keys = {}
  42.     for k, v in pairs(t) do
  43.       if type(k) ~= "string" and type(k) ~= "number" then
  44.         io.stderr:write("Cannot generate permanent value for global with non-string key at " .. path .. ". Things may not go well.\n")
  45.       else
  46.         table.insert(keys, k)
  47.       end
  48.     end
  49.     table.sort(keys)
  50.     for _, k in ipairs(keys) do
  51.       local name = path .. "." .. k
  52.       local v = t[k]
  53.       -- This avoids duplicate permanent value entries, see above.
  54.       if perms[v] == nil then
  55.         local vt = type(v)
  56.         if "function" == vt or "userdata" == vt then
  57.           perms[v] = name
  58.           uperms[name] = v
  59.         elseif "table" == vt and not processed[v] then
  60.           processed[v] = true
  61.           flattenAndStore(name, v)
  62.         end
  63.       end
  64.     end
  65.   end
  66.   flattenAndStore("_G", _G)
  67. end
  68.  
  69. -- [[ Autosave background task ]]
  70.  
  71. local lastSave = os.time()
  72. local function autosave()
  73.   if os.difftime(os.time(), lastSave) > autosaveInterval then
  74.     lastSave = os.time()
  75.     local data, reason = eris.persist(perms, _ENV)
  76.     if not data then
  77.       io.stderr:write("Failed to persist environment: " .. tostring(reason) .. "\n")
  78.     else
  79.       local f, reason = io.open(stateFile, "wb")
  80.       if not f then
  81.         io.stderr:write("Failed to save environment: " .. tostring(reason) .. "\n")
  82.       else
  83.         f:write(data)
  84.         f:close()
  85.       end
  86.     end
  87.   end
  88.   -- Check again later. This basically uses the garbage collector as a
  89.   -- scheduler (this method is called when the table is collected). It
  90.   -- is a concept I first saw posted by Roberto on the list a while
  91.   -- back, in the context of sandboxing I think.
  92.   setmetatable({}, {__gc=autosave})
  93. end
  94. autosave()
  95.  
  96. -- [[ Startup / load autosave ]]
  97.  
  98. local f = io.open(stateFile, "rb")
  99. if f then
  100.   -- Got a previous state, try to restore it.
  101.   local data = f:read("*a")
  102.   f:close()
  103.   local env, reason = eris.unpersist(uperms, data)
  104.   if not env then
  105.     io.stderr:write("Failed to unpersist environment: " .. tostring(reason) .. "\n")
  106.   else
  107.     for k, v in pairs(env) do
  108.       _ENV[k] = env[k]
  109.       setmetatable(_ENV, getmetatable(env))
  110.     end
  111.   end
  112. end
Advertisement
Add Comment
Please, Sign In to add comment