Lunatic: allow serialization of tables in more general cases.

git-svn-id: https://svn.eduke32.com/eduke32@3908 1a8010ca-5511-0410-912e-c29ae57300e0
This commit is contained in:
helixhorned 2013-06-28 14:07:39 +00:00
parent bb71052cb7
commit 7cb94070e1
4 changed files with 94 additions and 38 deletions

View file

@ -2051,7 +2051,7 @@ do
-- XXX: System gamevars? Most of them ought to be saved with C data. -- XXX: System gamevars? Most of them ought to be saved with C data.
for modname, modvars in pairs(module_gamevars) do for modname, modvars in pairs(module_gamevars) do
sb:addrawf("if (N==%q) then", modname) sb:startmod(modname)
-- Handle global gamevars first. -- Handle global gamevars first.
for i=1,#modvars do for i=1,#modvars do
@ -2088,7 +2088,7 @@ do
end end
end end
sb:addraw("end") sb:endmod()
end end
-- Get the whole code as a string. -- Get the whole code as a string.

View file

@ -177,9 +177,13 @@ also be *`local`*.
Game variables may take on only values of types that Lunatic knows how to Game variables may take on only values of types that Lunatic knows how to
serialize into savegames. These are the following: serialize into savegames. These are the following:
* booleans, numbers, and strings * Booleans, numbers, and strings, collectively called the _basic types_
* tables, but with restrictions on their contents and topology described below (TODO) * Custom Lunatic types that are labeled _serializeable_ in their documentation
* 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"] // [icon="icons/din_w_collapse.png"]

View file

@ -4,6 +4,8 @@
local string = require("string") local string = require("string")
local table = require("table") local table = require("table")
local format = string.format
local assert = assert local assert = assert
local getmetatable = getmetatable local getmetatable = getmetatable
local pairs = pairs local pairs = pairs
@ -31,7 +33,7 @@ local function basicSerialize(o)
return o and "t" or "f" return o and "t" or "f"
elseif (type(o) == "string") then elseif (type(o) == "string") then
-- TODO: return refname if it's shorter -- TODO: return refname if it's shorter
return string.format("%q", o) return format("%q", o)
end end
end end
@ -45,13 +47,52 @@ end
-- user Lunatic environment. -- user Lunatic environment.
local savebuffer_mt = { local savebuffer_mt = {
__index = { __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 <value> that can be referenced by -- Add an entry of Lua object <value> that can be referenced by
-- <refcode> (which should be Lua code that can appear on both sides of -- <lhscode> on the left hand side of an assignment.
-- an assignment).
-- Returns 'true' if <value> cannot be serialized. -- Returns 'true' if <value> cannot be serialized.
add = function(self, refcode, value) add = function(self, lhscode, value)
local valcode = basicSerialize(value) local valcode = basicSerialize(value)
local havetab = false
if (valcode == nil) then if (valcode == nil) then
-- <value> is a not a 'basic' Lua object, but one passed by -- <value> is a not a 'basic' Lua object, but one passed by
@ -59,26 +100,22 @@ local savebuffer_mt = {
if (not self.val2ref[value]) then if (not self.val2ref[value]) then
-- Object is being serialized for the first time. -- 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 if (isSerializeable(value)) then
-- We have a serializeable object from Lunatic -- We have a serializeable object from Lunatic
-- (e.g. actorvar). -- (e.g. actorvar).
self:getRequire(value)
-- First, get the code necessary to create this object, self:emitT(refcode, value:_serialize(), value)
-- usually 'require'ing a module into a local variable. valcode = refcode
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 elseif (type(value)=="table") then
-- We have a Lua table.
havetab = true
-- Create a new table for this gamevar. -- 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 for k,v in pairs(value) do
local keystr = basicSerialize(k) local keystr = basicSerialize(k)
@ -86,32 +123,29 @@ local savebuffer_mt = {
return true return true
end end
if (type(v)=="table" and not isSerializeable(v)) then
-- nested tables: NYI
return true
end
-- Generate the name under which the table element -- Generate the name under which the table element
-- is referenced. -- is referenced.
local refcode2 = string.format("%s[%s]", refcode, keystr) local refcode2 = format("%s[%s]", refcode, keystr)
-- Recurse! -- Recurse!
self:add(refcode2, v) if (self:add(refcode2, v)) then
return true
end
end end
valcode = refcode
else else
-- We have anything else: can't serialize. -- We have anything else: can't serialize.
return true return true
end end
self.val2ref[value] = refcode
else else
-- Object was previously serialized, get Lua expression it
-- can be referenced with.
valcode = self.val2ref[value] valcode = self.val2ref[value]
end end
end end
if (not havetab) then self:emitAssign(lhscode, valcode)
self:addraw(refcode.."="..valcode)
end
end, end,
-- Add a single string to the buffer. -- Add a single string to the buffer.
@ -121,7 +155,7 @@ local savebuffer_mt = {
-- Add a single formatted string to the buffer. -- Add a single formatted string to the buffer.
addrawf = function(self, fmt, ...) addrawf = function(self, fmt, ...)
self:addraw(string.format(fmt, ...)) self:addraw(format(fmt, ...))
end, end,
-- Get the Lua code recreating the values as a string. -- Get the Lua code recreating the values as a string.
@ -141,9 +175,14 @@ end
-- Create a new savebuffer object. -- Create a new savebuffer object.
function savebuffer() function savebuffer()
-- .numrefs: how many table or cdata objects we have serialized
-- .val2ref: [<Lua object>] = <Lua code string> -- .val2ref: [<Lua object>] = <Lua code string>
-- .havereq = [<string>] = true -- .havereq = [<string>] = true
-- .strbuf: array of Lua code pieces -- .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) return setmetatable(sb, savebuffer_mt)
end end

View file

@ -4,6 +4,8 @@ local con = require("con")
local bit = require("bit") local bit = require("bit")
local math = require("math") local math = require("math")
local printf = printf
local tostring = tostring
local rs = con.rotatesprite local rs = con.rotatesprite
local gameevent = gameevent local gameevent = gameevent
@ -19,10 +21,21 @@ local a_local_gamevar = "yes, this one too"
test_gamevar2 = 'qwe' 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" --========== require "end_gamevars" --==========
not_a_gamevar = "no" not_a_gamevar = "no"
printf("a_table.ATAB[4]=%s", tostring(a_table.ATAB[4]))
a_table.ATAB[4] = "!FOUR!"
local DOT1x5 = 3135 local DOT1x5 = 3135
local BAR1x5 = 3163 local BAR1x5 = 3163