Search code examples
c++11lualua-apilua-c++-connection

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.


Solution

  • 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.