Lunatic: begin gamevar system. Not much there yet.

In Lunatic, gamevars (variables that are saved with savegames) are per-module.

git-svn-id: https://svn.eduke32.com/eduke32@3642 1a8010ca-5511-0410-912e-c29ae57300e0
This commit is contained in:
helixhorned 2013-04-05 17:53:06 +00:00
parent 49290a090c
commit 023ff8f190
2 changed files with 82 additions and 24 deletions

View file

@ -12,6 +12,7 @@ local math = math
local assert = assert local assert = assert
local error = error local error = error
local getfenv = getfenv
local getmetatable = getmetatable local getmetatable = getmetatable
local ipairs = ipairs local ipairs = ipairs
local loadstring = loadstring local loadstring = loadstring
@ -62,7 +63,7 @@ local setmtonce = defs_c.setmtonce
-- Must be after loading "defs_common" which redefines "print" to use -- Must be after loading "defs_common" which redefines "print" to use
-- OSD_Printf() -- OSD_Printf()
local print = print local print, printf = print, defs_c.printf
---=== EDuke32 game definitions ===--- ---=== EDuke32 game definitions ===---
@ -1250,22 +1251,26 @@ do
end end
end end
local function check_valid_modname(modname, errlev)
if (type(modname) ~= "string") then local package_loaded = {} -- false/true/table
error("module name must be a string", errlev+1) local modname_stack = {} -- [<depth>]=string
local module_gamevars = {} -- [<modname>] = { <gvname1>, <gvname2>, ... }
local function getcurmodname(thisfuncname)
if (#modname_stack == 0) then
error("'"..thisfuncname.."' must be called at the top level of a require'd file", 3)
-- ... as opposed to "at runtime".
end end
-- TODO: restrict valid names? return modname_stack[#modname_stack]
end end
local function errorf(level, fmt, ...) local function errorf(level, fmt, ...)
local errmsg = string.format(fmt, ...) local errmsg = string.format(fmt, ...)
error(errmsg, level+1) error(errmsg, level+1)
end end
local package_loaded = {}
local modname_stack = {}
local ERRLEV = 5 local ERRLEV = 5
local function readintostr(fn) local function readintostr(fn)
@ -1299,6 +1304,15 @@ local function readintostr(fn)
return ffi.string(str, sz) return ffi.string(str, sz)
end end
local required_module_mt = {
__newindex = function()
-- TODO: allow gamevars to be nil?
error("modifying module table forbidden", 2)
end,
__metatable = true,
}
-- The "require" function accessible to Lunatic code. -- The "require" function accessible to Lunatic code.
-- Base modules in allowed_modules are wrapped so that they cannot be -- Base modules in allowed_modules are wrapped so that they cannot be
-- modified, user modules are searched in the EDuke32 search -- modified, user modules are searched in the EDuke32 search
@ -1306,22 +1320,49 @@ end
-- * never messes with the global environment, it only returns the module. -- * never messes with the global environment, it only returns the module.
-- * allows passing varargs beyond the name to the module. -- * allows passing varargs beyond the name to the module.
local function our_require(modname, ...) local function our_require(modname, ...)
check_valid_modname(modname, 2) -- Check module name is valid first.
-- TODO: restrict valid names?
if (type(modname) ~= "string") then
error("module name must be a string", 2)
end
-- see whether it's a base module name first -- Handle the section between module() and require("end_gamevars").
if (modname == "end_gamevars") then
local thismodname = getcurmodname("require")
if (module_gamevars[thismodname] ~= nil) then
error("\"require 'end_gamevars'\" must be called at most once per require'd file", 2)
end
local gvnames = {}
for name in pairs(getfenv(2)) do
gvnames[#gvnames] = name
if (ffiC._DEBUG_LUNATIC ~= 0) then
printf("MODULE %s GAMEVAR %s", thismodname, name)
end
end
module_gamevars[thismodname] = gvnames
return
end
-- See whether it's a base module name.
if (allowed_modules[modname] ~= nil) then if (allowed_modules[modname] ~= nil) then
return allowed_modules[modname] return allowed_modules[modname]
end end
--- search user modules --- Search user modules...
local omod = package_loaded[modname] local omod = package_loaded[modname]
if (omod ~= nil) then if (omod ~= nil) then
if (omod==true) then if (omod==false) then
error("Loop while loading modules", ERRLEV-1) error("Loop while loading modules", ERRLEV-1)
end end
-- already loaded -- already loaded
assert(omod==true or type(omod)=="table")
return omod return omod
end end
@ -1336,7 +1377,7 @@ local function our_require(modname, ...)
errorf(ERRLEV-1, "Couldn't load \"%s\": %s", modname, errmsg) errorf(ERRLEV-1, "Couldn't load \"%s\": %s", modname, errmsg)
end end
package_loaded[modname] = true package_loaded[modname] = false -- 'not yet loaded'
table.insert(modname_stack, modname) table.insert(modname_stack, modname)
-- Run the module code! -- Run the module code!
@ -1348,14 +1389,9 @@ local function our_require(modname, ...)
if (type(modtab) == "table") then if (type(modtab) == "table") then
-- Protect module table if there is one... -- Protect module table if there is one...
local mt = { setmetatable(modtab, required_module_mt)
__index = modtab, else
__newindex = function(tab,idx,val) package_loaded[modname] = true
error("modifying module table forbidden", 2)
end,
}
setmetatable(modtab, mt)
end end
return modtab return modtab
@ -1370,12 +1406,18 @@ local module_mt = {
-- Our 'module' replacement doesn't get the module name from the function args -- Our 'module' replacement doesn't get the module name from the function args
-- since a malicious user could remove other loaded modules this way. -- since a malicious user could remove other loaded modules this way.
-- Also, our 'module' has no varargs ("option functions" in Lua). -- Also, our 'module' takes no varargs ("option functions" in Lua).
-- TODO: make transactional? -- TODO: make transactional?
local function our_module() local function our_module()
local modname = modname_stack[#modname_stack] if (#modname_stack == 0) then
if (type(modname) ~= "string") then
error("'module' must be called at the top level of a require'd file", 2) error("'module' must be called at the top level of a require'd file", 2)
-- ... as opposed to "at runtime".
end
local modname = getcurmodname("module")
if (type(package_loaded[modname])=="table") then
error("'module' must be called at most once per require'd file", 2)
end end
local M = setmetatable({}, module_mt) local M = setmetatable({}, module_mt)
@ -1411,6 +1453,7 @@ G_.ipairs = ipairs
G_.pairs = pairs G_.pairs = pairs
G_.pcall = pcall G_.pcall = pcall
G_.print = print G_.print = print
G_.printf = printf
G_.module = our_module G_.module = our_module
G_.next = next G_.next = next
G_.require = our_require G_.require = our_require
@ -1881,6 +1924,8 @@ setmetatable(
__index = function (_, n) __index = function (_, n)
error("attempt to read undeclared variable '"..n.."'", 2) error("attempt to read undeclared variable '"..n.."'", 2)
end, end,
__metatable = true,
}) })
-- Change the environment of the running Lua thread so that everything -- Change the environment of the running Lua thread so that everything

View file

@ -1,4 +1,5 @@
local require = require
local con = require("con") local con = require("con")
local bit = require("bit") local bit = require("bit")
local math = require("math") local math = require("math")
@ -47,3 +48,15 @@ local function rotatesprite_test()
end end
gameevent(gv.EVENT_DISPLAYREST, rotatesprite_test) gameevent(gv.EVENT_DISPLAYREST, rotatesprite_test)
module(...) --====================
local not_a_gamevar = "nyet"
test_gamevar = 123
test_gamevar2 = 'qwe'
require "end_gamevars" --==========
not_a_gamevar2 = "no"