From e3a85f4083473c876e3a9dd9ad8d62f4284ec432 Mon Sep 17 00:00:00 2001 From: helixhorned Date: Fri, 24 May 2013 13:54:30 +0000 Subject: [PATCH] Lunatic: fix restoring of tables. Also add savegame.lua which I forgot! git-svn-id: https://svn.eduke32.com/eduke32@3807 1a8010ca-5511-0410-912e-c29ae57300e0 --- polymer/eduke32/source/lunatic/control.lua | 80 ++++++---- polymer/eduke32/source/lunatic/dynsymlist | 3 +- polymer/eduke32/source/lunatic/dynsymlist_m32 | 2 + polymer/eduke32/source/lunatic/savegame.lua | 151 ++++++++++++++++++ 4 files changed, 205 insertions(+), 31 deletions(-) create mode 100644 polymer/eduke32/source/lunatic/savegame.lua diff --git a/polymer/eduke32/source/lunatic/control.lua b/polymer/eduke32/source/lunatic/control.lua index bdad61b59..4235ae837 100644 --- a/polymer/eduke32/source/lunatic/control.lua +++ b/polymer/eduke32/source/lunatic/control.lua @@ -1768,14 +1768,26 @@ local function kopen4load(fn, searchfirst) end --- Common serialization function for gamearray and actorvar. -local function serialize_array(ar, strtab) - for i,v in pairs(ar) do - if (type(i)=="number") then - assert(type(v)=="number") -- XXX: not enforced by public API - strtab[#strtab+1] = "["..i.."]="..tostring(v).."," - end +local function serialize_value(strtab, i, v) + -- Save only user values (i.e. not 'meta-fields' like '_size'). + if (type(i)=="number" and v~=nil) then + strtab[#strtab+1] = "["..i.."]="..tostring(v).."," end +end + +-- Common serialization function for gamearray and actorvar. +local function serialize_array(ar, strtab, maxnum) +-- if (ffiC._DEBUG_LUNATIC ~= 0) then + -- Iterate in numeric order. XXX: also for non-debug? + for i=0,maxnum-1 do + serialize_value(strtab, i, rawget(ar, i)) + end +-- else +-- for i,v in pairs(ar) do +-- serialize_value(strtab, i, v) +-- end +-- end + strtab[#strtab+1] = "})" return table.concat(strtab) @@ -1929,6 +1941,18 @@ local gamearray_methods = { end, + --- Internal routines --- + + -- * All values equal to the default one (0) are cleared. + _cleanup = function(gar) + for i=0,gar._size-1 do + if (rawget(gar, i)==0) then + rawset(gar, i, nil) + end + end + end, + + --- Serialization --- _get_require = function(gar) @@ -1937,7 +1961,8 @@ local gamearray_methods = { _serialize = function(gar) local strtab = { OUR_NAME..".actorvar(", tostring(gar._size), ",{" } - return serialize_array(gar, strtab) + gar:_cleanup() + return serialize_array(gar, strtab, gar._size) end, } @@ -1956,16 +1981,6 @@ local gamearray_mt = { rawset(gar, idx, val) end, - -- Calling a gamearray causes its cleanup: - -- * All values equal to the default one (0) are cleared. - __call = function(gar) - for i=0,gar._size-1 do - if (rawget(gar, i)==0) then - rawset(gar, i, nil) - end - end - end, - __metatable = "serializeable", } @@ -2004,6 +2019,19 @@ end -- Per-actor variable. local actorvar_methods = { + --- Internal routines --- + + -- * All values for sprite not in the game world are cleared. + -- * All values equal to the default one are cleared. + _cleanup = function(acv) + for i=0,ffiC.MAXSPRITES-1 do + if (ffiC.sprite[i].statnum == ffiC.MAXSTATUS or rawget(acv, i)==acv._defval) then + rawset(acv, i, nil) + end + end + end, + + --- Serialization --- _get_require = function(acv) @@ -2012,7 +2040,10 @@ local actorvar_methods = { _serialize = function(acv) local strtab = { OUR_NAME..".actorvar(", tostring(acv._defval), ",{" } - return serialize_array(acv, strtab) + -- TODO: Must clean up sometime if not saving, too. (That is, what is + -- A_ResetVars() in the C-CON build.) + acv:_cleanup() + return serialize_array(acv, strtab, ffiC.MAXSPRITES) end, } @@ -2032,17 +2063,6 @@ local actorvar_mt = { rawset(acv, idx, val) end, - -- Calling a per-actor variable causes its cleanup: - -- * All values for sprite not in the game world are cleared. - -- * All values equal to the default one are cleared. - __call = function(acv) - for i=0,ffiC.MAXSPRITES-1 do - if (ffiC.sprite[i].statnum == ffiC.MAXSTATUS or rawget(acv, i)==acv._defval) then - rawset(acv, i, nil) - end - end - end, - __metatable = "serializeable", } diff --git a/polymer/eduke32/source/lunatic/dynsymlist b/polymer/eduke32/source/lunatic/dynsymlist index 56b534db4..b8775daf8 100644 --- a/polymer/eduke32/source/lunatic/dynsymlist +++ b/polymer/eduke32/source/lunatic/dynsymlist @@ -81,6 +81,8 @@ getticks; gethitickms; OSD_Printf; +crc32once; + luaJIT_BC_defs_common; luaJIT_BC_engine_maptext; @@ -169,7 +171,6 @@ luaJIT_BC_savegame; rand_jkiss_u32; rand_jkiss_dbl; -md4once; A_IncurDamage; G_CheckActivatorMotion; diff --git a/polymer/eduke32/source/lunatic/dynsymlist_m32 b/polymer/eduke32/source/lunatic/dynsymlist_m32 index 8974fbcbe..75adf45b6 100644 --- a/polymer/eduke32/source/lunatic/dynsymlist_m32 +++ b/polymer/eduke32/source/lunatic/dynsymlist_m32 @@ -81,6 +81,8 @@ getticks; gethitickms; OSD_Printf; +crc32once; + luaJIT_BC_defs_common; luaJIT_BC_engine_maptext; diff --git a/polymer/eduke32/source/lunatic/savegame.lua b/polymer/eduke32/source/lunatic/savegame.lua new file mode 100644 index 000000000..66414adcb --- /dev/null +++ b/polymer/eduke32/source/lunatic/savegame.lua @@ -0,0 +1,151 @@ + +-- Savegame facilities for Lunatic. + +local string = require("string") +local table = require("table") + +local getmetatable = getmetatable +local pairs = pairs +local setmetatable = setmetatable +local tostring = tostring +local type = type + + +module(...) + + +---=== Serialization, based on the idea from PiL ===--- + +-- 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 + return string.format("%q", o) + end +end + + +local function isSerializeable(obj) + return (getmetatable(obj)=="serializeable") +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 = { + -- Add an entry of Lua object that can be referenced by + -- (which should be Lua code that can appear on both sides of + -- an assignment). + -- Returns 'true' if cannot be serialized. + add = function(self, refcode, value) + local valcode = basicSerialize(value) + local havetab = false + + if (valcode == nil) then + -- 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. + + if (isSerializeable(value)) then + -- We have a serializeable object from Lunatic + -- (e.g. actorvar). + -- TODO: clean up (e.g. clear default values for actorvar). + + -- First, get the code necessary to create this object, + -- usually 'require'ing a module into a local variable. + local reqcode = value:_get_require() + if (self.havereq[reqcode] == nil) then + self.havereq[reqcode] = true + self.strbuf[#self.strbuf+1] = reqcode + end + + valcode = value:_serialize() + + elseif (type(value)=="table") then + -- We have a Lua table. + havetab = true + + -- Clear the table instead of creating a new one. + -- This way, local references to it don't become stale. + self:addrawf("ct(%s)", refcode) + + for k,v in pairs(value) do + local keystr = basicSerialize(k) + if (keystr == nil) then + return true + end + + if (type(v)=="table" and not isSerializeable(v)) then + -- nested tables: NYI + return true + end + + -- Generate the name under which the table element + -- is referenced. + local refcode2 = string.format("%s[%s]", refcode, keystr) + + -- Recurse! + self:add(refcode2, v) + end + else + -- We have anything else: can't serialize. + return true + end + + self.val2ref[value] = refcode + else + valcode = self.val2ref[value] + end + end + + if (not havetab) then + self:addraw(refcode.."="..valcode) + end + 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, ...) + self:addraw(string.format(fmt, ...)) + 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", + -- Clear table function: + "local function ct(t)", + " for k in pairs(t) do t[k]=nil end", + "end", + } +end + +-- Create a new savebuffer object. +function savebuffer() + -- .val2ref: [] = + -- .havereq = [] = true + -- .strbuf: array of Lua code pieces + local sb = { val2ref={}, havereq={}, strbuf=sb_get_initial_strbuf() } + return setmetatable(sb, savebuffer_mt) +end