Passing a Vector<struct> to Lua table - c++

I would like improve my code below by sending a C++ Pre-formatted Lua Table:
int GetCategory(lua_State* L)
{
uint32 Type = CHECKVAL<int>(L, 1);
lua_newtable(L);
int tbl = lua_gettop(L);
uint32 counter = 1;
// Struct CT { string CategoryBrandName, CategoryName }; > Vector<CT>
auto list = sManagerMgr->GetAll();
// Hack modify this to send a metatable/UserData/Table whatever is called
for (auto& elem : list)
{
switch (Type)
{
case 1:
lua_pushstring(L, elem->CategoryBrandName);
break;
case 2:
lua_pushstring(L, elem->CategoryName);
break;
}
lua_rawseti(L, tbl, counter);
counter++;
}
lua_settop(L, tbl);
return 1;
}
Basically,
lua_newtable pushes a table to the lua stack,
lua_gettop will take the top index, so the index where the table is at.
Then lua_pushstring(L, ELEMENT); lua_rawseti(L, tbl, counter); will put the ELEMENT to the table at the index tbl we got with gettop. The index of the element is the value of counter.
But The issue here is that i'm forced to call twice the fonction GetCategory to fill it as follow in my .lua file.
table.insert(Group, { GetCategory(1), GetCategory(2) });
Current Use :
print(i, Group(1)[i], Group(2)[i]);
So.. I would prefer to call it once and get something like this directly :
local Group =
{
[1] = { "elem->CategoryBrandName[1]", "elem->CategoryName[1]" },
[2] = { "elem->CategoryBrandName[2]", "elem->CategoryName[2]" }
--etc
};
I've tried filling elem into an 2D Array[1][2] and then pushing Array unsuccessfully
I've made a lot of research about Table, Metatables, MultiDimentional Arrays etc but I couldn't find something that would fit my need or works.
Does anyone has a solution ?

Why don't you just have your function return both values? Then you could just write
local Group = { GetCategories }
I am no expert with the C API, but I think this could be done fairly easily by just calling lua_newtable(L), so something like this:
int GetCategories(lua_State* L) {
lua_settop(L, 0);
// Discard arguments so we don't have to save the top of the stack
// and can just use numbers instead (see following lines)
lua_newtable(L); // Index 1 on stack
lua_newtable(L); // Index 2 on stack
// Do your magic
lua_settop(L, 2); // Get rid of your temp variables
return 2; // number of values we return in Lua
}
Optimization hint: you can use lua_createtable and tell it how many elements each of the tables will have so Lua can pre-allocate some memory for it.
EDIT: I just noticed this, but in your code:
for (auto& elem : list) {
switch (Type) {
case 1:
lua_pushstring(L, elem->CategoryBrandName);
break;
case 2:
lua_pushstring(L, elem->CategoryName);
break;
}
lua_rawseti(L, tbl, counter);
counter++;
}
You just keep pushing values to the stack. This may, for long vectors, overflow the stack (sooner rather than later), leading to trouble. A better approach would be to 1) push to stack 2) insert into table 3) pop them back off:
// Modified for my suggested implementation that returns
// two tables. They can easily be turned around here.
for (auto& elem : list) {
lua_pushstring(L, elem->CategoryBrandName);
lua_rawseti(L, 1, counter++);
lua_pop(L, 1);
lua_pushstring(L, elem->CategoryName);
lua_rawseti(L, 2, counter++);
lua_pop(L, 1);
}
It's always a good idea to be aware of what is and what isn't on the stack. Saving some memory can not only improve performance, but also avoid potential problems due to (Lua) stack overflow.
One last detail: You don't need ; in Lua, and it is considered bad style to use them unless you have two statements in one line print('more readable'); print('like this').

In case if anyone is looking to something similar, here's how I managed by using lua_createtable. It works as intended, It may need some improvement thought.
int GetCategory(lua_State* L)
{
int counter = 1;
int MaxListSize = 2;
auto Categories = sManagerMgr->GetAll();
lua_createtable(L, Categories.size(), 0);
for (auto& elem : Categories)
{
vector<string> list;
list.reserve(MaxListSize);
list.emplace_back(elem->CategoryBrandName);
list.emplace_back(elem->CategoryName);
Macro::Push(L, counter); // custom
lua_createtable(L, 0, MaxListSize);
for (int i = 1; i <= MaxListSize; i++)
{
Macro::Push(L, list.at(i - 1)); // custom
lua_rawseti(L, -2, i);
}
lua_settable(L, -3);
list.clear();
counter++;
}
return 1;
}
Would produce an output similar to
local Group =
{
[1] = { "elem->CategoryBrandName[1]", "elem->CategoryName[2]" },
[2] = { "elem->CategoryBrandName[1]", "elem->CategoryName[2]" }
--etc
};

Related

Accessing a Lua table within a table from C++ side

I'm trying to transfer a table where there may be nested tables from the lua and write to a .ini file. But I just can not how I need to go on the stack to get data from nested tables. This code does not work as it should. Function setData work not correctly. What could be the problem?
C++ code
int sasl::LuaUtilities::luaWriteIni(LuaState inOutState)
{
string path;
boost::property_tree::ptree iniTree;
LUA_CHECK_NARG_EQ(inOutState, 2);
LUA_GET_PARAM(inOutState, 1, path);
int nargs = lua_gettop(inOutState);
for (int i = 1; i <= nargs; i++) {
if (lua_istable(inOutState, nargs)) {
setData(inOutState, nargs, iniTree);
}
}
return 0;
}
void sasl::LuaUtilities::setData(LuaState inOutState, int index, boost::property_tree::ptree & inIniTree)
{
// Push an initial nil to init lua_next
lua_pushnil(inOutState);
// Parse the table at index
while (lua_next(inOutState, index))
{
if (lua_istable(inOutState, index))
{
setData(inOutState, index, inIniTree);
}
else
{
string key = lua_tostring(inOutState, -2);
string value = lua_tostring(inOutState, -1);
}
// Pop value, keep key
lua_pop(inOutState, 1);
}
return;
}
Lua code
t = {}
local fileName = findResourceFile("test.ini")
t = readINI(fileName)
writeINI(fileName, t) --this not work in c++ side
There are two problems. lua_istable(inOutState, index) is wrong, because index is not the value of the key retrieved by next. That index is always the table you're iterating over. So you'll infinitely recurse over the same table when you call setData with that index.
In fact, passing index to setData itself is almost certainly wrong. Or at least, it's probably not right. You want to use relative indices here, but calling next pushes an extra value onto the stack.
What you probably want to do is have setData assume that the table to iterate over is at index -1 (ie: the top of the stack). That way, you're just calling lua_next(state, -2) (this is -2 because the key to get the next one for is at the top). And when you recursively call setData for a table value, you don't need to provide an index, because the table value is already at the top of the stack.
The second problem is that you never write the key/value pairs. You also never check to see if the value is something which can be converted to a string. It could be a Lua userdata.

Table as parameter in C function called from Lua

I'm dealing with Lua for a longer time but there is one point I can't achieve.
In a C function which is called from Lua I'm able to read a global Lua table using the table name like this:
C:
// get table
lua_getglobal(L, "tableName");
if (!lua_istable(L, -1))
break;
// count entries in table
ULONG numEntries = 0;
lua_pushnil(L);
while(lua_next(L,-2))
{
numEntries++;
lua_pop(L, 1);
}
but if I have a lua function which calls a C function like this:
Lua:
luaTable = { }
luaTable.Param1 = Value1
luaCallC("This is a Text", luaTable)
How do I access the table argument?
C:
// get table
// ???
// count entries in table
ULONG numEntries = 0;
lua_pushnil(L);
while(lua_next(L,-2))
{
numEntries++;
lua_pop(L, 1);
}
Arguments to a CFunction are pressed onto the virtual stack in the order that they are provided, and it is simply up to you to do the error checking required before you operate on these values.
Lua 5.3 Manual ยง4.8 - lua_CFunction:
In order to communicate properly with Lua, a C function must use the following protocol, which defines the way parameters and results are passed: a C function receives its arguments from Lua in its stack in direct order (the first argument is pushed first).
[ ... ]
The first argument (if any) is at index 1 and its last argument is at index lua_gettop(L). To return values to Lua, a C function just pushes them onto the stack, in direct order (the first result is pushed first), and returns the number of results
An example of exhaustively checking the number of elements in a table, with an arbitrary first argument.
int count (lua_State *L) {
luaL_checktype(L, 2, LUA_TTABLE);
puts(lua_tostring(L, 1));
size_t ec = 0;
lua_pushnil(L);
while (lua_next(L, 2)) {
lua_pop(L, 1);
ec++;
}
lua_pushinteger(L, (lua_Integer) ec);
return 1;
}
After registering the function for use in Lua:
count('foo', { 'a', 'b', 'c' }) -- 3

Lua C++ Table Iteration

I am having a slight confusion of how lua_next really works. User defines a table:
a={["a1"]=20,["a2"]=30}
I want to print this table with a C++ code:
inline int lua_print(lua_State* L)
{
wxString wxReturnStr=wxEmptyString;
wxString tempString=wxEmptyString;
int nargs = lua_gettop(L);
for (int i=1; i <= nargs; i++)
{
int type = lua_type(L, i);
switch (type)
{
case LUA_TNIL:
break;
case LUA_TBOOLEAN:
tempString<<(lua_toboolean(L, i) ? "true" : "false");
break;
case LUA_TNUMBER:
tempString<<lua_tonumber(L, i);
break;
case LUA_TSTRING:
tempString<<lua_tostring(L, i);
break;
case LUA_TTABLE:
{
lua_pushnil(L);
while(lua_next(L,-2))
{
const char* key=lua_tostring(L,-2);
double val=lua_tonumber(L,-1);
lua_pop(L,1);
tempString<<key<<"="<<val<<"\t";
}
break;
}
default:
tempString<<lua_typename(L, type);
break;
}
wxReturnStr=wxReturnStr+tempString+"\n";
tempString=wxEmptyString;
}
lua_pop(L,nargs);
This code works very well when I call from Lua:
print(a) -- Works well
However, imagine I have a table in Lua as:
b={["b1"]=10, ["b2"]=15}
if I call the code as:
print(a,b) -- Twice prints only contents of b
My understanding with how lua_next work is in the following figure: [Edition #1]
Where is the bug?
The bug is in lua_next(L, -2) line, because -2 refers to stack top minus one, which happens here to be the last argument to print.
Use lua_next(L, i) instead.
Upd: Lua stack indexes are subject to float when moving code around at development stage, so the general advice is to pin indexes with sml int t = lua_gettop(L) just after getting/pushing/considering values and to use that t instead of -n (though this specific case appears to be sort of a keystroke bug.)
You forgot the lua_pop after processing the table.
lua_pushnil(L);
while(lua_next(L,-2))
{
const char* key=lua_tostring(L,-2);
double val=lua_tonumber(L,-1);
lua_pop(L,1);
tempString<<key<<"="<<val<<"\t";
}
lua_pop(L, 1); // THE FIX, pops the nil on the stack used to process the table
This means, that extra nil is left on the stack, so in the second iteration, the
case LUA_TNIL:
break;
just prints nothing.
About your graphics representation of the stack. The command under each image represents the state after the command was called. So the very last image is missing the [Key = a2] item on the stack.

How to generate Lua iterators for C++ std::vector using SWIG?

In lua/std_vector.i in SWIG 2.0.8 comment says:
And no support for iterators & insert/erase
but maybe someone knows how to do it?
For example, it's possible to add #length operator by defining __len (it may work by accident, I've found it by trials and errors):
%include "std_vector.i"
%extend std::vector { int __len(void*) { return self->size(); } }
namespace std {
%template(PointVector) vector<fityk::Point>;
}
I've tried similar trick with __call, but I'm stuck. SWIG wrapping gets in the way. I've tried using %native, but I couldn't make it work together with %extend.
I don't know how to do it in SWIG template, but I managed to plug __call to SWIG-generated metatable from C++, after calling luaopen_module:
SWIG_Lua_get_class_metatable(L_, PointVector);
SWIG_Lua_add_function(L_, "__call", lua_vector_iterator);
So the vector can be used as iterator. A bit awkward 2 in 1, but works:
> = points
<PointVector userdata: 190FBE8>
> for n, p in points do print(n, p) end
0 (24.0154; 284; 16.8523)
1 (24.0541; 343; 18.5203)
The iterator returns index and value, like enumerate() in Python, and Lua passes the index back to the iterator next time (3rd arg on the stack). I haven't seen this behaviour documented, so I may rely on implementation details.
Here is the function used as __call():
// SWIG-wrapped vector is indexed from 0. Return (n, vec[n]) starting from n=0.
static int lua_vector_iterator(lua_State* L)
{
assert(lua_isuserdata(L,1)); // in SWIG everything is wrapped as userdata
int idx = lua_isnil(L, -1) ? 0 : lua_tonumber(L, -1) + 1;
// no lua_len() in 5.1, let's call size() directly
lua_getfield(L, 1, "size");
lua_pushvalue(L, 1); // arg: vector as userdata
lua_call(L, 1, 1); // call vector<>::size(this)
int size = lua_tonumber(L, -1);
if (idx >= size) {
lua_settop(L, 0);
return 0;
}
lua_settop(L, 1);
lua_pushnumber(L, idx); // index, to be returned
lua_pushvalue(L, -1); // the same index, to access value
lua_gettable(L, 1); // value, to be returned
lua_remove(L, 1);
return 2;
}

How to return two or more tables from lua function?

I would be really appreciative if someone explain me how lua-C stack works when lua function called from C returns two tables or table which has nested table inside
when I am trying to do it it seems to look fine but only at first glimpse:
if ( lua_istable(L, -1) )
printf("its OK\n");
if ( lua_istable(L, -2) )
printf("its OK\n");
lua_pushnil(L);
while ( lua_next(L, -2) )
{
if(lua_isnumber(L, -1))
{
int i = (int)lua_tonumber(L, -1);
const char *key = lua_tostring(L, -2);
printf("%d %s \n", i, key);
}
lua_pop(L, 1);
}
lua_pop(L, 1);
In this case I got two messages that first table is on level -1, on the second one is on level -2, but afterthis code, when I am trying to get the next table my program crashes
when I check stack on table existence
for ( int i = -2; ; --i)
if ( lua_istable(L, i) )
printf("its %d OK\n", i);
I got following result:
its -233 OK
its -645 OK
its -1245 OK
its -1549 OK
its -2807 OK
its -2815 OK
its -2816 OK
can somebody help me out with this ?
Note that when lua_next returns 0 is has popped the key, and pushed nothing, so at the end of the while loop you have your two tables on the stack.
The lua_pop after your while loop is popping the top table off the stack.
The subsequent for loop is starting at index -2 which is past the table, and could contain anything. Furthermore, the for loop will never terminate.