c++clualua-apilua-c++-connection

lua - store closure in C, invoke async in C


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

Solution

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