It was thus said that the Great Soni L. once stated:
On 23/06/15 04:00 AM, Dirk Laurie wrote:
2015-06-23 3:51 GMT+02:00 Soni L. <fakedme@gmail.com>:
Why can't we get a way to make independent Lua states from Lua,
anyway? (or rather with isolated registry and global metatables - this
means the GC would be shared between them, thus allowing objects
to be shared between them, but they wouldn't share the same global
metatables or the same globals table). It would open up great
possibilities!
The presently available language features in that direction are:
1. debug.getregistry()[1], which contains the Lua state, and
debug.getregistry()[2], which contains the global table. These
are assignable. By "global metatable", do you mean things like
getmetatable("")?
2. Coroutines.
3. Loading code with a specified _ENV.
What additional functions, callable from Lua, would you need
for your envisaged ability? Can you describe them in terms of
currently available API routines, so that one can knock together
a little module in order to experiment with the idea?
It sounds like what you want is to virtualize Lua within Lua. The silly
answer to that is to wrap the Lua API in a Lua module, so you can do:
gL = lua.Lnewstate()
gL:gc('stop')
gL:Lopenlibs()
gL:gc('start')
rc = gL:Lloadfilex('foobar.lua')
if rc ~= 0 then
error()
end
rc = gL:pcall(1,1)
if rc ~= 0 then
errmsg = gL:tostring(-1)
print(errmsg)
end
I'm not sure if that's what you are looking for though, as gL would be a
completely separate Lua state and passing data between the two is a manual
process (especially tables---basically, you have to serialize and
deserialize).
debug.getmetatable(luastate, object) and debug.setmetatable(luastate,
object, newmt) (luastate would only be relevant for nil, boolean,
string, function, lightuserdata, thread; all other types have per-object
- instead of per-state - metatables), debug.getregistry(luastate) and a
debug.setregistry(luastate, newregistry) that lets you replace the
registry (e.g. replace a coroutine's registry so that it can use your
own custom registry, with a custom global environment - PROBABLY
wouldn't accept the main thread as an argument - these can NOT be
simulated, but my "250 LOC beast" can simulate per-thread
debug.(s/g)etmetatable)
By default every new thread (aka coroutine) would inherit the creator's
metatables and registry.
A lua coroutine is represented by a lua_State:
struct lua_State {
/* ... snip ... */
global_State *l_G;
/* ... snip ... */
};
Each lua_State points to the same global state structure, which looks
like (Lua 5.3):
typedef struct global_State {
lua_Alloc frealloc; /* function to reallocate memory */
void *ud; /* auxiliary data to 'frealloc' */
lu_mem totalbytes; /* number of bytes currently allocated - GCdebt */
l_mem GCdebt; /* bytes allocated not yet compensated by the collector */
lu_mem GCmemtrav; /* memory traversed by the GC */
lu_mem GCestimate; /* an estimate of the non-garbage memory in use */
stringtable strt; /* hash table for strings */
TValue l_registry;
unsigned int seed; /* randomized seed for hashes */
lu_byte currentwhite;
lu_byte gcstate; /* state of garbage collector */
lu_byte gckind; /* kind of GC running */
lu_byte gcrunning; /* true if GC is running */
GCObject *allgc; /* list of all collectable objects */
GCObject **sweepgc; /* current position of sweep in list */
GCObject *finobj; /* list of collectable objects with finalizers */
GCObject *gray; /* list of gray objects */
GCObject *grayagain; /* list of objects to be traversed atomically */
GCObject *weak; /* list of tables with weak values */
GCObject *ephemeron; /* list of ephemeron tables (weak keys) */
GCObject *allweak; /* list of all-weak tables */
GCObject *tobefnz; /* list of userdata to be GC */
GCObject *fixedgc; /* list of objects not to be collected */
struct lua_State *twups; /* list of threads with open upvalues */
Mbuffer buff; /* temporary buffer for string concatenation */
unsigned int gcfinnum; /* number of finalizers to call in each GC step */
int gcpause; /* size of pause between successive GCs */
int gcstepmul; /* GC 'granularity' */
lua_CFunction panic; /* to be called in unprotected errors */
struct lua_State *mainthread;
const lua_Number *version; /* pointer to version number */
TString *memerrmsg; /* memory-error message */
TString *tmname[TM_N]; /* array with tag-method names */
struct Table *mt[LUA_NUMTAGS]; /* metatables for basic types */
} global_State;
It appears that all one has to do is allocate a new global state, copy all
the fields, then set, say, strt, l_registry, mt and possibly seed with new
values. The only thing that gives me pause though, is the GC related
fields, as the garbage collector might get confused as it doesn't seem to be
an easy way to share the GC between global states. So you either have to
move the fields you want to override to the lua_State, or make a
global_global state to be able to share the GC between global states. I
think the safest thing would be to move strt, l_registry, mt and seed to
lua_State (and all the code changes that requires) as that might have the
least impact code change wise, as well as a minimal impact on performance
(the GC still uses one pointer dereference to get to its data rather than
two). On the downside, lua_States will now take more memory.
-spc (Have you considered writing Lua in LuaJIT?)