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