Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple scripts in a single Lua state and working with _ENV

I'm currently learning how to use the Lua C API and while I've had success binding functions between C/C++ and Lua, I have a few questions:

  1. Is it a good idea to load multiple scripts into a single lua_State? Is there a way to close specific chunks? If a script is no longer in use how can I clear it from the lua_State while retaining everything else?

  2. What is the best way use scripts that may use the same name for functions/global variables? If I load all of them the newer definitions override the older ones.

    After reading online I think I need to separate each loaded chunk into different environments. The way I envision this working is each time a chunk is loaded I assign it a unique environment name, when I need to work with it it I just use that name to fetch the environment from the LUA_REGISTRYINDEX and perform the operation. So far I haven't figured out how to do this. There are examples online but they use Lua 5.1.

like image 440
inzombiak Avatar asked Apr 01 '16 12:04

inzombiak


2 Answers

Is it a good idea to load multiple scripts into a single lua_State?

Yes, definitely. Unless those scripts is unrelated and should run in multiple parallel threads.

Is there a way to close specific chunks?

Chunk is just a value of type "function". When you don't have that value stored anywhere - chunk will be garbage-collected.
Anything chunk produced - globals, or locals that has references somewhere outside - those will live on.

how to clear it from the lua_State while retaining everything else?

That depends on how do you see that chunk. Is it just a set of functions, or represents some entity with own state. If you don't create global functions and variables, then everything defined in separate script file will be local to chunk, and will be removed when there's no references to chunk left.

What is the best way use scripts that may use the same name for functions/global variables?

Consider rewriting your code. Do not create any globals, unless it's explicitly required to build communications with other parts of your program. Make variables local (owned by chunk), or store it in table/closure that will be returned by chunk as new object - chunk might be a factory producing new objects, and not jush the script.
Also Lua runs just faster with local variables.

The way I envision this working is each time a chunk is loaded I assign it a unique environment name

You should do that if scripts comes from outside - written by users, or received from other external sources. Sandboxing is cool, but there's no need in sandboxing if chunks is your internal stuff. Consider rewriting code without globals. Return some object (api table, or closure) if your chunk produces other objects - you can call that chunk many times without reloading it. Or save one global - module interface, if chunk represents Lua-like module. If you don't organize your code well, then you will be forced to use separate environments, and you'll have to prepare new environment for every script, copy basic stuff like print/pairs/string/etc. You'll have many breaks in run time until you figure out what's more is missing from new environment, and so on.

like image 74
Vlad Avatar answered Nov 02 '22 12:11

Vlad


After poking around some more I found what I think is the solution I was looking for. I'm not sure if this is the correct/best way to do it, but it works in my basic test case. @jpjacobs answer on this question helped a lot.

test1.lua

x = 1
function hi()
    print("hi1");
    print(x);
end
hi()

test2.lua

x =2
function hi()
    print("hi2");
    print(x);
end
hi()

main.cpp

int main(void)
{
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);

    char* file1 = "Rooms/test1.lua";
    char* file2 = "Rooms/test2.lua";

    //We load the file
    luaL_loadfile(L, file1);
    //Create _ENV tables
    lua_newtable(L);
    //Create metatable
    lua_newtable(L);
    //Get the global table
    lua_getglobal(L, "_G");
    lua_setfield(L, -2, "__index");
    //Set global as the metatable
    lua_setmetatable(L, -2);
    //Push to registry with a unique name.
    //I feel like these 2 steps could be merged or replaced but I'm not sure how
    lua_setfield(L, LUA_REGISTRYINDEX, "test1");
    //Retrieve it. 
    lua_getfield(L, LUA_REGISTRYINDEX, "test1");
    //Set the upvalue (_ENV)
    lua_setupvalue(L, 1, 1);
    //Run chunks
    lua_pcall(L, 0, LUA_MULTRET, 0);

    //Repeat
    luaL_loadfile(L, file2);
    lua_newtable(L);
    lua_newtable(L);
    lua_getglobal(L, "_G");
    lua_setfield(L, -2, "__index");
    lua_setmetatable(L, -2);
    lua_setfield(L, LUA_REGISTRYINDEX, "test2");
    lua_getfield(L, LUA_REGISTRYINDEX, "test2");
    lua_setupvalue(L, 1, 1);
    lua_pcall(L, 0, LUA_MULTRET, 0);

    //Retrieve the table containing the functions of the chunk
    lua_getfield(L, LUA_REGISTRYINDEX, "test1");
    //Get the function we want to call
    lua_getfield(L, -1, "hi");
    //Call it
    lua_call(L, 0, 0);
    //Repeat
    lua_getfield(L, LUA_REGISTRYINDEX, "test2");
    lua_getfield(L, -1, "hi");
    lua_call(L, 0, 0);
    lua_getfield(L, LUA_REGISTRYINDEX, "test2");
    lua_getfield(L, -1, "hi");
    lua_call(L, 0, 0);
    lua_getfield(L, LUA_REGISTRYINDEX, "test1");
    lua_getfield(L, -1, "hi");
    lua_call(L, 0, 0);

    lua_close(L);
}

Output:

hi1
1
hi2
2
hi1
1
hi2
2
hi2
2
hi1
1

I'm using Lua 5.3.2 with Visual Studio 2013 if that means anything.

This basic test case works as needed. I'll continue testing to see if any issues/improvements come up. If any sees any way I could improve this code or and glaring mistakes, please leave a comment.

like image 2
inzombiak Avatar answered Nov 02 '22 10:11

inzombiak