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

Sharing global variables between different Lua states through require

本文关键字:状态 之间 共享 全局变量 Lua require 通过      更新时间:2023-10-16

我正试图找到一种方法,在不同的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_State是完全独立的。一个人不能直接影响另一个人身上发生的任何事情。您可以将一些C代码公开给其中一个,允许它修改另一个,或者它们都可以访问一些外部资源(例如文件(,允许它们共享数据。

但除了这样的事情,不,他们不能相互作用。

优选的方法是不使它们分离lua_States。

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

N。B.:正如你从冗长的样板中所判断的那样,这不是一个好的解决方案。您应该避免同时拥有多个虚拟机。如果出于并发目的需要多个虚拟机,请考虑使用像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);
}

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

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

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

您的最后一点,关于公开某个getter/setter,是有效的。你可以将一些数据存储在C/C++中,并有两个不同的Lua状态可以访问它。你仍然需要将这些数据分别绑定到每个VM。