Reputation: 3044
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
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
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
Reputation: 10939
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
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