通过require在不同的Lua状态之间共享全局变量

时间:2018-07-14 05:09:10

标签: c++ lua

我试图找到一种在不同的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之间共享全局变量吗?

已添加:

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

例如这样的

脚本A:

B

脚本B:

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

我想知道您如何看待这种设计来替代script = my.Script("test") print(script:getVar("num"))

3 个答案:

答案 0 :(得分:4)

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

但是除了这样的事情,不,他们无法互动。

首选的方法是不要将它们分开lua_State

答案 1 :(得分:1)

除了将全局值包含在Lua模块中之外,您还可以将指向C ++值的指针作为指向包含这些全局值的表的元表的上位值。然后,将具有相同元表的globals表推入两个VM。现在访问globals.num时,将触发getglobalsetglobal元方法(取决于您是读还是写)。这些将更新C ++端的值,以便在两个VM之间共享它。

N.B。:从冗长的样板中可以判断出这不是一个好的解决方案。您应该避免同时拥有多个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);
}

作为对我自己的挑战,我实现了一个完整的全局表,该表可以传达类型为nilboolintdoublestring的值在两个Lua州之间。可以使用所有具有字符串表示形式的名称来命名它们。

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

答案 2 :(得分:1)

虽然Lua状态默认情况下是分开的,但是某些绑定库提供了将信息从一个传递到另一个的功能。

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

但是最后您仍然会有两份。您不能直接从另一个Lua状态修改。

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