Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[ This script automatically saves the global environment periodically
- and restores it when this script is run. Recommended usage is with
- the standalone Lua interpreter like this:
- lua -i autosave.lua
- It was coded for and tested with Eris (https://github.com/fnuecke/eris).
- This approach is quite limited and could be improved in a number of ways:
- - Userdata cannot be persisted automatically, so if, for example, some
- files are open the session cannot be saved / restored.
- - There's the (unlikely) possibility of the script trying to save while
- already doing so, if the save takes longer than `autosaveInterval`
- and the garbage collector is triggered in the save logic.
- - I could not figure out a way to check whether Lua is shutting down in
- the autosave() function. It gets called when Lua closes, but it may
- decide not to save if the last save was earlier than the set interval.
- ]]
- -- [[ Config ]]
- -- The name of the file to persist the global state to.
- local stateFile = ".lua.eris"
- -- The interval in seconds in which to save the global state. Avoids
- -- excessive saves if the garbage collector hyperventilates.
- local autosaveInterval = 10
- -- [[ General persistence bootstrapping ]]
- -- Stuff we cannot persist directly. Basically all of the built-in
- -- C functions and userdata. We need a mapping to and from 'permanent'
- -- values, that act as placeholders for these values.
- local perms, uperms = {}, {}
- do
- local processed = {} -- list of seen tables, avoids cycles.
- local function flattenAndStore(path, t)
- -- Get the list of keys so we can sort it. This is needed because
- -- permanent values have to be unique, so we need to pick a name for
- -- them in a deterministic fashion (which pairs isn't).
- local keys = {}
- for k, v in pairs(t) do
- if type(k) ~= "string" and type(k) ~= "number" then
- io.stderr:write("Cannot generate permanent value for global with non-string key at " .. path .. ". Things may not go well.\n")
- else
- table.insert(keys, k)
- end
- end
- table.sort(keys)
- for _, k in ipairs(keys) do
- local name = path .. "." .. k
- local v = t[k]
- -- This avoids duplicate permanent value entries, see above.
- if perms[v] == nil then
- local vt = type(v)
- if "function" == vt or "userdata" == vt then
- perms[v] = name
- uperms[name] = v
- elseif "table" == vt and not processed[v] then
- processed[v] = true
- flattenAndStore(name, v)
- end
- end
- end
- end
- flattenAndStore("_G", _G)
- end
- -- [[ Autosave background task ]]
- local lastSave = os.time()
- local function autosave()
- if os.difftime(os.time(), lastSave) > autosaveInterval then
- lastSave = os.time()
- local data, reason = eris.persist(perms, _ENV)
- if not data then
- io.stderr:write("Failed to persist environment: " .. tostring(reason) .. "\n")
- else
- local f, reason = io.open(stateFile, "wb")
- if not f then
- io.stderr:write("Failed to save environment: " .. tostring(reason) .. "\n")
- else
- f:write(data)
- f:close()
- end
- end
- end
- -- Check again later. This basically uses the garbage collector as a
- -- scheduler (this method is called when the table is collected). It
- -- is a concept I first saw posted by Roberto on the list a while
- -- back, in the context of sandboxing I think.
- setmetatable({}, {__gc=autosave})
- end
- autosave()
- -- [[ Startup / load autosave ]]
- local f = io.open(stateFile, "rb")
- if f then
- -- Got a previous state, try to restore it.
- local data = f:read("*a")
- f:close()
- local env, reason = eris.unpersist(uperms, data)
- if not env then
- io.stderr:write("Failed to unpersist environment: " .. tostring(reason) .. "\n")
- else
- for k, v in pairs(env) do
- _ENV[k] = env[k]
- setmetatable(_ENV, getmetatable(env))
- end
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment