2013-05-24 13:54:30 +00:00
|
|
|
|
|
|
|
-- Savegame facilities for Lunatic.
|
|
|
|
|
|
|
|
local string = require("string")
|
|
|
|
local table = require("table")
|
|
|
|
|
2013-06-28 14:07:39 +00:00
|
|
|
local format = string.format
|
|
|
|
|
2013-06-07 10:18:17 +00:00
|
|
|
local assert = assert
|
2013-05-24 13:54:30 +00:00
|
|
|
local getmetatable = getmetatable
|
|
|
|
local pairs = pairs
|
|
|
|
local setmetatable = setmetatable
|
|
|
|
local tostring = tostring
|
|
|
|
local type = type
|
|
|
|
|
2013-06-30 20:38:35 +00:00
|
|
|
local cansave_cdata = require("lprivate").cansave_cdata
|
|
|
|
|
2013-05-24 13:54:30 +00:00
|
|
|
|
|
|
|
module(...)
|
|
|
|
|
|
|
|
|
|
|
|
---=== Serialization, based on the idea from PiL ===---
|
|
|
|
|
2013-06-07 10:18:17 +00:00
|
|
|
assert(tostring(0/0)=="nan")
|
|
|
|
assert(tostring(1/0)=="inf")
|
|
|
|
|
2013-05-24 13:54:30 +00:00
|
|
|
-- Serialize a 'primitive' Lua value.
|
|
|
|
local function basicSerialize(o)
|
|
|
|
-- Compare with sb_get_initial_strbuf() below.
|
|
|
|
-- XXX: nil?
|
|
|
|
if (type(o) == "number") then
|
|
|
|
-- NOTE: NaN and infinity handled.
|
|
|
|
return tostring(o)
|
|
|
|
elseif (type(o) == "boolean") then
|
|
|
|
return o and "t" or "f"
|
|
|
|
elseif (type(o) == "string") then
|
|
|
|
-- TODO: return refname if it's shorter
|
2013-06-28 14:07:39 +00:00
|
|
|
return format("%q", o)
|
2013-05-24 13:54:30 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local function isSerializeable(obj)
|
2013-06-30 20:38:35 +00:00
|
|
|
return (getmetatable(obj)=="serializeable" or cansave_cdata(obj))
|
2013-05-24 13:54:30 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- 'Save buffer' class. Objects of this class keep track of a string buffer
|
|
|
|
-- contiaining Lua code to recreate the values of particular variables from a
|
|
|
|
-- user Lunatic environment.
|
|
|
|
local savebuffer_mt = {
|
|
|
|
__index = {
|
2013-06-28 14:07:39 +00:00
|
|
|
-- Called on starting processing one module.
|
|
|
|
startmod = function(self, modname)
|
|
|
|
self:addrawf("if (N==%q) then", modname)
|
|
|
|
-- Will contain all tables and cdata refs of this module, indexed
|
|
|
|
-- by number.
|
|
|
|
self:addraw("local T={}")
|
|
|
|
self:resetRefs()
|
|
|
|
end,
|
|
|
|
|
|
|
|
-- Called on finishing processing one module.
|
|
|
|
endmod = function(self)
|
|
|
|
self:addraw("end")
|
|
|
|
end,
|
|
|
|
|
|
|
|
--== vvv INTERNAL vvv ===---
|
|
|
|
resetRefs = function(self)
|
|
|
|
self.numrefs = 0
|
|
|
|
self.val2ref = {}
|
|
|
|
self.havereq = {}
|
|
|
|
end,
|
|
|
|
|
|
|
|
-- Get the code necessary to create this object, usually 'require'ing a
|
|
|
|
-- module into a local variable.
|
|
|
|
getRequire = function(self, value)
|
|
|
|
local reqcode = value:_get_require()
|
|
|
|
if (self.havereq[reqcode] == nil) then
|
|
|
|
self.havereq[reqcode] = true
|
|
|
|
self.strbuf[#self.strbuf+1] = reqcode
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
|
|
|
|
emitT = function(self, refcode, valcode, obj)
|
|
|
|
self:addrawf("%s=%s", refcode, valcode)
|
|
|
|
self.val2ref[obj] = refcode
|
|
|
|
end,
|
|
|
|
|
|
|
|
emitAssign = function(self, lhscode, refcode)
|
|
|
|
self:addrawf("%s=%s", lhscode, refcode)
|
|
|
|
end,
|
|
|
|
--== ^^^ INTERNAL ^^^ ===---
|
|
|
|
|
2013-05-24 13:54:30 +00:00
|
|
|
-- Add an entry of Lua object <value> that can be referenced by
|
2013-06-28 14:07:39 +00:00
|
|
|
-- <lhscode> on the left hand side of an assignment.
|
2013-05-24 13:54:30 +00:00
|
|
|
-- Returns 'true' if <value> cannot be serialized.
|
2013-06-28 14:07:39 +00:00
|
|
|
add = function(self, lhscode, value)
|
2013-05-24 13:54:30 +00:00
|
|
|
local valcode = basicSerialize(value)
|
|
|
|
|
|
|
|
if (valcode == nil) then
|
|
|
|
-- <value> is a not a 'basic' Lua object, but one passed by
|
|
|
|
-- reference.
|
|
|
|
if (not self.val2ref[value]) then
|
|
|
|
-- Object is being serialized for the first time.
|
|
|
|
|
2013-06-28 14:07:39 +00:00
|
|
|
-- Create a slot in 'T' for this object by which we can
|
|
|
|
-- refer to it in the following.
|
|
|
|
self.numrefs = self.numrefs+1
|
|
|
|
local refcode = format("T[%d]", self.numrefs)
|
|
|
|
|
2013-05-24 13:54:30 +00:00
|
|
|
if (isSerializeable(value)) then
|
|
|
|
-- We have a serializeable object from Lunatic
|
|
|
|
-- (e.g. actorvar).
|
2013-06-28 14:07:39 +00:00
|
|
|
self:getRequire(value)
|
|
|
|
self:emitT(refcode, value:_serialize(), value)
|
|
|
|
valcode = refcode
|
2013-05-24 13:54:30 +00:00
|
|
|
|
|
|
|
elseif (type(value)=="table") then
|
2013-06-20 18:31:47 +00:00
|
|
|
-- Create a new table for this gamevar.
|
2013-06-28 14:07:39 +00:00
|
|
|
-- TODO: emit table initializations where possible.
|
|
|
|
self:emitT(refcode, "{}", value)
|
2013-05-24 13:54:30 +00:00
|
|
|
|
|
|
|
for k,v in pairs(value) do
|
|
|
|
local keystr = basicSerialize(k)
|
|
|
|
if (keystr == nil) then
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Generate the name under which the table element
|
|
|
|
-- is referenced.
|
2013-06-28 14:07:39 +00:00
|
|
|
local refcode2 = format("%s[%s]", refcode, keystr)
|
2013-05-24 13:54:30 +00:00
|
|
|
|
|
|
|
-- Recurse!
|
2013-06-28 14:07:39 +00:00
|
|
|
if (self:add(refcode2, v)) then
|
|
|
|
return true
|
|
|
|
end
|
2013-05-24 13:54:30 +00:00
|
|
|
end
|
2013-06-28 14:07:39 +00:00
|
|
|
|
|
|
|
valcode = refcode
|
2013-05-24 13:54:30 +00:00
|
|
|
else
|
|
|
|
-- We have anything else: can't serialize.
|
|
|
|
return true
|
|
|
|
end
|
|
|
|
else
|
2013-06-28 14:07:39 +00:00
|
|
|
-- Object was previously serialized, get Lua expression it
|
|
|
|
-- can be referenced with.
|
2013-05-24 13:54:30 +00:00
|
|
|
valcode = self.val2ref[value]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-06-28 14:07:39 +00:00
|
|
|
self:emitAssign(lhscode, valcode)
|
2013-05-24 13:54:30 +00:00
|
|
|
end,
|
|
|
|
|
|
|
|
-- Add a single string to the buffer.
|
|
|
|
addraw = function(self, str)
|
|
|
|
self.strbuf[#self.strbuf+1] = str
|
|
|
|
end,
|
|
|
|
|
|
|
|
-- Add a single formatted string to the buffer.
|
|
|
|
addrawf = function(self, fmt, ...)
|
2013-06-28 14:07:39 +00:00
|
|
|
self:addraw(format(fmt, ...))
|
2013-05-24 13:54:30 +00:00
|
|
|
end,
|
|
|
|
|
|
|
|
-- Get the Lua code recreating the values as a string.
|
|
|
|
getcode = function(self)
|
|
|
|
self.strbuf[#self.strbuf+1] = "" -- add newline at EOS
|
|
|
|
return table.concat(self.strbuf, "\n")
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
local function sb_get_initial_strbuf()
|
|
|
|
return {
|
|
|
|
"local nan, inf = 0/0, 1/0",
|
|
|
|
"local t, f = true, false",
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Create a new savebuffer object.
|
|
|
|
function savebuffer()
|
2013-06-28 14:07:39 +00:00
|
|
|
-- .numrefs: how many table or cdata objects we have serialized
|
2013-05-24 13:54:30 +00:00
|
|
|
-- .val2ref: [<Lua object>] = <Lua code string>
|
|
|
|
-- .havereq = [<string>] = true
|
|
|
|
-- .strbuf: array of Lua code pieces
|
2013-06-28 14:07:39 +00:00
|
|
|
local sb = {
|
|
|
|
numrefs=0, val2ref={}, havereq={},
|
|
|
|
strbuf=sb_get_initial_strbuf()
|
|
|
|
}
|
|
|
|
|
2013-05-24 13:54:30 +00:00
|
|
|
return setmetatable(sb, savebuffer_mt)
|
|
|
|
end
|