简体   繁体   English

将现有的C ++对象传递给Lua

[英]Passing existing C++ objects to Lua

I am currently working on my own videogame engine and I'm trying to implement support for lua scripts in order to code in game's behaviour. 我目前正在开发自己的视频游戏引擎,我正在尝试实现对lua脚本的支持,以便对游戏行为进行编码。 However, I'm currently struggling with C++ classes in Lua. 但是,我目前正在努力使用Lua中的C ++类。 I understand how to create a new instance of the class on heap by lua - but that's (probably) not what I want to do. 我理解如何通过lua在堆上创建类的新实例 - 但这可能(可能)不是我想要做的。

I rather need to pass an object that already exists in C++ into Lua and then work with it inside of the script. 我宁愿将一个已经存在于C ++中的对象传递给Lua,然后在脚本中使用它。 (Example: The engine has an instance of a monster and I'd like to run a script for the monster to see whether it sees the player - and if so, then the monster would attack the player). (示例:引擎有一个怪物的实例,我想为怪物运行一个脚本,看它是否看到了玩家 - 如果是,那么怪物会攻击玩家)。

The only solution I have found is this one: Passing existing C++ objects to Lua and calling the passed objects' member functions - however, the original poster is using luabind which requires boost (which I don't really want to use). 我找到的唯一解决方案是:将现有的C ++对象传递给Lua并调用传递的对象的成员函数 - 然而,原始的海报使用luabind,这需要boost(我真的不想使用)。

Thus, my questions are these: 因此,我的问题是:

How can I pass an object already allocated on heap in C++ to a Lua script? 如何将已在C ++堆上分配的对象传递给Lua脚本? (without using luabind) (不使用luabind)

Is this approach even correct? 这种方法是否正确? Most of what I've found tends to answer the question "how to create an instance of a C++ class in Lua" rather than just passing it, which leads me to think whether is my idea even right or not. 我发现的大部分内容都倾向于回答“如何在Lua中创建C ++类的实例”而不是仅仅传递它的问题,这让我想到我的想法是否正确与否。

Note: I do not mind using a tool such as luabind, I just don't want to use them in case they depend on external libraries such as boost. 注意:我不介意使用像luabind这样的工具,我只是不想使用它们以防它们依赖外部库如boost。 If there's any easy solution like this then I will gladly use it. 如果有这样的简单解决方案,那么我很乐意使用它。

If you want truly independent binding, here is classic C-style boilerplate. 如果你想要真正的独立绑定,这里是经典的C风格样板。 Just replace "Object" with your class name and implement // { } blocks. 只需将“Object”替换为您的类名并实现// { }块。 Turning push_Object from static to public will give you universal existing Object pusher that also caches objects in metatable (otherwise multi-push would create many distinct userdata with all-obvious gc-trouble). push_Object从static转为public将为您提供通用的现有 Object pusher,它也可以缓存metatable中的对象(否则多推将会创建许多不同的用户数据,其中包含明显的gc-trouble)。

You may also C++ify or library-fy that if you want, but I personally do not do this, because adding eg __newindex-to-environment proxy and other quirks would not be so straightforward if it was library. 如果你愿意,你也可以使用C ++ ify或library-fy,但我个人不这样做,因为如果它是库,添加例如__newindex到环境代理和其他怪癖就不那么简单了。 Actually, all boilerplate is in push_mt , push_Object , forget_Object and check_Object , everything other is subject to fine-tuning. 实际上,所有样板都在push_mtpush_Objectforget_Objectcheck_Object ,其他所有样板都需要进行微调。

Note that this only binds a single class, not all classes at once. 请注意,这仅与一个单一的类,并不是所有的班一次。

// { class Object { ... } }

static const char *tname = "Object";

static void push_Object(lua_State *L, Object *object);
static Object *check_Object(lua_State *L, int i);

static int
l_gc(lua_State *L)
{
    Object **ud = luaL_checkudata(L, 1, tname);

    if (*ud) {
        // { delete *ud }
        *ud = NULL;
    }
    return 0;
}

static int
l_tostring(lua_State *L)
{
    Object **ud = luaL_checkudata(L, 1, tname);

    lua_pushfstring(L, "%s: %p", tname, *ud);
    return 1;
}

static int
l_new(lua_State *L)
{
    Object *object = NULL; // { = new Object }

    push_Object(L, object);
    return 1;
}

static int
l_method(lua_State *L)
{
    Object *object = check_Object(L, 1);
    lua_Integer int_arg = luaL_checkinteger(L, 2);
    const char *str_arg = luaL_checklstring(L, 3, NULL);

    // { object->method(int_arg, str_arg) }

    return 0;
}

static const luaL_Reg lib[] = {
    // functions
    { "new",    l_new    }, // () -> object

    // methods
    { "method", l_method }, // (object, int, string) -> none

    { NULL, NULL },
};
static lua_CFunction first_m = l_method;

static void
push_mt(lua_State *L)
{
    if (luaL_newmetatable(L, tname)) {
        size_t m = 0; while (first_m != lib[m].func) m++;
        lua_createtable(L, 0, 0);
        luaL_register(L, NULL, &lib[m]);
        lua_setfield(L, -2, "__index");

        lua_pushcfunction(L, l_tostring);
        lua_setfield(L, -2, "__tostring");

        lua_pushcfunction(L, l_gc);
        lua_setfield(L, -2, "__gc");

        lua_pushstring(L, tname);
        lua_setfield(L, -2, "__metatable");

        // mt.objects = setmetatable({ }, { __mode = "v" })
        lua_createtable(L, 0, 0);
        lua_createtable(L, 0, 1);
        lua_pushstring(L, "v");
        lua_setfield(L, -2, "__mode");
        lua_setmetatable(L, -2);
        lua_setfield(L, -2, "objects");
    }
}

static void
push_Object(lua_State *L, Object *object)
{
    int top = lua_gettop(L);

    push_mt(L);
    lua_getfield(L, -1, "objects");
    // top+1 = mt
    // top+2 = mt.objects

    // ud = mt.objects[object]
    lua_pushlightuserdata(L, object);
    lua_gettable(L, top+2);

    if (lua_isnil(L, -1)) {
        lua_pop(L, 1);

        Object **ud = lua_newuserdata(L, sizeof(*ud));
        *ud = object;

        // setmetatable(ud, mt)
        lua_pushvalue(L, top+1);
        lua_setmetatable(L, -2);

        // mt.objects[object] = ud
        lua_pushlightuserdata(L, object);
        lua_pushvalue(L, -3);
        lua_pushvalue(L, top+2);
    }

    // return ud
    lua_replace(L, top+1);
    lua_settop(L, top+1);
    return; // ud at top
}

static void
forget_Object(lua_State *L, Object *object)
{
    int top = lua_gettop(L);

    push_mt(L);
    lua_getfield(L, -1, "objects");
    // top+1 = mt
    // top+2 = mt.objects

    // ud = mt.objects[object]
    lua_pushlightuserdata(L, object);
    lua_pushnil(L);
    lua_settable(L, top+2);

    lua_settop(L, top);
}

static Object *
check_Object(lua_State *L, int i)
{
    Object **ud = luaL_checkudata(L, i, tname);
    Object *object = *ud;

    if (object == NULL)
        luaL_error(L, "%s is finalized", tname);

    return object;
}

int
luaopen_Object(lua_State *L)
{
    push_mt(L); // register tname

    lua_createtable(L, 0, sizeof(lib)-1);
    luaL_register(L, NULL, lib);
    return 1;
}

https://github.com/Rapptz/sol https://github.com/Rapptz/sol

Sol is the perfect example of a library that can support C++11+-like semantics, including the passing of class types using templates. Sol是一个完美的库,它可以支持类似C ++ 11 +的语义,包括使用模板传递类类型。 If I were you, I'd look into Sol's source code and try to replicate its methods of object and data passing--much of Lua's "meta" functionality is probably build on its userdata structure, so I'd suggest you start looking there. 如果我是你,我会考虑Sol的源代码,并试图复制其对象和数据传递的方法-大部分Lua的“元”的功能可能是建立在其userdata的结构,所以我建议你开始寻找有。

By the way, Sol is dependent on only Lua, and is header-inclusion only. 顺便说一下,Sol只依赖于Lua,并且只是头部包含。 It's quite possible to just use it out of the box, provided you're using Lua 5.2 (and not 5.3, which is a big gripe of the community but shouldn't matter, as LuaJIT is also on 5.2, I believe). 只要你使用Lua 5.2(而不是5.3,这是社区的一个大抱怨,但不应该重要,因为LuaJIT也是5.2,我相信),开箱即用是很有可能的。

Most of what I've found tends to answer the question "how to create an instance of a C++ class in Lua" rather than just passing it, which leads me to think whether is my idea even right or not. 我发现的大部分内容都倾向于回答“如何在Lua中创建C ++类的实例”而不是仅仅传递它的问题,这让我想到我的想法是否正确与否。

Those are answers to your problem, too. 这些也是你问题的答案。 You can't create a C++ class in Lua. 无法在Lua中创建C ++类。 Any class construction is going to be done in the C++ code. 任何类构造都将在C ++代码中完成。

If you're not going to use a binding library, then you're going to need to be an export (no fear, it's not hard) on Lua's C API and metatables. 如果你不打算使用绑定库,那么你需要在Lua的C API metatables上进行导出(不用担心,这并不难)。

You expose the C++ object to Lua as a "userdata". 您将C ++对象公开为Lua作为“userdata”。 You then supply a metatable with an __index metamethod which allows you to write C++ handlers for when the object is indexed, such as when a property is being accessed (eg pt.x ) or when a method is being called (eg entity:attack(target) ). 然后,您提供了一个带有__index元方法的元表,它允许您在对象被索引时编写C ++处理程序,例如在访问属性时(例如pt.x )或调用方法时(例如entity:attack(target) )。 You also need a __gc metamethod so you can delete the C++ object when the corresponding Lua userdata is garbage collected. 您还需要一个__gc元方法,以便在对相应的Lua userdata进行垃圾回收时删除C ++对象。

There can be quite a bit of boilerplate code, which is what many C++ binding libraries are supposed to take care of. 可能有相当多的样板代码,这是许多C ++绑定库应该照顾的。 I've never used one, so I can't recommend one. 我从来没用过,所以我不推荐一个。 However, due to the Law of Leaky Abstractions , I would strongly recommend taking a stab at this on your own so that you understand how it works. 但是,由于“漏洞抽象法则” ,我强烈建议您自己动手,以便了解它是如何工作的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM