diff --git a/polymer/eduke32/source/lunatic/defs.ilua b/polymer/eduke32/source/lunatic/defs.ilua index 19b27660a..289f4c643 100644 --- a/polymer/eduke32/source/lunatic/defs.ilua +++ b/polymer/eduke32/source/lunatic/defs.ilua @@ -2051,7 +2051,7 @@ do -- XXX: System gamevars? Most of them ought to be saved with C data. for modname, modvars in pairs(module_gamevars) do - sb:addrawf("if (N==%q) then", modname) + sb:startmod(modname) -- Handle global gamevars first. for i=1,#modvars do @@ -2088,7 +2088,7 @@ do end end - sb:addraw("end") + sb:endmod() end -- Get the whole code as a string. diff --git a/polymer/eduke32/source/lunatic/doc/lunatic.txt b/polymer/eduke32/source/lunatic/doc/lunatic.txt index 32dbeaef9..f55125fcc 100644 --- a/polymer/eduke32/source/lunatic/doc/lunatic.txt +++ b/polymer/eduke32/source/lunatic/doc/lunatic.txt @@ -177,9 +177,13 @@ also be *`local`*. Game variables may take on only values of types that Lunatic knows how to serialize into savegames. These are the following: -* booleans, numbers, and strings -* tables, but with restrictions on their contents and topology described below (TODO) -* custom Lunatic types that are labeled _serializeable_ in their documentation +* Booleans, numbers, and strings, collectively called the _basic types_ +* Custom Lunatic types that are labeled _serializeable_ in their documentation +* Tables, but with the following restrictions on their contents: +** A table key may only be of basic type. +** A table value may be (a reference to) any serializeable object, but tables + or Lunatic objects that are so referenced *must* have originated in the + same module. Beyond that, there are no restrictions on the table topology. // [icon="icons/din_w_collapse.png"] diff --git a/polymer/eduke32/source/lunatic/savegame.lua b/polymer/eduke32/source/lunatic/savegame.lua index 23dc95398..5e48a3a4f 100644 --- a/polymer/eduke32/source/lunatic/savegame.lua +++ b/polymer/eduke32/source/lunatic/savegame.lua @@ -4,6 +4,8 @@ local string = require("string") local table = require("table") +local format = string.format + local assert = assert local getmetatable = getmetatable local pairs = pairs @@ -31,7 +33,7 @@ local function basicSerialize(o) return o and "t" or "f" elseif (type(o) == "string") then -- TODO: return refname if it's shorter - return string.format("%q", o) + return format("%q", o) end end @@ -45,13 +47,52 @@ end -- user Lunatic environment. local savebuffer_mt = { __index = { + -- 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 ^^^ ===--- + -- 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). + -- on the left hand side of an assignment. -- Returns 'true' if cannot be serialized. - add = function(self, refcode, value) + add = function(self, lhscode, value) local valcode = basicSerialize(value) - local havetab = false if (valcode == nil) then -- is a not a 'basic' Lua object, but one passed by @@ -59,26 +100,22 @@ local savebuffer_mt = { if (not self.val2ref[value]) then -- Object is being serialized for the first time. + -- 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) + if (isSerializeable(value)) then -- We have a serializeable object from Lunatic -- (e.g. 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() + self:getRequire(value) + self:emitT(refcode, value:_serialize(), value) + valcode = refcode elseif (type(value)=="table") then - -- We have a Lua table. - havetab = true - -- Create a new table for this gamevar. - self:addrawf("%s={}", refcode) + -- TODO: emit table initializations where possible. + self:emitT(refcode, "{}", value) for k,v in pairs(value) do local keystr = basicSerialize(k) @@ -86,32 +123,29 @@ local savebuffer_mt = { 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) + local refcode2 = format("%s[%s]", refcode, keystr) -- Recurse! - self:add(refcode2, v) + if (self:add(refcode2, v)) then + return true + end end + + valcode = refcode else -- We have anything else: can't serialize. return true end - - self.val2ref[value] = refcode else + -- Object was previously serialized, get Lua expression it + -- can be referenced with. valcode = self.val2ref[value] end end - if (not havetab) then - self:addraw(refcode.."="..valcode) - end + self:emitAssign(lhscode, valcode) end, -- Add a single string to the buffer. @@ -121,7 +155,7 @@ local savebuffer_mt = { -- Add a single formatted string to the buffer. addrawf = function(self, fmt, ...) - self:addraw(string.format(fmt, ...)) + self:addraw(format(fmt, ...)) end, -- Get the Lua code recreating the values as a string. @@ -141,9 +175,14 @@ end -- Create a new savebuffer object. function savebuffer() + -- .numrefs: how many table or cdata objects we have serialized -- .val2ref: [] = -- .havereq = [] = true -- .strbuf: array of Lua code pieces - local sb = { val2ref={}, havereq={}, strbuf=sb_get_initial_strbuf() } + local sb = { + numrefs=0, val2ref={}, havereq={}, + strbuf=sb_get_initial_strbuf() + } + return setmetatable(sb, savebuffer_mt) end diff --git a/polymer/eduke32/source/lunatic/test/test_rotspr.lua b/polymer/eduke32/source/lunatic/test/test_rotspr.lua index c595d4f2d..35aaa8af4 100644 --- a/polymer/eduke32/source/lunatic/test/test_rotspr.lua +++ b/polymer/eduke32/source/lunatic/test/test_rotspr.lua @@ -4,6 +4,8 @@ local con = require("con") local bit = require("bit") local math = require("math") +local printf = printf +local tostring = tostring local rs = con.rotatesprite local gameevent = gameevent @@ -19,10 +21,21 @@ local a_local_gamevar = "yes, this one too" test_gamevar2 = 'qwe' +a_table = { ELT1='ELT1!', ELT2=4444, ATAB={ q=333, [4]="!four!", w=444, ["true"]=false } } +local ref_to_a_table = a_table +ref_to_tabtab = ref_to_a_table.ATAB +local l_ref_to_tabtab = ref_to_tabtab + +ref_to_a_table.selfref = a_table +a_table[false] = { 1,2,3,con.actorvar(512) } + require "end_gamevars" --========== not_a_gamevar = "no" +printf("a_table.ATAB[4]=%s", tostring(a_table.ATAB[4])) +a_table.ATAB[4] = "!FOUR!" + local DOT1x5 = 3135 local BAR1x5 = 3163