Is Lua embedded in C++ capable of having persistent local variables? If not, is there a scripting language that does?

StackOverflow https://stackoverflow.com/questions/14087686

Вопрос

I have embedded Lua in my C++ application using LuaBind. I need to have variables that persist across multiple runs, that can't be accessed by other objects that run the same file name.

For example: let's say I have a class called NPC. An NPC holds a string, that is the name of the script they run. When an NPC is created, a variable is created called Health. When an NPC is hit, they lose 5 health. The scripts would be something like this in Lua:

local health = 10

function onHit()
    health = health - 5
end

The issue I have with this is that every NPC that runs this script, doesn't have their own instance of health. For example, let's say I create NPC A, and subtract 5 from its health. Then, I create NPC B. Because it resets health back to 10, if I tell NPC A to print health, it gives me back 10, even though it should be 5.

If I were to have a different Lua instance for every object, then it would work that way, but I would end up with hundreds of instances at a time in that case, which I understand is not a good thing.

Is there a way to have variables work like this in Lua? If not, is there a scripting language that will work like this in an efficient manner?

For reference, here is the code I am testing:

Lua:

local health = 10;

function onHit()
    health = health - 5
    print_out(health)
end

C++:

class NPC
{
public:
   NPC(lua_State* inState);
   void onHit();

   const char* behavior;
   lua_State* luaState;  
};

NPC::NPC(lua_State* inState)
{
   luaState = inState;
   behavior = "testBehavior.lua";
   luaL_dofile(luaState, behavior);
}

void NPC::onHit()
{    
   luaL_loadfile(luaState, behavior); 
   luabind::call_function<int>(luaState, "onHit");
}

void print_out(int number) {
   cout << "Health : " << number << endl;
}

int main() 
{
   lua_State *myLuaState = luaL_newstate();
   luabind::open(myLuaState);

   luabind::module(myLuaState) [
      luabind::def("print_out", print_out)
   ];

   NPC test(myLuaState);
   NPC test2(myLuaState);
   test.onHit();
   test2.onHit();
   test.onHit();

   lua_close(myLuaState);
}
Это было полезно?

Решение 3

I don't know about Lua, so I wouldn't talk about that. The question also tags as python. For that, I think you can serialize all the local variables that are 'important'.

Alternatively, you can check out my trick, persisting a dictionary called PyDON here: http://code.activestate.com/recipes/577508-pydon-an-alternative-to-xml/

Also check this, I think this will help even more: How do I serialize a Python dictionary into a string, and then back to a dictionary?

Другие советы

You could look into Lua's closures. It would look something like this:

function healthPoints()
    local health = 10
    return function()
        health = health - 5
        return health
    end
end

What happens is that each NPC gets their own function with their own counter. Each time they are hit, you just call the onHit function. You'd use it like so:

npc1.onHit = healthPoints()
npc2.onHit = healthPoints()
npc1.onHit() -- Now at 5, while npc2 is still at 10

You would add parameters like so:

function healthPoints(hp)
    local health = hp
    return function(dmg)
        health = health - dmg
        return health
    end
end

npc1.onHit = healthPoints(100)
npc1.onHit(-12)

I think it's worth a shot.

For reference, here is the code I am testing:

There is a lot wrong here. You keep re-running your Lua-script; that's why it keeps getting reset. The other problem is that your script is creating a global function, so each time you run it, you're getting a new global function, which uses a new local variable.

Stop using globals. Each NPC is a separate object. Therefore, it needs to have object-specific data.

behavior = "testBehavior.lua";
luaL_dofile(luaState, behavior);

This does not create any object-specific data. It simply runs a Lua script and completely discards any return values. Unless that script actually stores something object-specific globally, there won't be any object-specific data created.

What your Lua script needs to do is return a table that contains the object-specific data that the script needs for the object. The script should look like this:

local function onHit(self)
    self.health = self.health - 5
end

return {
  health = 10,
  onHit = onHit,
}

Your C++ code needs to store this table in the NPC class and then use it. This is done easily enough via Luabind calls. Your constructor should look like this:

NPC::NPC(lua_State* L)
{
    behavior = "testBehavior.lua";
    int err = luaL_loadfile(L, behavior);
    //Handle compiler errors. DON'T FORGET THIS!
    luabind::object func = luabind::from_stack(L, -1);
    lua_pop(L, 1);
    luaData = func(); //Catch exceptions for runtime errors
    lua_pop(L, 1);
}

Instead of having lua_State* luaState in your class, you keep around a luabind::object luaData in your class. If you need the lua_State, you can always get one from the luabind::object.

To call your Lua onHit function, simply use the luabind::object interface:

void NPC::onHit()
{    
    luaData["onHit"](luaData);
}

Note that you do not re-run the Lua script. That's what your problem was. You're just calling a function that the Lua script already defined.

Now, you seem to want to use locals instead of table memory. That's fine; it would prevent C++ code from directly accessing health (not without trickery). And it would simplify our code, since we wouldn't have to pass luaData to onHit. You can do that in Lua with this:

local health = 10
local NPC = {}

function NPC.onHit()
  health = health - 5
end

return NPC

The NPC constructor doesn't need to change; just our call to onHit.

void NPC::onHit()
{    
    luaData["onHit"]();
}

If you're dead-set on using globals, you could play games with the environment, but that's rather complicated. It would provide guaranteed isolation between individual script invocations.

It might seem like overkill, but why not store the needed value in an SQLite table?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top