简体   繁体   中英

Automated Lua Binding using C++

I'm building a simple 2D game engine, and its getting bigger and bigger, exposing all of the function in Lua will be impossible: so I'm trying to automate a little bit the process, Is there anyway to get all the n arguments (with different types) from the stack at once and inject them directly into the C++ function. I already automated functions args checking. still the function binding which is a little bit tricky

For Eg: I have normal code for changing sprite position:

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

The concept that i want achieve is more less like this:

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

So

  • LOAD_ARGS will get from the stack The given type respectively.
  • GET_ARG(i) will get arg at index i.
  • GET_ARGS_FROM(i) will do something like this x,y,z,type

I'm not requesting same behavior as it could be impossible to do, but something similar at least
And I'm sure that this isn't achievable only using 'plain' C++ and that I need also some 'magic' macros.
I'm not using the ready auto Lua binding "libraries" because there is custom structs and some functions are using complex structures and classes I have done a lot of searching and I feel really lost.

try Luaaa( https://github.com/gengyong/luaaa ), that's more lightweight, implement in only one single header file.

It satisfied your requirements perfectly.

sample codes:

    // 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");

You can find more details here( https://github.com/gengyong/luaaa ).

I'm the author of this lib, I strongly recommend you have a try for it, if you have any questions, please feel free to ask.

I found Luabridge to be extremely handy when performing these tasks. It's a header only library that works with older C++ code (before c++11 and after).

You don't program against the Lua stack directly, instead you wrap normal class/struct into binding definition.

The binding code looks something like this:

 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();

For Game Objects, I'll generally manage the lifetime in C++ and just pass pointers to Lua. Luabind lets you override the Lua CTor (__call) and ~Tor (__gc). Here are my copies:

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*) {}

And their use:

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

To call Lua Code from C++, you use LuaRefs which (IIRC) are basically variants that can be any Lua Type.


If you're interested, I found Zerobrane to be a good debugger, you just need to add the socket lua libraries

In my practice I find it very convenient to follow Lua's flexibility about arguments types, or different layout for arguments. You're not limited to strict C/C++ rules, you can have same method doing different job depending on exact arguments. Ie the semantic of the call would express some higher-level concept.

When doing such flexible methods, fully automatic binding are mostly useless. Say, you can accept a variety of objects as arguments. Either single number representing, for example, luminosity, or rgb triplet to specify exact color, or even a string giving a path to texture image. Plus maybe an optional following argument specifying non-default blending mode. You can't throw fixed generic arguments reader on that.

So instead of trying to parse some arguments upfront with some template voodoo, it's easier to have an utility functions, reading that Vec2/Vec2i/Vec3/... for you, taking just a pointer to Lua state, index on stack to start with, and returning result flag, or number of actually read values to help track arguments parsing.

GET_ARG(1) construct is so generic, that in my case it's just a method in a base class for all objects that must be accessible from Lua. That base is a template, so it knows the type of the object, automatically casting Lua userdata at index 1 (it's always 1) to exact native type without writing anything.

Returning values is also either too simple to complain about (one boolean, or couple of ints), or too complex to be handled by automatic bindings. Say, you return some big object, requiring some configuration/updating/registering after it is constructed, followed by more values, some of which are optional or depend on the type of previously returned data.

So your example in my code would translate to something like:

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

Depending on how much control you want over how parameters get to and from Lua, you may want a completely automatic binding, or as I prefer a semi-automatic binding. I've written something that allows you to define 'glue' functions that can use all the power that the Lua stack manipulation functions give you but otherwise handles object lifetime for you.

Not for everyone - but good for some.

https://github.com/merlinblack/manualbind

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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