2

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);
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
user947871
  • 339
  • 6
  • 17
  • Persistent local variables are the bane of all scripting languages. Try to find a different way. – Ignacio Vazquez-Abrams Dec 30 '12 at 02:09
  • @IgnacioVazquez-Abrams Do you have any suggestions? I doubt that not a single scripting language has solved this problem. – user947871 Dec 30 '12 at 02:36
  • It's far more likely that your code is just buggy. You don't show us the code that manages NPCs or anything. We need to see how you "create" an NPC, how you adjust it's health, and how you read it back. – Nicol Bolas Dec 30 '12 at 02:38
  • @NicolBolas I updated my post with the code that I am attempting to run. It is just s small minimal test, but I wanted to ry this out before I committed too much. – user947871 Dec 30 '12 at 03:29
  • 3
    @user947871: Just have a separate `lua_State` for every NPC and stop worrying. `lua_State`s are very cheap to create. If you run in to performance problems, you might be able to speed thing up by using `lua_newthread` instead of creating entirely separate `lua_State`s, but only do that if you have to. – Mankarse Dec 30 '12 at 03:36
  • Well, I would but I need behaviors for around 1000+ things at a time. that doesn't seem feasible. – user947871 Dec 30 '12 at 03:42
  • why not serialize them? @user947871 – Aniket Inge Dec 30 '12 at 03:54
  • @Aniket I may be a bit confused as to what you mean. Are you saying I could serialize my lua states? so I could have lua states for each item, and then when they aren't in use I serialize them? Could you please elaborate? – user947871 Dec 30 '12 at 03:58
  • @user947871 I don't know about lua(and its states) but I know about Python and in Python you can serialize your local variables and persist them. – Aniket Inge Dec 30 '12 at 03:59
  • 1
    @user947871: "*Well, I would but I need behaviors for around 1000+ things at a time. that doesn't seem feasible.*" Why not? Personally I prefer the closure approach, but allocating thousands of lua_State objects is nothing compared to the amount of memory you need for rendering. – Nicol Bolas Dec 30 '12 at 14:32
  • I'm going to try having the multiple Lua States. The game isn't very demanding graphically, so even with that many objects, it shouldn't be too bad. I'll update with my findings. – user947871 Dec 30 '12 at 21:29
  • 2
    @user947871: You shouldn't really do it anyway. Not because it would be a performance problem, but because it would be a coding problem. Sooner or later, you will need to either have NPCs communicate with each other, or your NPCs will need to use functions that are common to multiple NPCs. Both cases are made harder by giving them individual `lua_State`s. – Nicol Bolas Dec 30 '12 at 21:33

4 Answers4

8

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.

Q2Ftb3k
  • 678
  • 3
  • 9
  • 18
Netfangled
  • 2,071
  • 1
  • 18
  • 28
5

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.

hugomg
  • 68,213
  • 24
  • 160
  • 246
Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Awesome, I am going to try this in the next hour or so. I'll likely have questions. – user947871 Dec 30 '12 at 21:35
  • This is definitley the best answer. It would have taken me a long while to figure out that luabind could do this, but this answer explained that it could and was pretty clear on how to set up the lua side in a nice way. There is nothing else on the internet that describes this I swear. I did, however, hit an issue while implementing it: http://stackoverflow.com/questions/20450764/crash-when-calling-luabind-function-on-object – MintyAnt Dec 08 '13 at 19:08
2

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?

Community
  • 1
  • 1
Aniket Inge
  • 25,375
  • 5
  • 50
  • 78
  • This actually seems like a great solution. So basically, when I'm done running a script, I serialize the dictionary, which holds all of my variables. Then when I want to run the script again for that same instance, I can just de-serialize the dictionary and use that? – user947871 Dec 30 '12 at 04:07
  • exactly @user947871 that's the way to do it. Deserialize it when your app restarts. – Aniket Inge Dec 30 '12 at 04:10
2

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