LuaGlue icon indicating copy to clipboard operation
LuaGlue copied to clipboard

[ -- additional -- ] Lambda and std::function

Open DominikMS opened this issue 11 years ago • 12 comments

Example:

    g_lua.
        Class<ClassTest>("ClassTest").
            method("emptyRet", std::function<void(int)>([](int x)
                    {
                        std::cout << "[hello void std::function] " << x << std::endl;
                    } )).
            method("boolRet", std::function<bool(int)>([](int x)
                    {
                        std::cout << "[hello return std::function] " << x << std::endl;
                        return true;
                    } )).
            method("lambda", [](int x) { std::cout << "[hello lambda]" << std::endl; return true; } );
g_lua.func("getBoolTest", std::function<bool(int x)>( [](){ return true; } ));

Todo:

[Fixed] Can't bind std::function under LuaGlue.func()
[Fixed] Can't bind and lambda under LuaGlue.func()

Code:

Add file: LuaGlueStdFuncMethod.h

#ifndef LUAGLUE_STDFUNC_METHOD_H_GUARD
#define LUAGLUE_STDFUNC_METHOD_H_GUARD

#include <string>
#include <tuple>
#include <utility>

#include "LuaGlue/LuaGlueObject.h"
#include "LuaGlue/LuaGlueApplyTuple.h"
#include "LuaGlue/LuaGlueBase.h"


template<typename _Class>
class LuaGlueClass;

class LuaGlueMethodBase;

template<typename _Ret, typename _Class, typename... _Args>
class LuaGlueStdFuncMethod : public LuaGlueMethodBase
{
    private:
        typedef std::tuple<typename std::remove_const<typename std::remove_reference<_Args>::type>::type...> ArgsTuple;

        typedef _Class ClassType;
        typedef _Ret ReturnType;
        typedef std::function<_Ret(_Args...)> MethodType;

        LuaGlueClass<_Class> *glueClass;
        std::string name_;
        MethodType fn;

        ArgsTuple args;
        static const unsigned int Arg_Count_ = sizeof...(_Args);

    public:
        LuaGlueStdFuncMethod(LuaGlueClass<_Class> *luaClass, const std::string &name, MethodType &&fn) :
            glueClass(luaClass), name_(name), fn(std::forward<decltype(fn)>(fn))
        { }

        virtual ~LuaGlueStdFuncMethod() { }

        virtual bool glue(LuaGlueBase *luaGlue)
        {
            lua_pushlightuserdata(luaGlue->state(), this);
            lua_pushcclosure(luaGlue->state(), &lua_call_func, 1);
            lua_setfield(luaGlue->state(), -2, name_.c_str());
            return true;
        }

        static int lua_call_func(lua_State *state)
        {
            auto mimp = (LuaGlueStdFuncMethod<_Ret, _Class, _Args...> *)lua_touserdata(state, lua_upvalueindex(1));
            return mimp->invoke(state);
        }

        int invoke(lua_State *state)
        {
            ReturnType ret = applyTuple<_Ret, _Args...>(glueClass->luaGlue(), state, fn, args);
            lua_pop(state, (int)Arg_Count_);

            stack<_Ret>::put(glueClass->luaGlue(), state, ret);
            return 1;
        }
};

template<typename _Class, typename... _Args>
class LuaGlueStdFuncMethod<void, _Class, _Args...> : public LuaGlueMethodBase
{
    private:
        typedef std::tuple<typename std::remove_const<typename std::remove_reference<_Args>::type>::type...> ArgsTuple;

        typedef _Class ClassType;
        typedef std::function<void(_Args...)> MethodType;

        LuaGlueClass<_Class> *glueClass;
        std::string name_;
        MethodType fn;

        ArgsTuple args;
        static const unsigned int Arg_Count_ = sizeof...(_Args);

    public:
        LuaGlueStdFuncMethod(LuaGlueClass<_Class> *luaClass, const std::string &name, MethodType &&fn) :
            glueClass(luaClass), name_(name), fn(std::forward<decltype(fn)>(fn))
        { }

        virtual ~LuaGlueStdFuncMethod() { }

        virtual bool glue(LuaGlueBase *luaGlue)
        {
            lua_pushlightuserdata(luaGlue->state(), this);
            lua_pushcclosure(luaGlue->state(), &lua_call_func, 1);
            lua_setfield(luaGlue->state(), -2, name_.c_str());
            return true;
        }

        static int lua_call_func(lua_State *state)
        {
            auto mimp = (LuaGlueStdFuncMethod<void, _Class, _Args...> *)lua_touserdata(state, lua_upvalueindex(1));
            return mimp->invoke(state);
        }

        int invoke(lua_State *state)
        {
            applyTuple<void, _Args...>(glueClass->luaGlue(), state, fn, args);
            lua_pop(state, (int)Arg_Count_);
            return 0;
        }
};

#endif /* LUAGLUE_STDFUNC_METHOD_H_GUARD */

Edit file: LuaGluaClass.h

        // std::function and lambda method
        template<typename _Ret, typename... _Args>
        LuaGlueClass<_Class> &method(const std::string &name, std::function<_Ret(_Args...)> fn)
        {
            auto impl = new LuaGlueStdFuncMethod<_Ret, _Class, _Args...>(this, name, std::forward<decltype(fn)>(fn));
            methods.addSymbol(name.c_str(), impl);

            return *this;
        }


        template<typename F>
        struct BindLambda;
        template<typename Lambda, typename Ret, typename... Args>
        struct BindLambda<Ret(Lambda::*)(Args...) const>
        {
            static std::function<Ret(const Args...)> call(const Lambda& lambda)
            {
                return std::function<Ret(Args...)>(lambda);
            }
        };

        template<typename Lambda>
        LuaGlueClass<_Class> &method(const std::string &name, const Lambda& lambda)
        {
            typedef decltype(&Lambda::operator()) F;

            method(name, BindLambda<F>::call(lambda));
            return *this;
        }
        // end std::function and lambda method

Fix:

Edit LuaGluFunction.h[142]

ReturnType ret = applyTuple(g, state, fn_, args);

Edit LuaGluFunction.h[157]

auto mimp = (LuaGlueFunction<std::function<_Ret(_Args...)>> *)lua_touserdata(state, lua_upvalueindex(1));

Edit LuaGluFunction.h[191]

applyTuple<void, _Args...>(g, state, fn_, args);

Add LuaGlue.h

        //---   std::function   ---//
        template<typename Ret_, typename... Args_>
        LuaGlue &func(const std::string &name, std::function<Ret_(Args_...)> fn)
        {
            auto new_func = new LuaGlueFunction<std::function<Ret_(Args_...)>>(this, name, fn);
            functions.addSymbol(name.c_str(), new_func);
            return *this;
        }

        //---   Lambda   ---//
        template<typename F>
        struct BindLambda;
        template<typename Lambda, typename Ret, typename... Args>
        struct BindLambda<Ret(Lambda::*)(Args...) const>
        {
            static std::function<Ret(Args...)> call(const Lambda& lambda)
            {
                return std::function<Ret(Args...)>(lambda);
            }
        };

        template<typename Lambda>
        LuaGlue &func(const std::string &name, const Lambda& lambda)
        {
            typedef decltype(&Lambda::operator()) F;

            func(name, BindLambda<F>::call(lambda));
            return *this;
        }

DominikMS avatar Jun 08 '14 13:06 DominikMS

Hm that's not a bad idea. It would cut down on the number of stack specializations. Though I'm not sure that is valid for both 32bit and 64bit targets. signedness can become an issue depending on the compiler/platform.

Tomasu avatar Jun 08 '14 17:06 Tomasu

Lamda wise, you can pass them into a std::function and pass that around no? (not that I wont add lambda support, but it is a work around)

Tomasu avatar Jun 08 '14 17:06 Tomasu

That's ok. I'll see about adding lambda support .

As for your setRect example, that's a bit more interesting. I haven't added passing table's back to c++ yet. But your example would likely never be possible as written. setRect would have to take a map or a LuaGlueTable (when ever it gets added). Right now you can just make setRect take x,y,w,h as individual parameters. Theres a "func" overload to put a wrapper around a given method. It's technically called a decorator and lets you transform the inputs to different forms. Helpful for cases like this... it'd look something like: (which probably wont work as shown)

class Rect {
    public:
        void setRect(Rect r);
};

void main(int, char **argv)
{
    LuaGlue state;
    state.Class<Rect>("Rect").
    method("setRect", std::function<void(Rect r,int x,int y,int w, int h)>([]{ r.setRect({ .x = x, .y = y, .w = w, .h = h }); }));

    state.open().glue();

    // ....

    return;
}

I /could/ try and figure out if table properties match a given object's registered properties. I'll look into that as well. It'd be slower than passing things as individual arguments, but it would be handy.

Tomasu avatar Jun 08 '14 18:06 Tomasu

It seems std::function support in g.invokeFunction is broken at the moment. I'm working on a fix. And it may allow binding to lamdas.. but internally it'll turn things into std::function's I think.

Tomasu avatar Jun 08 '14 19:06 Tomasu

Ok, bare lambda if possible, aren't simple (as far as I know, you can't "detect" the type of a lambda in any way). So just wrap them in a std::function for now. I've also added a stdfunc example to test a couple uses of them. I've also fixed some uses of std::function.

Tomasu avatar Jun 09 '14 00:06 Tomasu

Hm, ok. I'll put that on the list of things to add. more complete support for std::function would be useful, like allowing non direct properties to get handed two std::functions to "get" and "set" them.

Tomasu avatar Jun 14 '14 13:06 Tomasu

I was trying to add raw lambda support earlier, but clearly my C++11 knowledge is poor :(. Thats fairly clever :o I'll play with that a bit.

I'm working on a large refactor of the Class/Type support, so I may not get to these requests right away. I have no time frame for when I'll get the big changes in and working. I'll do bug fixes on master in the mean time, but new features and big additions will have to wait till I've got the new branch merged with master.

Tomasu avatar Jun 14 '14 15:06 Tomasu

Also, you know what would help me, is if you could split all of your feature requests into separate issues? That would really help me keep track of them :)

Tomasu avatar Jun 14 '14 15:06 Tomasu

There should be a "Add Labels" column beside the issue form with labels in it you can click, one is "enhancement". if it's not there, I'll have to tag em.

Tomasu avatar Jun 14 '14 15:06 Tomasu

Ok. Not a problem.

Tomasu avatar Jun 14 '14 15:06 Tomasu

Hm, I'd prefer to have issues on each one. Much easier for me to keep track of your requests.

Tomasu avatar Jun 14 '14 15:06 Tomasu

No, one per group of similar additions. like one for the integer additions, one for the std::function stuff, etc. go back and look at your other sumissions as well and make sure each one is only about one thing.

Tomasu avatar Jun 14 '14 15:06 Tomasu