【问题标题】:Automated Lua Binding using C++使用 C++ 的自动 Lua 绑定
【发布时间】:2018-09-29 01:46:05
【问题描述】:

我正在构建一个简单的 2D 游戏引擎,而且它越来越大,在 Lua 中公开所有功能是不可能的:所以我正在尝试使这个过程自动化一点, 无论如何,是否可以一次从堆栈中获取所有 n 个参数(具有不同类型)并将它们直接注入 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 处获取 arg。
  • GET_ARGS_FROM(i) 将执行类似 x,y,z,type 的操作

我不是要求相同的行为,因为它可能是不可能的,但至少是类似的
而且我确信这仅使用“普通”C++ 是无法实现的,而且我还需要一些“魔法”宏。
我没有使用现成的自动 Lua 绑定“库”,因为有自定义结构并且某些函数正在使用复杂的结构和类 我做了很多搜索,我真的很迷茫。

【问题讨论】:

    标签: c++ lua generic-programming luabind


    【解决方案1】:

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

    它完美地满足了您的要求。

    示例代码:

        // include luaaa file
        #include "luaaa.hpp"
        using namespace luaaa;  
    
        // Your exists class
        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; // create and init lua
    
        // To export it:
        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)。

    我是这个lib的作者,强烈推荐你尝试一下,如果有任何问题,请随时提问。

    【讨论】:

      【解决方案2】:

      我发现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 是一个很好的调试器,你只需要添加socket lua 库

      【讨论】:

        【解决方案3】:

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

        在做这种灵活的方法时,全自动绑定大多是没用的。比如说,您可以接受各种对象作为参数。例如,表示亮度的单个数字,或表示精确颜色的 rgb 三元组,甚至是提供纹理图像路径的字符串。加上一个可选的以下参数,指定非默认混合模式。你不能在上面抛出固定的通用参数阅读器。

        因此,与其尝试使用一些模板巫术来预先解析一些参数,不如拥有一个实用函数更容易,为你读取 Vec2/Vec2i/Vec3/...,只需一个指向 Lua 状态的指针,在堆栈上索引开始,并返回结果标志或实际读取值的数量,以帮助跟踪参数解析。

        GET_ARG(1) 构造非常通用,在我的情况下,它只是一个基类中的一个方法,用于必须从 Lua 访问的所有对象。该基础是一个模板,因此它知道对象的类型,自动将索引 1(始终为 1)处的 Lua 用户数据转换为精确的原生类型,而无需编写任何内容。

        返回值要么太简单而无法抱怨(一个布尔值或几个整数),要么太复杂而无法通过自动绑定来处理。比如说,你返回了一个大对象,在构造它之后需要一些配置/更新/注册,然后是更多的值,其中一些是可选的,或者取决于之前返回的数据的类型。

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

        int LuaSprite::SetSpritePosition(lua_State* L)
        {   
            get_object(L)->setPosition(read_lua_vec2i(L, 2));
            return 0;
        }
        

        【讨论】:

          【解决方案4】:

          根据您对参数与 Lua 之间的获取方式的控制程度,您可能需要完全自动绑定,或者我更喜欢半自动绑定。我编写了一些东西,允许您定义“胶水”函数,这些函数可以使用 Lua 堆栈操作函数为您提供的所有功能,但可以为您处理对象生命周期。

          不适合所有人 - 但对某些人有好处。

          https://github.com/merlinblack/manualbind

          【讨论】:

            猜你喜欢
            • 2011-11-19
            • 2013-02-08
            • 2011-04-18
            • 2013-12-29
            • 2021-05-19
            • 1970-01-01
            • 2013-06-24
            • 2016-08-06
            • 1970-01-01
            相关资源
            最近更新 更多