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