使用C++自动绑定Lua

我正在构建一个简单的2D游戏引擎,它越来越大,将所有的函数暴露给Lua将会是不可能的:因此我试图自动化一下这个过程, 是否有任何方法可以一次从堆栈中获取所有参数(具有不同的类型)并将它们直接注入到C++函数中。 我已经自动化了函数参数检查,但是函数绑定有点棘手

例如: 我有一个正常的改变精灵位置的代码:

int LuaSprite::SetSpritePosition(lua_State* L)
{
    Drawable* sprt = (Drawable*)lua_touserdata(L, 1);
    int x = (int)lua_tonumber(L, 2);
    int y = (int)lua_tonumber(L, 3);
    sprt->setPosition(Vec2i(x, y));
    return 0;
}

我想要实现的概念更像是这样:

int LuaSprite::SetSpritePosition(lua_State* L)
{
    LOAD_ARGS(Sprite*, int, int)
    GET_ARG(1)->setPosition(Vec2i(GET_ARGS_FROM(1)));
    LOAD_RETURN(true, x, y, 3);
    return 3;
}

所以

  • LOAD_ARGS将分别从堆栈中获取给定的类型。

  • GET_ARG(i)将获取索引为i的参数。

  • GET_ARGS_FROM(i)将做类似于x,y,z,type的事情

我不是要求完全相同的行为,因为它可能是不可能做到的,但至少要类似于这样的行为

我相信这不仅仅需要“普通”的C++,还需要一些“魔法”宏。 我没有使用现成的自动Lua绑定“库”,因为有自定义结构和一些函数使用复杂的结构和类 我已经做了很多搜索,但我感到非常迷失。

点赞
用户7949696
用户7949696

我发现Luabridge在执行这些任务时非常便捷。它是一个仅有头文件的库,适用于旧的C++代码(在c++11之前和之后)。

您不必直接针对Lua堆栈进行编程,而是将普通的类/结构包装为绑定定义。

绑定代码如下所示:

 getGlobalNamespace(L).beginNamespace("GameFrameworkName")

.beginClass<RectF>("Rect")
.addStaticFunction("__call", &RectF_Ctor) // Constructor from Table!
.addData<float>("x", &RectF::x)
.addData<float>("y", &RectF::y)
.addData<float>("width", &RectF::width)
.addData<float>("height", &RectF::height)
.addFunction("Union", &RectF::Union)
.addFunction("Intersects", (bool (RectF::*)(const RectF&)) &RectF::Intersects)
.addFunction("Intersection", &RectF::Intersection)
.addFunction("Contains", (bool (RectF::*)(const PointF&)) &RectF::Contains)
.addFunction("Offset", (void (RectF::*)(const PointF&)) &RectF::Offset)
.addFunction("Inflate", (void (RectF::*)(float, float)) &RectF::Inflate)
.addFunction("__tostring", &RectF_ToString)
.addFunction("__eq",  (bool   (RectF::*)(const RectF&)) &RectF::operator==)
.addFunction("Copy", &RectF_Copy)
.endClass()

.beginClass<PointF>("Point")
.addStaticFunction("__call", &PointF_Ctor) // Constructor from Table!
.addData<float>("x", &PointF::x)
.addData<float>("y", &PointF::y)
.addFunction("__tostring", &PointF_ToString)
.addFunction("__add", (PointF (PointF::*)(const PointF&)) &PointF::operator+ )
.addFunction("__sub", &PointF_SubtractOperator )
.addFunction("__mul", &PointF_MultiplyOperator )
.addFunction("__div", &PointF_DivideOperator )
.addFunction("__eq",  (bool   (PointF::*)(const PointF&)) &PointF::operator==)
.addFunction("__unm", &PointF_Unm)
.addFunction("Copy",  &PointF_Copy)
.endClass()

.endNamespace();

对于游戏对象,我通常会在C++中管理其生命期,并将指针传递给Lua。 Luabind允许您重写Lua CTor(__call)和~Tor(__gc)。以下是我的副本:

template<typename T>
std::string DoNotCreate()
{
    DBG_ASSERT(false); // Do not try to create a new instance of this type
    return StrFormat("ERROR: Lua cannot create an instance of %s.", typeid(T).name());
}

///////////////////////////////////////
// \brief       Do Not Allow Lua or Luabridge to delete us
void DoNotGarbageCollect(void*) {}

它们的用途:

.deriveClass<Sprite, DisplayObject>("Sprite")
  .addStaticFunction("__call", &DoNotCreate<Sprite>)
  .addFunction("__gc", (void (*) (Sprite*)) &DoNotGarbageCollect)

要从C++调用Lua代码,您可以使用LuaRefs,这些引用(IIRC)基本上是可以成为任何Lua类型的变量。


如果您有兴趣,我发现Zerobrane是一个很好的调试器,您只需要添加套接字lua库即可。

2018-09-29 03:15:24
用户5675002
用户5675002

在我的实践中,我发现遵循 Lua 对参数类型灵活性或参数不同布局的方便性非常高。您不受限于严格的 C/C++ 规则,您可以有相同的方法根据确切的参数执行不同的任务。即,调用的语义将表达一些更高级的概念。

当编写这种灵活的方法时,完全自动的绑定大多是无用的。比如说,您可以接受各种对象作为参数。单个数字表示光度,或 RGB 三元组表示精确的颜色,甚至是给出纹理图像路径的字符串。再加上一个可选的跟随参数,指定非默认混合模式。您无法转换固定的通用参数读取器。

因此,与其尝试使用一些模板魔法来预先解析某些参数,不如拥有一个实用程序函数,为您读取 Vec2/Vec2i/Vec3/等等,仅需指向 Lua 状态的指针、从栈的索引开始,然后返回结果标志,或实际读取的值的数量,以帮助跟踪参数解析。

GET_ARG(1) 结构如此通用,以致于在我的情况下它只是必须从 Lua 访问的所有对象的基类中的一个方法。这个基类是一个模板,所以它知道对象的类型,可以自动将索引 1(它总是 1)处的 Lua userdata 强制转换为确切的本机类型,而无需编写任何内容。

返回值也要么太简单不值一提(一个布尔值或一些整数),要么太复杂无法通过自动绑定处理。例如,返回一些大型对象,它们在构造之后需要进行一些配置/更新/注册,然后是更多的值,其中一些是可选的或依赖于先前返回数据的类型。

因此,在我的代码示例中,您的示例将转换为:

int LuaSprite::SetSpritePosition(lua_State* L)
{
    get_object(L)->setPosition(read_lua_vec2i(L, 2));
    return 0;
}
2018-09-29 09:07:48
用户1414083
用户1414083

试试 Luaaa(https://github.com/gengyong/luaaa),它更轻量级,只实现了一个头文件。

它完全符合您的要求。

示例代码:

    // 包括 luaaa 文件
    #include "luaaa.hpp"
    using namespace luaaa;

    // 您已存在的类
    class Cat
    {
    public:
        Cat();
        virtual ~Cat();
    public:
        void setName(const std::string&);
        const std::string& getName() const;
        void eat(const std::list<std::string>& foods);
        //...
    private:
        //...
    };

    lua_State * state; // 创建并初始化 Lua

    // 将其导出:
    LuaClass<Cat>(state, "AwesomeCat")
    .ctor<std::string>();
    .fun("setName", &Cat::setName);
    .fun("getName", &Cat::getName);
    .fun("eat", &Cat::eat);
    .def("tag", "Cat");

您可以在这里找到更多详细信息(https://github.com/gengyong/luaaa)。

我是这个库的作者,强烈建议您尝试一下,如果您有任何问题,请随时询问。

2019-01-03 07:09:18
用户707446
用户707446

根据您想对从 Lua 中传递参数的方式以及控制权的大小,您可能需要完全自动的绑定,或者像我一样使用半自动的绑定。我编写了一个工具,它允许您定义“粘合”函数,这些函数可以使用 Lua 栈操作函数的所有功能,但同时还会为您处理对象的生命周期。

这并不适用于所有人,但适用于一些人。

https://github.com/merlinblack/manualbind

2020-06-10 04:47:24