通过require在不同的Lua状态之间共享全局变量 [英] Sharing global variables between different Lua states through require

查看:87
本文介绍了通过require在不同的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之间共享全局变量吗?

已添加:

如果不可能,使用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.

解决方案

不是将全局值包含在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);
}


作为对我自己的挑战,我实现了一个完整的全局表,该表可以在两个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);
}

I'm trying to find a way to share global variables of a specific Lua script(test.lua in the example) between different Lua states.

Here's my simple example code:

In test.lua

num = 2

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

I expect to get 5 but I get 2.

Is not possible to share global variables between different lua_State* through require?

ADDED :

If it's not possible, would it be a good idea to open test.lua using luaL_loadfile and then create getter/setter methods in C++ to share variable num between script A and B?

For example like this,

Script A:

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

Script B:

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

I wonder what you think about this design as an alternative to require.

解决方案

Rather than having the global value in a Lua module, you could push a pointer to a C++ value as an upvalue for a metatable to a table which contains those globals. Then you push the globals table with the same metatable to both VMs. When you now access globals.num the getglobal and setglobal metamethods are triggered (depending on whether you read or write). These will update the value on the C++ side, such that it is shared between the two VMs.

N.B.: As you can judge from the lengthy boilerplate this is not a good solution. You should avoid having multiple VMs at the same time. If you require multiple VMs for concurrency purposes, consider using a mature library like Lua Lanes rather than rolling your own (doing this right requires several thousands of lines of code).

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


As a challange to myself I implemented a complete global table which can communicate values of type nil, bool, int, double, and string between two Lua states. They can be named with everything that has a string representation.

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

I haven't checked all sorts of exceptional situations exhaustively, so no refunds!

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

这篇关于通过require在不同的Lua状态之间共享全局变量的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆