Phil
Phil

Reputation: 713

Passing a Vector<struct> to Lua table

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 ?

Upvotes: 0

Views: 1946

Answers (2)

Phil
Phil

Reputation: 713

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

Upvotes: 0

DarkWiiPlayer
DarkWiiPlayer

Reputation: 7056

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').

Upvotes: 1

Related Questions