1

I need an idea, how I can store lua closures to invoke them asynchronously later.

  1. my first idea was lua_tocfunction but a closure is not a cfunction and cannot be invoked from C directly
  2. second idea was to save the closure in the metatable, that I can push it and call it later, but it seems, that I cannot copy a closure. (Error: attempt to index a function value).

So I need your help please. How can I store a closure?

I admit, that I did not completely understand why there is an __index field in my lua ctor as I've copied that part from somewhere.

By the way: the program without onrender worked as expected. I'm using qt gui and the lua-states are closed, after qt's main loop, thus the created window is not going to be delete by __gc after the script.

bootstrap.lua

local w = w_render() -- create window object
w:show()

w:onrender(function()
    print('render')
end)

w_lua.cpp

// chlua_* are helper macros/templates/methods
// 1: self
// 2: render closure

int w_render_onrender(lua_State *L) {
    auto *self = chlua_this<GLWindow *>(L, 1, w_render_table);

    lua_pushvalue(L, 2); // copy closure to top
    lua_setfield(L, 2, "onrender_cb"); // save closure in metatable
    // !!! ERROR: attempt to index a function value



    self->onrender([L](){
        lua_getfield(L, 2, "onrender_cb");
        qDebug() << "onrender";
        lua_call(L, 0, 0);
    });

    return 0;
}

// Creates the object
int w_render(lua_State *L) {
    auto *&self = chlua_newuserdata<GLWindow *>(L);
    self = new GLWindow;

    if (luaL_newmetatable(L, w_render_table)) {
        luaL_setfuncs(L, w_render_methods, 0);
        lua_pushvalue(L, -1);
        lua_setfield(L, -2, "__index");
    }

    lua_setmetatable(L, -2);
    return 1;
}
greatwolf
  • 20,287
  • 13
  • 71
  • 105
Aitch
  • 1,617
  • 13
  • 24
  • Are you sure the indices are right in `w_render_onrender`? I'm assuming self udata that represents your `GLWindow *` is first, follow by the closure second. In which case shouldn't it be `lua_setfield(L, -3, "onrender_cb");` or `lua_setfield(L, 1, "onrender_cb");`? – greatwolf Dec 31 '16 at 16:21
  • I think what you need to do is `lua_getmetatable(L, 1); lua_insert(L, -2);` before you actually try to set the field. I'm not sure the pushvalue is even really needed here. That's not used in your `onrender` lambda anyway since you're doing a `lua_getfield` to fetch back the closure. – greatwolf Dec 31 '16 at 16:35
  • @greatwolf I've tried your 2 solutions: -3 or 1 gives me `attempt to index a userdata value` and the second solution also gives me `attempt to index a userdata value`. One thing I've noticed is, that get/setfield should imo be invoked with `1` since the [doc](https://www.lua.org/manual/5.2/manual.html#lua_setfield) says, that the index is where the table is stored on the stack and now I'm wondering how the lambda's implementation can be sure, that the table is there and nobody changed the stack in the meantime. – Aitch Dec 31 '16 at 17:08
  • Are you saying `lua_getmetatable(L, 1);` is giving `attempt to index a userdata value`? that doesn't really make sense. That's the `GLWindow *` udata with the metatable set correct? – greatwolf Dec 31 '16 at 17:11
  • @greatwolf whatever went wrong with my impl, your [answer](http://stackoverflow.com/a/41410679/4469738) works ;) – Aitch Jan 01 '17 at 11:43

1 Answers1

1

It looks like your problem is stemming from using the wrong indices and attempting to set/get fields on the wrong lua object on the stack. Assuming the udata representing your GLWindow * is first followed by the lua closure second, try changing the code like this:

int w_render_onrender(lua_State *L)
{
  luaL_checkudata(L, 1, w_render_table);
  luaL_checktype(L, 2, LUA_TFUNCTION);
  auto *self = chlua_this<GLWindow *>(L, 1, w_render_table);

  lua_getmetatable(L, 1);
  lua_insert(L, -2);      // GLWindow GLWindow_mt lua_closure
  lua_setfield(L, -2, "onrender_cb"); // save closure in metatable


  self->onrender([L]()
  {
    luaL_checkudata(L, 1, w_render_table);
    // assuming GLWindow udata is self and onrender_cb is your lua closure above
    // access GLWindow.onrender_cb through GLWindows's metatable
    lua_getfield(L, 1, "onrender_cb");
    qDebug() << "onrender";
    luaL_checktype(L, -1, LUA_TFUNCTION); // Just to be sure
    lua_call(L, 0, 0);
  });

  return 0;
}

Edit: After thinking about this some more, it probably makes more sense to create a lua reference using luaL_ref. This way you don't have to care what happens to be on the stack when self->onrender actually runs, which I'm assuming is async:

int w_render_onrender(lua_State *L)
{
  luaL_checkudata(L, 1, w_render_table);
  luaL_checktype(L, 2, LUA_TFUNCTION);
  auto *self = chlua_this<GLWindow *>(L, 1, w_render_table);

  auto lua_cb = luaL_ref(L, LUA_REGISTRYINDEX);
  // just to check that what's on the stack shouldn't matter
  lua_settop(L, 0);

  self->onrender([L, lua_cb]()
  {
    lua_rawgeti(L, LUA_REGISTRYINDEX, lua_cb);
    luaL_checktype(L, -1, LUA_TFUNCTION); // Just to be sure
    qDebug() << "onrender";
    lua_call(L, 0, 0);
    luaL_unref(L, LUA_REGISTRYINDEX, lua_cb); // assuming you're done with it
  });

  return 0;
}
greatwolf
  • 20,287
  • 13
  • 71
  • 105
  • second code snippet works like a charm thank you very much! Am I right, that the root of all evil is that async C code has problems to access the metatable, since unknown stack `L`? Wouldn't it be better design to once ref the udata itself (in the lua ctor) to be able to sync fetch it in the `lua_CFunction` and pass it into lambdas by capturing the ref as in the second solution. This way, I only have one ref per object, because I assume that there will be many callbacks per object. Nevertheless I'll mark your answer as accepted :). – Aitch Jan 01 '17 at 11:41
  • @Aitch yes you can assign the lua_cb to the udata and then ref the udata after(just provide a `__newindex` or you'll get that udata index error again). I ref'ed the callback directly just for simplicity. – greatwolf Jan 01 '17 at 12:05
  • @Aitch If `self->onrender` is really async then any number of things could be executed on the lua vm before the callback itself actually happens. In that case, what's actually on the stack at that time could be very different from what it was when `w_render_onrender` got called. I'm a little surprise the async call works, lua's design has no internal locks or syncing primitives. How do you ensure `L` is consistent and it didn't get prempt'ed in the middle of running a bytecode instruction? – greatwolf Jan 01 '17 at 12:10
  • I tried to write a closure-based object to have an async example [here](https://gist.github.com/anonymous/bcd612cb9398ce67ae80ee9c55e5a594) but lua says `bootstrap.lua:12: attempt to call upvalue 'onrender_cb' (a table value)` :( that's a pity. Actually that nearly is what I expect my C code to do async. If I 1. do not use yield and 2. ops on `L` is never multithreaded it should work, doesn't it? preemption is not possible without yield and coroutines, right? I've searched for a scripting possibilty and lua impl is light and easy, but I don't want to relinquish async scripting :( – Aitch Jan 01 '17 at 12:44
  • Or maybe I need to use something like `lua_State* cL = lua_newthread(L);` to make the async call like coroutines would do? The reason why it should be possible is, that I've found (LuaNode)[https://www.lua.org/wshop13/Burgueno.pdf] which does async io. Maybe I have a look at their impl on async calls. – Aitch Jan 01 '17 at 13:08
  • Yes that is correct, as long as it's not multithreaded concurrency since the os could interrupt your running process at any time. Lua coroutines are thread-like but they're still strictly speaking doing single threaded execution so there is no lock or dataraces to worry about. I wasn't thinking about lua coroutines when talking about async, I thought you were actually using real os multithreading. – greatwolf Jan 01 '17 at 13:44
  • it's nothing to worry about in that case. – greatwolf Jan 02 '17 at 01:05