lua - сохранить закрытие в C, вызвать асинхронность в C

Мне нужна идея, как я могу хранить закрытия lua, чтобы вызывать их асинхронно позже.

  1. моей первой идеей было lua_tocfunction, но замыкание не является cфункцией и не может быть вызвано из C напрямую
  2. Вторая идея заключалась в том, чтобы сохранить замыкание в метатаблице, чтобы я мог отправить его и вызвать позже, но, похоже, я не могу скопировать замыкание. (Error: attempt to index a function value).

Так что мне нужна ваша помощь, пожалуйста. Как я могу сохранить замыкание?

Признаюсь, я не совсем понял, почему в моем луакторе есть поле __index, так как я скопировал эту часть откуда-то.

Кстати: программа без onrender работала как положено. Я использую qt gui, и lua-состояния закрываются после основного цикла qt, поэтому созданное окно не будет удалено __gc после скрипта.

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

person Aitch    schedule 31.12.2016    source источник
comment
Вы уверены, что индексы правильные в w_render_onrender? Я предполагаю, что пользовательские данные, которые представляют ваш GLWindow *, являются первыми, за которыми следует закрытие. В каком случае это не должно быть lua_setfield(L, -3, "onrender_cb"); или lua_setfield(L, 1, "onrender_cb");?   -  person greatwolf    schedule 31.12.2016
comment
Я думаю, что вам нужно сделать lua_getmetatable(L, 1); lua_insert(L, -2);, прежде чем вы на самом деле попытаетесь установить поле. Я не уверен, что значение push здесь действительно нужно. В любом случае это не используется в вашей лямбде onrender, так как вы выполняете lua_getfield, чтобы вернуть замыкание.   -  person greatwolf    schedule 31.12.2016
comment
@greatwolf Я пробовал 2 ваших решения: -3 или 1 дает мне attempt to index a userdata value, а второе решение также дает мне attempt to index a userdata value. Одна вещь, которую я заметил, это то, что get/setfield следует вызывать с помощью 1, так как doc говорит, что индекс - это место, где таблица хранится в стеке, и теперь мне интересно, как реализация лямбды может быть уверена, что таблица существует, и никто не изменил стек за это время.   -  person Aitch    schedule 31.12.2016
comment
Вы хотите сказать, что lua_getmetatable(L, 1); дает attempt to index a userdata value? это действительно не имеет смысла. Это GLWindow * udata с правильным набором метатаблиц?   -  person greatwolf    schedule 31.12.2016
comment
@greatwolf, что бы ни пошло не так с моим внедрением, ваш ответ работает;)   -  person Aitch    schedule 01.01.2017


Ответы (1)


Похоже, ваша проблема связана с использованием неправильных индексов и попыткой установить/получить поля для неправильного объекта lua в стеке. Предполагая, что сначала за udata, представляющим ваш GLWindow *, следует закрытие lua, попробуйте изменить код следующим образом:

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

Редактировать: Подумав еще немного, возможно, имеет смысл создать ссылку на lua, используя luaL_ref. Таким образом, вам не нужно заботиться о том, что происходит в стеке, когда self->onrender действительно запускается, что, как я предполагаю, является асинхронным:

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;
}
person greatwolf    schedule 31.12.2016
comment
второй фрагмент кода работает как шарм, большое спасибо! Я прав, что корень всех зол в том, что асинхронный код C имеет проблемы с доступом к метатаблице, так как неизвестный стек L? Не лучше ли было бы один раз сослаться на саму udata (в lua ctor), чтобы иметь возможность синхронизировать выборку в lua_CFunction и передать ее в лямбда-выражения, захватив ссылку, как во втором решении. Таким образом, у меня есть только одна ссылка на объект, потому что я предполагаю, что для каждого объекта будет много обратных вызовов. Тем не менее я отмечу ваш ответ как принятый :). - person Aitch; 01.01.2017
comment
@Aitch, да, вы можете назначить lua_cb для udata, а затем сослаться на udata после (просто укажите __newindex, иначе вы снова получите эту ошибку индекса udata). Я указал обратный вызов напрямую только для простоты. - person greatwolf; 01.01.2017
comment
@Aitch Если self->onrender действительно асинхронный, то любое количество вещей может быть выполнено на lua vm до того, как действительно произойдет сам обратный вызов. В этом случае то, что на самом деле находится в стеке в это время, может сильно отличаться от того, что было при вызове w_render_onrender. Я немного удивлен, что асинхронный вызов работает, дизайн lua не имеет внутренних блокировок или примитивов синхронизации. Как вы гарантируете, что L непротиворечиво и что оно не было вытеснено в середине выполнения инструкции байт-кода? - person greatwolf; 01.01.2017
comment
Я попытался написать объект на основе замыкания, чтобы иметь асинхронный пример здесь, но lua говорит bootstrap.lua:12: attempt to call upvalue 'onrender_cb' (a table value) : ( жаль. На самом деле это почти то, что я ожидаю, что мой код C будет выполнять асинхронность. Если я 1. не использую yield и 2. операции на L никогда не выполняются многопоточными, это должно работать, не так ли? вытеснение невозможно без yield и сопрограммы, не так ли? Я искал возможность написания сценариев, и lua impl легок и прост, но я не хочу отказываться от асинхронных сценариев :( - person Aitch; 01.01.2017
comment
Или, может быть, мне нужно использовать что-то вроде lua_State* cL = lua_newthread(L);, чтобы сделать асинхронный вызов, как это сделали бы сопрограммы? Причина, по которой это должно быть возможно, заключается в том, что я нашел (LuaNode)[lua.org /wshop13/Burgueno.pdf], который выполняет асинхронный ввод-вывод. Может быть, я взгляну на их реализацию асинхронных вызовов. - person Aitch; 01.01.2017
comment
Да, это правильно, если это не многопоточный параллелизм, поскольку ОС может прервать ваш запущенный процесс в любое время. Сопрограммы Lua являются потокоподобными, но, строго говоря, они по-прежнему выполняют однопоточное выполнение, поэтому не нужно беспокоиться о блокировке или гонках данных. Я не думал о сопрограммах lua, когда говорил об асинхронности, я думал, что вы на самом деле используете многопоточность реальной ОС. - person greatwolf; 01.01.2017
comment
в этом случае не о чем беспокоиться. - person greatwolf; 02.01.2017