3

I have a lua script which contains this table:

test = {}
test[1] = "Naya"
test[2] = 1000
test[3] = 1
test[4] = 20

I have a struct in my C++ program. Intended to be filled with the information contained within that table:

//Enemy Struct
struct Enemy
{
    string name;
    int health;
    int family;
    int level;
} enemy;

It is easy enough to fill the struct for a table of known size and known key names.

it becomes slightly more challenging when there is:

  • a table of unknown size (simple to find table length)
  • numerical key names. (simple to just use i)
  • fill the struct (the part I'm having an issue with)

What I would like is to fill this Struct using a simple for loop, like this:

for (int i = 1; i < length; i++)
        {
            lua_pushnumber(L, i);
            lua_gettable(L, -2);
            if (lua_isnumber(L, -1))
                enemy.at(i)? = lua_tonumber(L, -1);
            else
                enemy.at(i) ? = lua_tostring(L, -1);

            lua_pop(L, 1);
        }

Struct has no .at(x) function. And therefore I cannot use this simple loop to iterate through the struct to fill it.

I have thought about using a 2D array with a vector/struct. But I was unsure if this would be effective.

I would like the Struct to be filled with data from the lua script using a loop.

  • "Struct has no ".at(x)" function. " - No. C++ does not have *reflection* - which seems to be the feature you are looking for. – Jesper Juhl Aug 06 '19 at 19:14

3 Answers3

1

You would need some sort of reflection to be able to dynamically get the member at index i.
Unfortunately there is nothing in standard c++ that could do that for you.

There are a few ways how you could get this to work:

  • The easiest solution would be to just read the values and assign them directly to the enemy object, e.g.:
Enemy e;
lua_pushnumber(L, 1);
lua_gettable(L, -2);
e.name = lua_tostring(L, -1);
// ...

This however will become very messy very quickly if you have lots of members.

  • assuming you could use the same type for all members (e.g. all strings) you could use a vector instead of the Enemy struct, e.g.:
std::vector<std::string> enemy;
for(int i = 0; i < length; i++) {
  lua_pushnumber(L, i);
  lua_gettable(L, -2);
  enemy.push_back(lua_tostring(state, -1));
  lua_pop(L, 1);
}

The obvious disadvantage of this solution is that you need to use indexes into vector instead of fancy names like health, name, etc.. and that you're limited to a single type.

#include <boost/fusion/adapted/struct/adapt_struct.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/algorithm/iteration.hpp>
#include <lua/lua.hpp>

struct Enemy
{
    std::string name;
    int health;
    int family;
    int level;
} enemy;

BOOST_FUSION_ADAPT_STRUCT(
    Enemy,
    (std::string, name)
    (int, health)
    (int, family)
    (int, level)
)

struct LuaVisitor {
    LuaVisitor(lua_State* state) : idx(1), state(state) {}

    void operator()(std::string& strVal) const {
        next();
        // TODO: check if top of stack is actually a string (might be nil if key doesn't exist in table)
        strVal = lua_tostring(state, -1);
        lua_pop(state, 1);
    }

    void operator()(int& intVal) const {
        next();
        // TODO: check if top of stack is actually a number (might be nil if key doesn't exist in table)
        intVal = lua_tonumber(state, -1);
        lua_pop(state, 1);
    }

    void next() const {
        lua_pushnumber(state, idx++);
        lua_gettable(state, -2);
    }

    mutable int idx;
    lua_State* state;
};


int main(int argc, char* argv[]) {
    // create a sample table
    lua_State* state = luaL_newstate();
    luaL_dostring(state, "return {\"Hello World\", 1337, 12, 123}");

    // read the enemy from the table
    Enemy e;
    boost::fusion::for_each(e, LuaVisitor(state));

    std::cout << "[Enemy] Name: " << e.name << ", health: " << e.health << ", family: " << e.family << ", level: " << e.level << std::endl;

    lua_close(state);
    return 0;
}

This will give you the ability to dynamically set the members of the Enemy struct, however boost is very heavy-weight & you need to keep the BOOST_FUSION_ADAPT_STRUCT macro up-to-date with the actual struct.

  • You can also use a lua binding library like e.g. luaaa to map your Enemy object directly to lua (instead of passing a table around):
#include <lua/luaaa.h>

struct Enemy
{
    Enemy() {}
    virtual ~Enemy() {}

    void init(std::string _name, int _health, int _family, int _level) {
        name = _name;
        health = _health;
        family = _family;
        level = _level;
    }

    std::string name;
    int health;
    int family;
    int level;
};

void myFunction(Enemy* e) {
    std::cout << "[Enemy] Name: " << e->name << ", health: " << e->health << ", family: " << e->family << ", level: " << e->level << std::endl;
}

int main(int argc, char* argv[]) {
    // init lua & bindings
    lua_State* state = luaL_newstate();
    luaaa::LuaClass<Enemy> luaEnemy(state, "Enemy");
    luaEnemy
        .ctor()
        .fun("init", &Enemy::init);
    luaaa::LuaModule mod(state, "sampleModule");
    mod.fun("myFunction", myFunction);

    luaL_dostring(state, R"(
      enemy = Enemy.new()
      enemy:init("My Enemy", 1, 2, 3)
      sampleModule.myFunction(enemy)
    )");

    lua_close(state);
    return 0;
}

then you can directly use the enemy object in lua:

enemy = Enemy.new()
enemy:init("My Enemy", 1, 2, 3)
sampleModule.myFunction(enemy)

Hopefully one of those options covers your use case ;)

Turtlefight
  • 9,420
  • 2
  • 23
  • 40
0

Does this have to work with information of various sizes? Otherwise you could always fill it by assigning the values like this: Enemy.name = ...;. Or there's the option of writing the data to a .csv file (Comma-seperated values, just in case) and then reading that into the struct.
If it's a one-time thing, you could also just initialize the struct like that Enemy enemy = Enemy{name, ...}; Hope this helps :)

NightDice
  • 116
  • 7
  • The Table could have any size within the lua script. The intention was to avoid having to manually write identical and repetitive code for each new key name. – Johnny Mccrum Aug 06 '19 at 19:26
  • Okay, then I'd recommend not using a struct and instead use something like a vector. For a vector, you could iterate over it à la `for(int i=0;i – NightDice Aug 06 '19 at 19:35
0

I think there are three ways to achieve this:

1) Simplest, create a vector, read the file line by line, parse what is after ...=? and then put it into the vector, perhaps all converted to string. So, you'll be using a std::vector<string> for everything to be stored.

2) Alright difficulty, you can use a class instead, and populate std::vector<Object*> in it, dynamically. Same as the above, but now you can save as int or string etc.

3) Hardest, you need to implement a form of reflection. Here is what I could find: How can I add reflection to a C++ application?

It's a good read anyway.

Okan Barut
  • 279
  • 1
  • 7
  • You're probably right with the string vector, I guess I could then just convert the array to it's correct types immediately after. – Johnny Mccrum Aug 07 '19 at 00:08