diff --git a/polymer/eduke32/source/lunatic/defs.ilua b/polymer/eduke32/source/lunatic/defs.ilua index 0d83eaf96..f10756e9c 100644 --- a/polymer/eduke32/source/lunatic/defs.ilua +++ b/polymer/eduke32/source/lunatic/defs.ilua @@ -9,6 +9,7 @@ local ffiC = ffi.C local CF = CF local bit = bit +local coroutine = coroutine local string = string local table = table local math = math @@ -578,6 +579,8 @@ int32_t g_elCONSize; char *g_elCON; void El_SetCON(const char *conluacode); +char *g_elSavecode; +void El_FreeSaveCode(void); const char *(*El_SerializeGamevars)(int32_t *slenptr); const char *s_buildRev; @@ -1385,6 +1388,7 @@ local package_loaded = {} -- [] = false/true/table local modname_stack = {} -- []=string local module_gamevars = {} -- [] = { , , ... } local module_gvlocali = {} -- [] = { , } +local module_thread = {} -- [] = local function getcurmodname(thisfuncname) if (#modname_stack == 0) then @@ -1421,7 +1425,7 @@ local function getnumlocals(l) for i=1,200 do -- level: -- 0 is getlocal() itself. - -- 1 is this function. + -- 1 is this function (getnumlocals). -- 2 is the function calling getnumlocals() -- 3 is the function calling that one. if (debug.getlocal(3, i) == nil) then @@ -1438,6 +1442,27 @@ local required_module_mt = { __metatable = true, } +-- Will contain a function to restore gamevars when running from savegame +-- restoration. See SAVEFUNC_ARGS for its arguments. +local g_restorefunc = nil + +-- Local gamevar restoration function run from +-- our_require('end_gamevars') <- [user module]. +local function restore_local(li, lval) + -- level: + -- 0 is getlocal() itself. + -- 1 is this function (restore_local). + -- 2 is the function calling restore_local(), the savecode. + -- 3 is the function calling the savecode, our_require. + -- 4 is the function calling our_require, the module function. + if (ffiC._DEBUG_LUNATIC ~= 0) then + printf("Restoring index #%d (%s) with value %s", + li, debug.getlocal(4, li), tostring(lval)) + end + + assert(debug.setlocal(4, li, lval)) +end + -- The "require" function accessible to Lunatic code. -- Base modules in allowed_modules are wrapped so that they cannot be -- modified, user modules are searched in the EDuke32 search @@ -1475,11 +1500,24 @@ local function our_require(modname, ...) gvmodi[2] = getnumlocals() if (ffiC._DEBUG_LUNATIC ~= 0) then - printf("Module '%s' has %d locals, index %d to %d", thismodname, - gvmodi[2]-gvmodi[1]+1, gvmodi[1], gvmodi[2]) + local numlocals = gvmodi[2]-gvmodi[1]+1 + if (numlocals > 0) then + printf("Module '%s' has %d locals, index %d to %d", + thismodname, numlocals, gvmodi[1], gvmodi[2]) + end end - return + -- Potentially restore gamevars. + if (g_restorefunc) then + local modtab = package_loaded[thismodname] + assert(type(modtab)=="table") + -- SAVEFUNC_ARGS. + g_restorefunc(thismodname, modtab, restore_local) + end + + -- Return whether we're NOT running from a savegame restore in the + -- second outarg. (Lunatic-private!) + return nil, (g_restorefunc==nil) end -- See whether it's a base module name. @@ -1513,6 +1551,10 @@ local function our_require(modname, ...) errorf(ERRLEV-1, "Couldn't open file \"%s\"", modfn) end + -- Implant code that yields the module thread just before it would return + -- otherwise. + str = str.."\nrequire('coroutine').yield()" + local modfunc, errmsg = loadstring(str, modfn) if (modfunc == nil) then errorf(ERRLEV-1, "Couldn't load \"%s\": %s", modname, errmsg) @@ -1521,18 +1563,48 @@ local function our_require(modname, ...) package_loaded[modname] = false -- 'not yet loaded' table.insert(modname_stack, modname) - -- Run the module code! - modfunc(modname, ...) -- TODO: call protected and report errors here later? + -- Run the module code in a separate Lua thread! + local modthread = coroutine.create(modfunc) + local ok, retval = coroutine.resume(modthread, modname, ...) + + if (not ok) then + errorf(ERRLEV-1, "Failed running \"%s\": %s", modname, retval) + end table.remove(modname_stack) local modtab = package_loaded[modname] + if (type(modtab) ~= "table") then + -- The module didn't call our 'module'. Check if it returned a table. + -- In that case, the coroutine has finished its main function and has + -- not reached our implanted 'yield'. + if (coroutine.status(modthread)=="dead" and type(retval)=="table") then + modtab = retval + package_loaded[modname] = modtab + else + package_loaded[modname] = true + end + end + if (type(modtab) == "table") then - -- Protect module table if there is one... + -- Protect module table in any case (i.e. either if the module used our + -- 'module' or if it returned a table). setmetatable(modtab, required_module_mt) - else - package_loaded[modname] = true + end + + local gvmodi = module_gvlocali[modname] + + if (gvmodi and gvmodi[2]>=gvmodi[1]) then + if (coroutine.status(modthread)=="suspended") then + -- Save off the suspended thread so that we may get its locals later on. + -- It is never resumed, but only ever used for debug.getlocal(). + module_thread[modname] = modthread + + if (ffiC._DEBUG_LUNATIC ~= 0) then + printf("Keeping coroutine for module \"%s\"", modname) + end + end end return modtab @@ -1557,7 +1629,7 @@ local function our_module() local modname = getcurmodname("module") - if (type(package_loaded[modname])=="table") then + if (package_loaded[modname]) then error("'module' must be called at most once per require'd file", 2) end @@ -1902,12 +1974,16 @@ do ffiC.El_SerializeGamevars = function(slenptr) local sb = savegame.savebuffer() - sb:addraw("local M") + -- Module name, module table, restore_local. See SAVEFUNC_ARGS. + sb:addraw("local N,M,F=...") + -- A local to temporarily hold module locals. + sb:addraw("local L") - -- XXX: System gamevars? + -- XXX: System gamevars? Most of them ought to be saved with C data. for modname, modvars in pairs(module_gamevars) do - -- NOTE: when emitting 'require' code, reverse '.' -> '/' substitution. - sb:addrawf("M=require%q", modname:gsub("/",".")) + sb:addrawf("if (N==%q) then", modname) + + -- Handle global gamevars first. for i=1,#modvars do local varname = modvars[i] -- Serialize gamevar named 'varname' from module named 'modname'. @@ -1920,6 +1996,29 @@ do return (modname==CON_MODULE_NAME and "" or modname).."."..varname end end + + local modthread = module_thread[modname] + + if (modthread) then + -- Handle local gamevars. + local gvmodi = module_gvlocali[modname] + + for li=gvmodi[1],gvmodi[2] do + -- Serialize local with index
  • . Get its value first. + local lname, lval = debug.getlocal(modthread, 1, li) + + if (sb:add("L", lval)) then + -- We couldn't serialize that gamevar. + slenptr[0] = -1 + return "local "..modname.."."..lname + end + + -- Call restore_local. + sb:addrawf("F(%d,L)", li) + end + end + + sb:addraw("end") end -- Get the whole code as a string. @@ -2060,6 +2159,19 @@ setmetatable( -- environment earlier. setfenv(0, _G) +do + -- If we're running from a savegame restoration, create the restoration + -- function. Must be here, after the above setfenv(), because it must be + -- created in this protected ('user') context! + local cstr = ffiC.g_elSavecode + if (cstr~=nil) then + local restorecode = ffi.string(cstr) + ffiC.El_FreeSaveCode() + + g_restorefunc = assert(loadstring(restorecode)) + end +end + -- Run the CON code translated into Lua. if (concode) then local confunc, conerrmsg = loadstring(concode, "CON") @@ -2067,10 +2179,11 @@ if (concode) then error("Failure loading translated CON code: "..conerrmsg, 0) end - local conmodule, conlabels = confunc() - - package_loaded[CON_MODULE_NAME] = conmodule - module_gamevars[CON_MODULE_NAME] = { "A", "V" } -- See CON_GAMEVARS is lunacon.lua. + -- Emulate our 'require' for the CON module when running it, for + -- our_module() which is called from the generated Lua code. + table.insert(modname_stack, CON_MODULE_NAME) + local conlabels = confunc() + table.remove(modname_stack) -- Set up CON.DEFS module, providing access to labels defined in CON from Lua. local mt = { __index = conlabels, __newindex = basemodule_newindex } diff --git a/polymer/eduke32/source/lunatic/dynsymlist b/polymer/eduke32/source/lunatic/dynsymlist index 45e7dbba6..ea9140c0c 100644 --- a/polymer/eduke32/source/lunatic/dynsymlist +++ b/polymer/eduke32/source/lunatic/dynsymlist @@ -93,6 +93,8 @@ g_elCONSize; g_elCON; El_SetCON; +g_elSavecode; +El_FreeSaveCode; El_SerializeGamevars; s_buildRev; diff --git a/polymer/eduke32/source/lunatic/lunacon.lua b/polymer/eduke32/source/lunatic/lunacon.lua index ab39c9822..e6d775f48 100644 --- a/polymer/eduke32/source/lunatic/lunacon.lua +++ b/polymer/eduke32/source/lunatic/lunacon.lua @@ -177,6 +177,7 @@ local function new_initial_codetab() -- mapping system. return { -- Requires. + "local require=require", "local _con, _bit, _math = require'con', require'bit', require'math'", "local _xmath, _geom = require'xmath', require'geom'", @@ -191,10 +192,17 @@ local function new_initial_codetab() "local _band, _bor, _bxor = _bit.band, _bit.bor, _bit.bxor", "local _lsh, _rsh, _arsh = _bit.lshift, _bit.rshift, _bit.arshift", - -- Switch function table, indexed by global switch sequence number: - "local _SW = {}", - -- CON "states" (subroutines), gamevars and gamearrays (see mangle_name()) - "local _F,_V,_A={},{},{}", + -- * CON "states" (subroutines) and + -- * Switch function table, indexed by global switch sequence number: + "local _F,_SW = {},{}", + + -- CON gamevars and gamearrays (see mangle_name()), set up for + -- restoration from savegames. + "module(...)", + "_V,_A={},{}", + "-- NOTE to the reader: This require's result is Lunatic-private API! DO NOT USE!", + "local _dummy,_S=require'end_gamevars'", + "local _V,_A=_V,_A", -- Static ivec3s so that no allocations need to be made. "local _IVEC = { _geom.ivec3(), _geom.ivec3() }", @@ -1220,7 +1228,9 @@ function Cmd.gamearray(identifier, initsize) local ga = { name=mangle_name(identifier, "A"), size=initsize } g_gamearray[identifier] = ga + addcode("if _S then") addcodef("%s=_con._gamearray(%d)", ga.name, initsize) + addcode("end") end function Cmd.gamevar(identifier, initval, flags) @@ -1272,6 +1282,7 @@ function Cmd.gamevar(identifier, initval, flags) local linestr = "--"..getlinecol(g_lastkwpos) -- Emit code to set the variable at Lua parse time. + -- XXX: How does this interact with savegame restoration? if (bit.band(oflags, GVFLAG.PERPLAYER) ~= 0) then -- Replace player index by 0. PLAYER_0. -- TODO_MP: init for all players. @@ -1300,7 +1311,8 @@ function Cmd.gamevar(identifier, initval, flags) local gv = { name=mangle_name(identifier, "V"), flags=flags } g_gamevar[identifier] = gv - -- TODO: Write gamevar system on the Lunatic side and hook it up. + addcode("if _S then") + if (bit.band(flags, GVFLAG.PERX_MASK)==GVFLAG.PERACTOR) then addcodef("%s=_con.actorvar(%d)", gv.name, initval) elseif (bit.band(flags, GVFLAG.PERX_MASK)==GVFLAG.PERPLAYER and g_cgopt["playervar"]) then @@ -1309,6 +1321,8 @@ function Cmd.gamevar(identifier, initval, flags) else addcodef("%s=%d", gv.name, initval) end + + addcode("end") end function Cmd.dynamicremap() @@ -3325,12 +3339,8 @@ end -- : Get line info? local function get_code_string(codetab, lineinfop) - -- Finalize translated code: return table containing gamevar and gamearray - -- tables. CON_GAMEVARS. - codetab[#codetab+1] = "return { V=_V, A=_A }" - -- Return defined labels in a table... - codetab[#codetab+1] = ",{" + codetab[#codetab+1] = "return {" for label, val in pairs(g_labeldef) do -- ... skipping 'NO' and those that are gamevars in C-CON. if (g_labeltype[label]==LABEL.NUMBER and not g_labelspecial[label]) then diff --git a/polymer/eduke32/source/lunatic/savegame.lua b/polymer/eduke32/source/lunatic/savegame.lua index 6800cdf2e..23dc95398 100644 --- a/polymer/eduke32/source/lunatic/savegame.lua +++ b/polymer/eduke32/source/lunatic/savegame.lua @@ -62,7 +62,6 @@ local savebuffer_mt = { 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. @@ -78,9 +77,8 @@ local savebuffer_mt = { -- 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) + -- Create a new table for this gamevar. + self:addrawf("%s={}", refcode) for k,v in pairs(value) do local keystr = basicSerialize(k) @@ -138,11 +136,6 @@ local function sb_get_initial_strbuf() return { "local nan, inf = 0/0, 1/0", "local t, f = true, false", - "local pairs = assert(pairs)", - -- Clear table function: - "local function ct(t)", - " for k in pairs(t) do t[k]=nil end", - "end", } end diff --git a/polymer/eduke32/source/lunatic/test/delmusicsfx.lua b/polymer/eduke32/source/lunatic/test/delmusicsfx.lua index f27fa921f..09971077e 100644 --- a/polymer/eduke32/source/lunatic/test/delmusicsfx.lua +++ b/polymer/eduke32/source/lunatic/test/delmusicsfx.lua @@ -1,6 +1,8 @@ local require = require -local con = require "con" + +local string = require("string") +local con = require("con") local gv = gv local sprite = sprite @@ -35,6 +37,7 @@ require "end_gamevars" -- refer to locals defined prior to the gamevar section in it. local tag = tag +local Q = 1200 gameevent{"JUMP", actor.FLAGS.chain_beg, function(aci, pli) @@ -75,3 +78,15 @@ function(aci, pli) insp = not insp end} + +-- Display the number of times we jumped on the screen. +gameevent +{ + "DISPLAYREST", + + function() + con._definequote(Q, string.format("jumped %d times", ournumjumps)) + -- NOTE: uses INTERNAL interface, don't copy! + con._minitext(160, 10, Q, 0,0) + end +} diff --git a/polymer/eduke32/source/savegame.c b/polymer/eduke32/source/savegame.c index 3805325fd..0b130ff98 100644 --- a/polymer/eduke32/source/savegame.c +++ b/polymer/eduke32/source/savegame.c @@ -1700,17 +1700,11 @@ static uint8_t *dosaveplayer2(FILE *fil, uint8_t *mem) PRINTSIZE("ud"); mem=writespecdata(svgm_secwsp, fil, mem); // sector, wall, sprite PRINTSIZE("sws"); - mem=writespecdata(svgm_script, fil, mem); // script - PRINTSIZE("script"); - mem=writespecdata(svgm_anmisc, fil, mem); // animates, quotes & misc. - PRINTSIZE("animisc"); - -#if !defined LUNATIC - Gv_WriteSave(fil, 1); // gamevars - mem=writespecdata(svgm_vars, 0, mem); - PRINTSIZE("vars"); -#else +#ifdef LUNATIC { + // Serialize Lunatic gamevars. When loading, the restoration code must + // be present before Lua state creation in svgm_script, so save it + // right before, too. int32_t slen, slen_ext; const char *svcode = El_SerializeGamevars(&slen); @@ -1722,21 +1716,91 @@ static uint8_t *dosaveplayer2(FILE *fil, uint8_t *mem) return mem; } - // TODO: compress text. fwrite("\0\1LunaGVAR\3\4", 12, 1, fil); slen_ext = B_LITTLE32(slen); fwrite(&slen_ext, sizeof(slen_ext), 1, fil); - fwrite(svcode, slen, 1, fil); + dfwrite(svcode, 1, slen, fil); // cnt and sz swapped g_savedOK = 1; } #endif + mem=writespecdata(svgm_script, fil, mem); // script + PRINTSIZE("script"); + mem=writespecdata(svgm_anmisc, fil, mem); // animates, quotes & misc. + PRINTSIZE("animisc"); + +#if !defined LUNATIC + Gv_WriteSave(fil, 1); // gamevars + mem=writespecdata(svgm_vars, 0, mem); + PRINTSIZE("vars"); +#endif return mem; } -#define LOADRD(ptr, sz, cnt) (kdfread(ptr,sz,cnt,fil)!=(cnt)) -#define LOADRDU(ptr, sz, cnt) (kread(fil,ptr,(sz)*(cnt))!=(sz)*(cnt)) +#ifdef LUNATIC +char *g_elSavecode = NULL; + +static int32_t El_ReadSaveCode(int32_t fil) +{ + // Read Lua code to restore gamevar values from the savegame. + // It will be run from Lua with its state creation later on. + + char header[12]; + int32_t slen; + + if (kread(fil, header, 12) != 12) + { + OSD_Printf("doloadplayer2: failed reading Lunatic gamevar header.\n"); + return -100; + } + + if (Bmemcmp(header, "\0\1LunaGVAR\3\4", 12)) + { + OSD_Printf("doloadplayer2: Lunatic gamevar header doesn't match.\n"); + return -101; + } + + if (kread(fil, &slen, sizeof(slen)) != sizeof(slen)) + { + OSD_Printf("doloadplayer2: failed reading Lunatic gamevar string size.\n"); + return -102; + } + + slen = B_LITTLE32(slen); + if (slen < 0) + { + OSD_Printf("doloadplayer2: invalid Lunatic gamevar string size %d.\n", slen); + return -103; + } + + if (slen > 0) + { + char *svcode = Bmalloc(slen+1); + if (svcode == NULL) + G_GameExit("OUT OF MEMORY in doloadplayer2()."); + + if (kdfread(svcode, 1, slen, fil) != slen) // cnt and sz swapped + { + OSD_Printf("doloadplayer2: failed reading Lunatic gamevar restoration code.\n"); + Bfree(svcode); + return -104; + } + + svcode[slen] = 0; + g_elSavecode = svcode; + } + + return 0; +} + +void El_FreeSaveCode(void) +{ + // Free Lunatic gamevar savegame restoration Lua code. + Bfree(g_elSavecode); + g_elSavecode = NULL; +} +#endif static int32_t doloadplayer2(int32_t fil, uint8_t **memptr) { @@ -1749,6 +1813,13 @@ static int32_t doloadplayer2(int32_t fil, uint8_t **memptr) PRINTSIZE("ud"); if (readspecdata(svgm_secwsp, fil, &mem)) return -4; PRINTSIZE("sws"); +#ifdef LUNATIC + { + int32_t ret = El_ReadSaveCode(fil); + if (ret < 0) + return ret; + } +#endif if (readspecdata(svgm_script, fil, &mem)) return -5; PRINTSIZE("script"); if (readspecdata(svgm_anmisc, fil, &mem)) return -6; @@ -1769,57 +1840,6 @@ static int32_t doloadplayer2(int32_t fil, uint8_t **memptr) } } PRINTSIZE("vars"); -#else - { - // Read Lua code to restore gamevar values from the savegame and run it. - - char header[12]; - int32_t slen; - - if (kread(fil, header, 12) != 12) - { - OSD_Printf("doloadplayer2: failed reading Lunatic gamevar header.\n"); - return -100; - } - - if (Bmemcmp(header, "\0\1LunaGVAR\3\4", 12)) - { - OSD_Printf("doloadplayer2: Lunatic gamevar header doesn't match.\n"); - return -101; - } - - if (kread(fil, &slen, sizeof(slen)) != sizeof(slen)) - { - OSD_Printf("doloadplayer2: failed reading Lunatic gamevar string size.\n"); - return -102; - } - - slen = B_LITTLE32(slen); - if (slen < 0) - { - OSD_Printf("doloadplayer2: invalid Lunatic gamevar string size %d.\n", slen); - return -103; - } - - if (slen > 0) - { - char *svcode = Bmalloc(slen); - if (svcode == NULL) - G_GameExit("OUT OF MEMORY in doloadplayer2()."); - - if (kread(fil, svcode, slen) != slen) - { - OSD_Printf("doloadplayer2: failed reading Lunatic gamevar restoration code.\n"); - return -104; - } - - if (L_RunString(&g_ElState, svcode, 0, slen, "luaload")) - { - OSD_Printf("doloadplayer2: failed restoring Lunatic gamevars.\n"); - return -105; - } - } - } #endif if (memptr)