在单个 Lua 状态中运行多个脚本并使用 _ENV

我目前正在学习使用 Lua C API,虽然我已经成功地在 C/C++ 和 Lua 之间绑定了函数,但是我有几个问题:

  1. 将多个脚本加载到单个 lua_State 中是否是一个好主意?是否有一种关闭特定块的方法?如果脚本不再使用,如何从 lua_State 中清除它而保留其他所有内容?

  2. 如何最好地使用可能对函数/全局变量使用相同名称的脚本?如果我加载它们所有的话,新的定义将覆盖旧的定义。

    在阅读在线文档后,我认为需要将每个加载的块 分隔开成不同的环境。我设想的方式是,每次加载块时,我都会为它指定一个唯一的环境名称,当我 需要使用它时,我只需使用该名称从 LUA_REGISTRYINDEX 中获取该 环境并执行操作。到目前为止,我还没有想出如何去做。在线上有例子,但是它们使用的是 Lua 5.1。

点赞
用户5675002
用户5675002

是否将多个脚本加载到单个lua_State中是一个好主意?

是的,除非这些脚本是不相关的,并且应在多个并行线程中运行。

是否有关闭特定代码块的方法?

代码块只是值为“function”的值。当您没有在任何地方存储该值时,代码块将被垃圾回收。

代码块生成的任何内容——全局变量或具有在外部引用的本地变量——都将继续存在。

如何在保留所有其他内容的同时从lua_State中清除它?

这取决于您如何看待该代码块。它只是一组函数,还是代表具有自己状态的某个实体。如果没有创建全局函数和变量,那么在单独的脚本文件中定义的所有内容都将局部于代码块,并且在没有对代码块的引用时将被删除。

如何最好地使用可能为函数/全局变量使用相同名称的脚本?

考虑重写您的代码。除非明确要求在程序的其他部分建立通信,否则不要创建任何全局变量。将变量设为本地(由块拥有),或将其存储在将由块返回为新对象的表/闭包中。代码块可能是生成新对象的工厂,而不仅仅是“脚本”。

此外,Lua 只需要更快的局部变量。

我设想的工作方式是每次加载一个代码块时,我都会为其分配一个唯一的环境名称

如果脚本来自外部——用户编写的或来自其他外部来源,请执行此操作。沙盒非常酷,但如果块是您的内部文件,则无需沙盒。考虑重新编写代码以避免全局变量。如果您的块生成其他对象,则返回一些对象(API 表或闭包)。您可以多次调用该块而无需重新加载它。或者,如果块表示类似 Lua 的模块,则保存一个全局变量——模块接口。如果您没有良好地组织代码,则将被迫使用单独的环境,并且您必须为每个脚本准备新环境,复制基本内容,如打印/对/字符串等。您将在运行时遇到许多中断,直到您弄清新环境中缺少什么为止,等等。

2016-04-03 09:01:10
用户1490747
用户1490747

经过一番探索,我找到了我想要的解决方案。我不确定这是否是正确/最好的方法,但它在我的基本测试中有效。@jpjacobs在这个问题的答案对我帮助很大。

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);
}

输出:

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

我使用的是Lua 5.3.2和Visual Studio 2013,如果有任何意义的话。

这个基本测试案例满足了我的需求。我将继续测试,看看是否会出现任何问题/改进。如果有任何看起来可以改进这个代码或者有任何显眼的错误,请留言。

2016-04-04 17:12:33
用户9608077
用户9608077

你应该将你的每个脚本作为一个不同的模块对待。就像你的代码中有多于1个的 "require" 一样。

你的 'loaded chunk' 应该返回一个将被全局储存的表格。

加载许多全局变量并不是一个好主意,因为在你添加更多模块后,这可能会导致问题。

2016-04-04 22:33:50