Zack Lee
Zack Lee

Reputation: 3044

How to get updated value of table sent from C++ to Lua function?

I'm trying to pass a float vector from C++ function to Lua function as a table parameter and then get the updated value of the vector after the Lua function call.

Here's the simple example code.

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

And then I can call this function as the following.

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

As I commented in the code, I would like to update the elements of vector<float> vec after calling the Lua function.

Is it possible to pass the array to Lua function as a reference? (like how it works in C++ functions)

If not, is it possible to get the values of Lua table(t) so I can write them back to the float vector in C++ after calling the function?

Thanks!

Upvotes: 3

Views: 2811

Answers (3)

celticminstrel
celticminstrel

Reputation: 1679

The simplest way to get a vector out of a Lua array table is the following:

std::vector<float> vec;
const size_t table_idx = -1;
const size_t length = lua_rawlen(L, table_idx);
vec.reserve(length);
for(size_t i = 1; i <= length; i++) {
    lua_rawgeti(L, table_idx, i);
    vec.push_back(luaL_checknumber(L, -1));
    lua_pop(L, 1);
}

Unlike the accepted answer, this method queries Lua for the length of the array, instead of using the pre-existing length of the array on the C++ side.

It could also be done without raw lookup by simply using lua_len and lua_geti instead of lua_rawlen and lua_rawgeti. (However, I think lua_geti doesn't exist in older Lua versions, so in that case you'd need to use lua_gettable like in the accepted answer.)

Upvotes: 0

Roberto
Roberto

Reputation: 96

Just for future references, I (try to) enhance Henri's code, put a function to send 2d arrays form C to lua and put some comments. If helps anyone...

#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>

#include <iostream>
#include <vector>
#include <string>
#include <sol/assert.hpp>

using namespace std;

template <typename T, typename U>
void as_table(lua_State* L, T begin, U end, char *tablename) {
    lua_newtable(L);
    for(int i=0; begin!=end; ++begin, i++) {
        lua_pushnumber(L, i);
        lua_pushnumber(L, *begin);
        lua_rawset(L, -3);
    }
    lua_setglobal(L, tablename);
}

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);
        lua_gettable(L, -2);
        *begin = lua_tonumber(L, -1);
        lua_pop(L, 1);
    }
}

template <typename T>
void as_2Darray(lua_State *L, T **arr, int n0, int n1) {
    lua_newtable(L);
    for (int i=0; i<n0; i++) {
        lua_pushnumber(L, i);
        lua_newtable(L);
        for (int j=0; j<n1; j++) {
            lua_pushnumber(L, j);
            lua_pushnumber(L, arr[i][j]);
            lua_rawset(L, -3);
        }
        lua_rawset(L, -3);
    }
}

int main(int argc, char *argv[]) {
    /**
    Full example of setting and getting variables from and to Lua/C++
    */

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

    ///Sending x=11 to Lua
    lua_pushnumber(L, 11);
    lua_setglobal(L, "x");

    ///Sending a vector to Lua
    vector<double> cvec(10, 1222); //vector with some noise
    cvec[0] = 333;
    cvec[1] = 444;
    cvec[9] = 999;
    as_table(L, cvec.begin(), cvec.end(), "vetor");


    /**
    For future reference: Lua works based on something like a "stack pattern" (I know that there should be a nicer name, just be kind, ok?).
    Simplifying a lot: we push stuff and then execute commands based on what is on the stack.
    More info:
    https://www.lua.org/pil/24.2.1.html
    https://www.youtube.com/watch?v=4l5HdmPoynw
    https://github.com/tylerneylon/lua_api_demo

    Eg: the following 4 lines:
    */
    ///Sending an array foo={47, 80} to Lua
    lua_newtable(L);        // put a table on the stack
    lua_pushnumber(L, 0);   // put the number "0" on the stack
    lua_pushnumber(L, 47);  // put the number "47" on the stack
    lua_rawset(L, -3);      // put the value of stack[-1] at index identified on stack[-2] on the table of stack [-3]
                            // i.e., index=0 -> value = 47

    lua_pushnumber(L, 1); //index
    lua_pushnumber(L, 80); //value
    lua_rawset(L, -3);

    lua_setglobal(L, "foo");   //Label the table at stack[-1] as foo

    ///Execute some code
    luaL_dostring(L, "x=15\nprint(x)\nprint(foo[0])\nprint(vetor[0])\nfoo[0] = 387\nprint(foo[0])");

    ///Get value of x
    lua_getglobal(L,"x");
    double x = (double)lua_tonumber(L, -1);

    cout << "x from lua: " << x << endl;

    ///Get an element from array foo
    lua_getglobal(L, "foo");
    lua_pushinteger(L, 0);
    lua_gettable(L, -2);
    cout << "foo[0] = " << lua_tonumber(L, -1) << endl;

    ///Get the vector
    vector<double> luavec(10, 2);
    lua_getglobal(L, "vetor");
    from_table(L, luavec.begin(), luavec.end());

    int a = 0;
    for(auto i:luavec) {
        cout << a<<".luavec: " << i << endl;
        a++;
    }

    double **alpha = new double*[2];
    alpha[0] = new double[3];
    for (int i=0; i<3; i++) {
        alpha[0][i] = i+1;
    }
    alpha[1] = new double[3];
    for (int i=0; i<3; i++) {
        alpha[1][i] = i+10;
    }

    as_2Darray(L, alpha, 2, 3);
    lua_setglobal(L, "twod");

    luaL_dostring(L, "print('twod:',twod[0][0], twod[1][0], twod[0][1])");
}

Upvotes: 0

Henri Menke
Henri Menke

Reputation: 10939

Converting std::vector to and from Lua table

As discussed in chat it might be desirable to convert the function arguments from std::vector<float> to Lua tables and the return value from Lua table to std::vector<float>. The advantage of this is that it is completely transparent at the Lua end.

The function as_table creates a new table from a pair of iterators, from_table converts from a Lua table on top of the stack to a pair of iterators.

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

Here is the little Lua script which is to be used with the testcase above.

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

Pushing the vector as a table is advantageous if most of the data manipulation is done on the Lua end, because Lua tables are actually pretty fast. If, however, most of the computations were done on the C++ end and the Lua end would only pass around the data between functions implemented in C++ this approach would add a large overhead, because we'd spend lots of time converting back and forth between Lua tables and std::vector. To this end, Lua provides userdata, a method to wrap C/C++ datastructures in such a way that they apprear as native Lua datatypes. The downside is, that when offering functions to inspect userdata from Lua, those are usually slow, because arguments have to be checked repeatedly and multiple nested functions have to be called. Combine this with metatables to have syntactic sugar for array access and length operations and you will find yourself in performance hell.

That said, I have constructed an example of pushing the vector as userdata and set its metatable. This process is also described in the chapter 28.1 – Userdata in the book “Programming in Lua” (read it!).

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

This can execute the following Lua script

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

Given a global function transform_vector, e.g.

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

one can call this function with a vector argument and retrieve a vector result just like with any other Lua function.

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

Upvotes: 2

Related Questions