Как получить обновленное значение таблицы, отправленной из C ++ в функцию Lua? - PullRequest
0 голосов
/ 29 мая 2018

Я пытаюсь передать вектор с плавающей запятой из функции C ++ в функцию Lua в качестве параметра таблицы, а затем получить обновленное значение вектора после вызова функции Lua.

Вот простой пример кода.

void myFunc(lua_State *L, std::vector<float> &vec) {

    lua_getglobal(L, "myFunc");
    lua_newtable(L);

    for (size_t i=0; i<vec.size(); ++i) {

        lua_pushinteger(L, i+1);
        lua_pushnumber(L, vec[i]);
        lua_settable(L, -3);
    }
    if (lua_pcall(L, 1, 0, 0) != 0) {

        std::cout << "Error : Failed to call myFunc" << std::endl;
    }
}

И тогда я могу вызвать эту функцию следующим образом:

std::vector<float> vec = {1,2,3,4,5}; //an array that will be sent to Lua as table
    myFunc(L, vec); //call "myFunc" function in Lua and pass the array as an argument

    /* <Lua function which will be called>
     function myFunc(t)
        for i=1, #t do
            t[i] = t[i] * 2
        end
     end
     */

    //how to update elements of "vec" here so it now becomes {2,4,6,8,10}?

Как я прокомментировал в коде, я хотел бы обновить элементы vector<float> vec после вызова Luaфункция.

Можно ли передать массив функции Lua в качестве ссылки?(например, как это работает в функциях C ++)

Если нет, возможно ли получить значения таблицы Lua (t), чтобы я мог записать их обратно в вектор с плавающей точкой в ​​C ++ после вызова функции?

Спасибо!

1 Ответ

0 голосов
/ 30 мая 2018

Преобразование std :: vector в таблицу Lua и из нее

Как уже говорилось в чате , может быть желательно преобразовать аргументы функции из std::vector<float> в таблицы Lua и возвращаемое значение изЛуа стол до std::vector<float>.Преимущество этого состоит в том, что он полностью прозрачен в конце Lua.

Функция as_table создает новую таблицу из пары итераторов, from_table преобразует таблицу Lua поверх стека впара итераторов.

#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>

#include <lua.hpp>

template <typename T, typename U>
void as_table(lua_State* L, T begin, U end) {
    lua_newtable(L);
    for (size_t i = 0; begin != end; ++begin, ++i) {
        lua_pushinteger(L, i + 1);
        lua_pushnumber(L, *begin);
        lua_settable(L, -3);
    }
}

template <typename T, typename U>
void from_table(lua_State* L, T begin, U end) {
    assert(lua_istable(L,-1));
    for (size_t i = 0; begin != end; ++begin, ++i) {
        lua_pushinteger(L, i + 1);
        lua_gettable(L, -2);
        *begin = lua_tonumber(L, -1);
        lua_pop(L, 1);
    }
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <script.lua>\n";
        return 1;
    }

    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    if (luaL_dofile(L, argv[1]) != 0) {
        std::cerr << "lua_dofile failed: " << lua_tostring(L, -1) << '\n';
        lua_close(L);
        return 1;
    }

    lua_getglobal(L, "perform");

    std::vector<float> iv(2000, 1);
    std::vector<float> ov(2000, 2);

    as_table(L, iv.begin(), iv.end());
    as_table(L, ov.begin(), ov.end());

    if (lua_pcall(L, 2, 1, 0) != 0) {
        std::cerr << "lua_pcall failed: " << lua_tostring(L, -1)
                  << '\n';
        lua_close(L);
        return 1;
    }

    std::vector<float> w(2000);
    from_table(L, w.begin(), w.end());

    assert(std::all_of(w.begin(), w.end(),
                       [](float p) { return p == 3.0f; }));
}

Вот небольшой скрипт Lua, который должен использоваться с описанным выше тестовым примером.

function perform(v1,v2)
    local n = math.min(#v1,#v2)
    local v = {}
    for i = 1,n do
        v[i] = v1[i] + v2[i]
    end
    return v
end

std :: vector as userdata

* 1018Выдвижение вектора в виде таблицы полезно, если большая часть манипуляций с данными выполняется на конце Lua, потому что таблицы Lua на самом деле довольно быстрые.Однако, если бы большинство вычислений выполнялось на стороне C ++, а конец Lua передавал бы только данные между функциями, реализованными в C ++, такой подход привел бы к большим накладным расходам, потому что мы тратили бы много времени на преобразование назад и вперед междуLua и std::vector.Для этого Lua предоставляет userdata , метод для обертывания структур данных C / C ++ таким образом, что они воспринимаются как нативные типы данных Lua.Недостатком является то, что при предложении функций для проверки пользовательских данных из Lua они обычно медленны, потому что аргументы должны проверяться многократно и вызываться несколько вложенных функций.Объедините это с метатаблями, чтобы иметь синтаксический сахар для доступа к массиву и операций с длиной, и вы окажетесь в адской производительности.

Тем не менее, я построил пример использования вектора как пользовательских данных и установил его метатабельность.Этот процесс также описан в главе 28.1 - Данные пользователя в книге «Программирование на Lua» (читай!).

#include <iostream>
#include <vector>

#include <lua.hpp>

std::vector<float>& checkvector(lua_State *L, int index) {
    std::vector<float> *v = *static_cast<std::vector<float> **>(
        luaL_checkudata(L, index, "std::vector<float>"));
    luaL_argcheck(L, v != nullptr, index, "invalid pointer");
    return *v;
}

static int newvector(lua_State *L) {
    size_t size = luaL_checkinteger(L, 1);
    luaL_argcheck(L, size >= 0, 1, "invalid size");
    *static_cast<std::vector<float> **>(lua_newuserdata(
        L, sizeof(std::vector<float> *))) = new std::vector<float>(size);

    luaL_getmetatable(L, "std::vector<float>");
    lua_setmetatable(L, -2);
    return 1;
}

void pushvector(lua_State *L, std::vector<float> const &v) {
    std::vector<float> *udata = new std::vector<float>();
    *udata = v;
    *static_cast<std::vector<float> **>(lua_newuserdata(
        L, sizeof(std::vector<float> *))) = udata;

    luaL_getmetatable(L, "std::vector<float>");
    lua_setmetatable(L, -2);
}

static int deletevector(lua_State *L) {
    delete &checkvector(L, 1);
    return 0;
}

static int setvector(lua_State *L) {
    std::vector<float> &v = checkvector(L, 1);
    size_t index = luaL_checkinteger(L, 2) - 1;
    luaL_argcheck(L, index < v.size(), 2, "index out of range");
    luaL_argcheck(L, lua_isnumber(L, 3), 3, "not a number");
    float record = lua_tonumber(L, 3);

    v.at(index) = record;

    return 0;
}

static int getvector(lua_State *L) {
    std::vector<float> &v = checkvector(L, 1);
    size_t index = luaL_checkinteger(L, 2) - 1;
    luaL_argcheck(L, index < v.size(), 2, "index out of range");

    lua_pushnumber(L, v.at(index));

    return 1;
}

static int getsize(lua_State *L) {
    std::vector<float> &v = checkvector(L, 1);

    lua_pushinteger(L, v.size());

    return 1;
}

static int vectortostring(lua_State *L) {
    std::vector<float> &v = checkvector(L, 1);

    lua_pushfstring(L, "std::vector<float>(%d)", v.size());

    return 1;
}

static const struct luaL_Reg vector_float_lib[] = {
    {"new", newvector},
    {nullptr, nullptr} // sentinel
};

static const struct luaL_Reg vector_float_meta[] = {
    {"__tostring", vectortostring},
    {"__newindex", setvector},
    {"__index", getvector},
    {"__len", getsize},
    {"__gc", deletevector},
    {nullptr, nullptr} // sentinel
};

int luaopen_vector_float(lua_State *L) {
    luaL_newmetatable(L, "std::vector<float>");
    luaL_setfuncs(L, vector_float_meta, 0);
    luaL_newlib(L, vector_float_lib);
    return 1;
}


static int send_vector(lua_State *L) {
    std::vector<float> v = { 1, 2, 3, 4 };
    pushvector(L,v);
    return 1;
}

static int retrieve_vector(lua_State *L) {
    std::vector<float> &v = checkvector(L, 1);
    for (auto const &p : v) {
        std::cout << p << '\n';
    }
    return 0;
}

int main(int argc, char *argv[]) {
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    luaL_requiref(L, "vector", luaopen_vector_float, 1);
    lua_pop(L, 1);

    lua_pushcfunction(L,send_vector);
    lua_setglobal(L,"send_vector");

    lua_pushcfunction(L,retrieve_vector);
    lua_setglobal(L,"retrieve_vector");

    if (argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <script.lua>\n";
        return 1;
    }

    luaL_dofile(L, argv[1]);

    lua_close(L);
}

Это может выполнить следующий сценарий Lua

local v = send_vector()
for i = 1,#v do
    v[i] = 2*v[i]
end
retrieve_vector(v)

При наличии глобальной функции transform_vector, например,

function transform_vector(v)
    for i = 1,#v do
        v[i] = 2*v[i]
    end
    return v
end

, эту функцию можно вызвать с помощью аргумента вектора и получить векторный результат, как и с любой другой функцией Lua.

std::vector<float> v = { 1, 2, 3, 4 };
lua_getglobal(L,"transform_vector");
pushvector(L,v);
if (lua_pcall(L,1,1,0) != 0) {
    // handle error
}
std::vector<float> w = checkvector(L, -1);
...