【问题标题】:Sharing global variables between different Lua states through require通过 require 在不同 Lua 状态之间共享全局变量
【发布时间】:2018-12-22 10:38:11
【问题描述】:

我正在尝试找到一种在不同 Lua 状态之间共享特定 Lua 脚本(示例中为test.lua)的全局变量的方法。

这是我的简单示例代码:

test.lua

num = 2

main.cpp

#include <iostream>
#include <lua.hpp>
int main() 
{    
    lua_State *L1 = luaL_newstate(); //script A
    luaL_openlibs(L1);
    lua_settop(L1, 0);
    luaL_dostring(L1, "require('test') num = 5");

    lua_State *L2 = luaL_newstate(); //script B
    luaL_openlibs(L2);
    lua_settop(L2, 0);
    luaL_dostring(L2, "require('test') print(num)");

    lua_close(L1);
    lua_close(L2);
}

我希望收到5,但我收到了2

不能在不同的lua_State*require 之间共享全局变量吗?

添加:

如果不可能,最好使用luaL_loadfile 打开test.lua,然后在C++ 中创建getter/setter 方法以在脚本AB 之间共享变量num

比如这样,

脚本 A:

script = my.Script("test")
script:setVar("num", 5)

脚本 B:

script = my.Script("test")
print(script:getVar("num"))

我想知道您对这种设计作为require 的替代方案有何看法。

【问题讨论】:

  • 不能用数字,但是如果num是C用户数据,那么两个VM可以访问相同的内存。
  • 您可以将我对您另一个问题的回答作为起点 (stackoverflow.com/a/50600153)。我看到您确实希望其他人为您编写代码,但是编写所有这些元表和元方法并测试它们是很多工作。我可能会为了钱而这样做,但即便如此,我也可能有更好的事情要做。
  • 你应该购买一份Programming in Lua。我在这里提到的所有技术都通过示例进行了详细描述,因此您可以复制、粘贴和适应。
  • 您的问题已经过充分研究,但您总是要求答案,这些答案会以可复制示例的形式为您提供解决方案。相反,您应该自己尝试一些东西,并在遇到困难时回来。也就是说,请继续提问,我喜欢它们。
  • 我已经用更复杂的版本更新了我的答案。也许你想看看。

标签: c++ lua


【解决方案1】:

两个不同的lua_States 是完全独立的。一个人不能直接影响发生在另一个人身上的任何事情。您可以将一些 C 代码暴露给一个允许它修改另一个的 C 代码,或者它们都可以访问一些允许它们共享数据的外部资源(例如文件)。

但是在这样的事情之外,不,他们不能互动。

对此的首选方法是不要将它们分开lua_States。

【讨论】:

    【解决方案2】:

    您可以将指向 C++ 值的指针作为元表的上值推送到包含这些全局变量的表中,而不是在 Lua 模块中使用全局值。然后将具有相同元表的globals 表推送到两个虚拟机。当您现在访问globals.num 时,将触发getglobalsetglobal 元方法(取决于您是读还是写)。这些将更新 C++ 端的值,以便在两个 VM 之间共享。

    注: 从冗长的样板文件中可以看出,这不是一个好的解决方案。您应该避免同时拥有多个虚拟机。如果您需要多个 VM 用于并发目的,请考虑使用像 Lua Lanes 这样的成熟库,而不是自己滚动(要做到这一点需要数千行代码)。

    #include <string>
    
    #include <lua.hpp>
    
    int setglobal(lua_State *L) {
        void *p = luaL_checkudata(L, 1, "globals_meta");
        luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
    
        std::string key = lua_tostring(L, 2);
        luaL_argcheck(L, key == "num", 2, "unknown global");
    
        int value = luaL_checkinteger(L, 3);
        luaL_argcheck(L, lua_isnumber(L, 3), 3, "not a number");
    
        int *num = static_cast<int *>(lua_touserdata(L, lua_upvalueindex(1)));
        *num = value;
        lua_pop(L, 1);
    
        return 0;
    }
    
    int getglobal(lua_State *L) {
        void *p = luaL_checkudata(L, 1, "globals_meta");
        luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
    
        std::string key = lua_tostring(L, 2);
        luaL_argcheck(L, key == "num", 2, "unknown global");
    
        int num = *static_cast<int *>(lua_touserdata(L, lua_upvalueindex(1)));
        lua_pop(L, 1);
    
        lua_pushinteger(L, num);
        return 1;
    }
    
    static const struct luaL_Reg globals_meta[] = {
        {"__newindex", setglobal},
        {"__index", getglobal},
        {nullptr, nullptr} // sentinel
    };
    
    int main() {
        int num = 2;
    
        // script A
    
        lua_State *L1 = luaL_newstate();
        luaL_openlibs(L1);
    
        luaL_newmetatable(L1, "globals_meta");
        lua_pushlightuserdata(L1, &num);
        luaL_setfuncs(L1, globals_meta, 1);
    
        lua_newuserdata(L1, 0);
        luaL_getmetatable(L1, "globals_meta");
        lua_setmetatable(L1, -2);
        lua_setglobal(L1, "globals");
    
        luaL_dostring(L1, "print('Script A: ' .. globals.num) globals.num = 5");
    
        // script B
    
        lua_State *L2 = luaL_newstate();
        luaL_openlibs(L2);
    
        luaL_newmetatable(L2, "globals_meta");
        lua_pushlightuserdata(L2, &num);
        luaL_setfuncs(L2, globals_meta, 1);
    
        lua_newuserdata(L2, 0);
        luaL_getmetatable(L2, "globals_meta");
        lua_setmetatable(L2, -2);
        lua_setglobal(L2, "globals");
    
        luaL_dostring(L2, "print('Script B: ' .. globals.num)");
    
        lua_close(L1);
        lua_close(L2);
    }
    

    作为对自己的挑战,我实现了一个完整的全局表,它可以在两个 Lua 状态之间传递 nilboolintdoublestring 类型的值。它们可以用任何具有字符串表示的东西来命名。

    -- To be on the safe side, just use numbers and strings as keys
    globals[1] = "x"
    globals.num = 5
    
    -- Be careful when using table or function literals as keys
    -- Two empty tables don't have the same representation
    globals[{}] = 2 -- "table: 0x10d55a0" = 2
    globals[{}] = 1 -- "table: 0x10ce2c0" = 1
    

    各种异常情况我都没有仔细检查过,所以不予退款!

    #include <iostream>
    #include <string>
    #include <unordered_map>
    
    #include <boost/variant.hpp>
    
    #include <lua.hpp>
    
    enum class nil {};
    using Variant = boost::variant<nil, bool, int, double, std::string>;
    
    int setglobal(lua_State *L) {
        void *p = luaL_checkudata(L, 1, "globals_meta");
        luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
    
        std::string key = luaL_tolstring(L, 2, nullptr);
    
        auto &globals = *static_cast<std::unordered_map<std::string, Variant> *>(
            lua_touserdata(L, lua_upvalueindex(1)));
        Variant &v = globals[key];
    
        switch (lua_type(L, 3)) {
        case LUA_TNIL:
            v = nil{};
            break;
        case LUA_TBOOLEAN:
            v = static_cast<bool>(lua_toboolean(L, 3));
            lua_pop(L, 1);
            break;
        case LUA_TNUMBER:
            if (lua_isinteger(L, 3)) {
                v = static_cast<int>(luaL_checkinteger(L, 3));
            } else {
                v = static_cast<double>(luaL_checknumber(L, 3));
            }
            lua_pop(L, 1);
            break;
        case LUA_TSTRING:
            v = std::string(lua_tostring(L, 3));
            lua_pop(L, 1);
            break;
        default:
            std::string error = "Unsupported global type: ";
            error.append(lua_typename(L, lua_type(L, 3)));
            lua_pushstring(L, error.c_str());
            lua_error(L);
            break;
        }
        return 0;
    }
    
    int getglobal(lua_State *L) {
        void *p = luaL_checkudata(L, 1, "globals_meta");
        luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
    
        std::string key = luaL_tolstring(L, 2, nullptr);
    
        auto globals = *static_cast<std::unordered_map<std::string, Variant> *>(
            lua_touserdata(L, lua_upvalueindex(1)));
        lua_pop(L, 1);
        auto search = globals.find(key);
        if (search == globals.end()) {
            lua_pushstring(L, ("unknown global: " + key).c_str());
            lua_error(L);
            return 0;
        }
        Variant const &v = search->second;
    
        switch (v.which()) {
        case 0:
            lua_pushnil(L);
            break;
        case 1:
            lua_pushboolean(L, boost::get<bool>(v));
            break;
        case 2:
            lua_pushinteger(L, boost::get<int>(v));
            break;
        case 3:
            lua_pushnumber(L, boost::get<double>(v));
            break;
        case 4:
            lua_pushstring(L, boost::get<std::string>(v).c_str());
            break;
        default: // Can't happen
            std::abort();
            break;
        }
    
        return 1;
    }
    
    static const struct luaL_Reg globals_meta[] = {
        {"__newindex", setglobal},
        {"__index", getglobal},
        {nullptr, nullptr} // sentinel
    };
    
    int main() {
        std::unordered_map<std::string, Variant> globals;
        globals["num"] = 2;
    
        // script A
    
        lua_State *L1 = luaL_newstate();
        luaL_openlibs(L1);
    
        luaL_newmetatable(L1, "globals_meta");
        lua_pushlightuserdata(L1, &globals);
        luaL_setfuncs(L1, globals_meta, 1);
    
        lua_newuserdata(L1, 0);
        luaL_getmetatable(L1, "globals_meta");
        lua_setmetatable(L1, -2);
        lua_setglobal(L1, "globals");
    
        if (luaL_dostring(L1, "print('Script A: ' .. globals.num)\n"
                              "globals.num = 5") != 0) {
            std::cerr << "L1:" << lua_tostring(L1, -1) << '\n';
            lua_pop(L1, 1);
        }
    
        // script B
    
        lua_State *L2 = luaL_newstate();
        luaL_openlibs(L2);
    
        luaL_newmetatable(L2, "globals_meta");
        lua_pushlightuserdata(L2, &globals);
        luaL_setfuncs(L2, globals_meta, 1);
    
        lua_newuserdata(L2, 0);
        luaL_getmetatable(L2, "globals_meta");
        lua_setmetatable(L2, -2);
        lua_setglobal(L2, "globals");
    
        if (luaL_dostring(L2, "print('Script B: ' .. globals.num)") != 0) {
            std::cerr << "L1:" << lua_tostring(L2, -1) << '\n';
            lua_pop(L2, 1);
        }
    
        lua_close(L1);
        lua_close(L2);
    }
    

    【讨论】:

    • 非常感谢您长时间详细的回答。现在我明白了if num was C userdata, the two VMs could access the same memory 的意思,但我实际上需要两个虚拟机来共享第三个脚本的变量,在我的示例中是test.lua。而且我认为使用require 是不可能的,所以我想我将创建gettersetter 函数。不管怎样,你的例子帮助我理解了如何在 Lua 中使用userdata,所以我真的很感激。
    【解决方案3】:

    虽然默认情况下 Lua 状态是独立的,但一些绑定库公开了将信息从一个状态传输到另一个状态的功能。

    例如,在sol 中,有一些方法可以将相当任意的 Lua 数据(包括函数)序列化为 C++ 数据。然后,您可以将该数据反序列化为另一个 Lua 状态,以有效地复制它 (code link)。

    但你最终还是会有两份。您不能直接从另一种 Lua 状态修改。

    关于暴露一些 getter/setter 的最后一点是有效的。您可以将一些数据存储在 C/C++ 中,并有两种不同的 Lua 状态可以访问它。您仍然必须将该数据分别绑定到每个 VM。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-16
      • 2012-12-14
      • 2011-06-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多