Skip to main content
GameDev.net gamedev.net
🔒 Locked

Improving my Lua setup

Started by Derakon Apr 8, 2006 at 8:57 PM 3 replies 1.5k views
Original Post
Derakon
Derakon
I have a working Lua setup that allows me to define sprite behaviour in scripts. However, it's really quite clunky and I'm reaching the point where I need to improve it. The problem is that I don't really know how to go about doing this. My current setup (link contains a sample Lua script and the relevant C/C++ functions) has a lua_State* for each sprite instance, which, on creation of the sprite, loads the relevant script file and registers a set of C functions. When I want to call a Lua function (e.g. to update the sprite state), I put the C++ state into a table, push the table onto the stack, call the function which modifies the state, pop the table, and update the C++ state to match what is in the popped table. This seems needlessly complex. Calling C functions from Lua is easy enough; no complaints there. But calling Lua functions in one script file from Lua functions in another script file (e.g. if a dying enemy wants to tell the scorekeeping sprite to update itself) is absolutely horrible. I can only pass one argument at a time (well, if I wanted to pass two arguments, then I'd have to make another version of the C functions that handle the interface between two sprites' scripts), and the mere fact that getting from Lua to Lua requires a detour through C just grates on my nerves. I'm fairly certain that there's a better way to do this. I just don't know where to get started. Any advice would be appreciated, even if it's only pointing me to a specific bit of the Lua documentation that would be pertinent.
Jetblade: an open-source 2D platforming game in the style of Metroid and Castlevania, with procedurally-generated levels
Kylotan
Kylotan
Usually, if you have more than one lua_State, you're doing something wrong. Each lua_State is designed to be totally independent, so it's not surprising that communicating between them is awkward. It's like running 2 separate C++ programs and finding that calling functions in one from the other is difficult. The answer is to combine it into one. Remember that Lua code is essentially data, and you can load in multiple scripts containing objects and functions and store them in a data structure to be used as you require.
Derakon
Derakon
Okay, I can see how having a single lua_State would be advantageous. But there's a few implementation issues I have.

1) Say I have the state for a given sprite, and I want to call that sprite's update function. I access the global lua_State, call lua_getglobal to access...wait, which update function? They all have the same name. The only way I can tell them apart is by which file they're in, and I don't want to enforce a naming scheme to match file names to function names.

2) Most of the scripts have local variables that aren't currently stored in the "state" table. Things like timers, aliveness, IDs of child sprites, and so on are not relevant to the engine and thus don't get stored in the state. How would I ensure that they stick around?

I hate to ask for code, but I really do learn best by example, and the Lua documentation leaves significant holes in its examples. I'm getting the distinct impression that what I want to do can be accomplished with userdata and metatables, but I don't really understand them well enough to figure out how.

Edit: What I'm really looking for, I suppose, is a way to specify a class in a Lua script that is guaranteed to have certain fields and methods but can contain more; only the guaranteed ones need to be accessible from C.

[Edited by - Derakon on April 9, 2006 3:11:37 PM]
Jetblade: an open-source 2D platforming game in the style of Metroid and Castlevania, with procedurally-generated levels
rileyriley
rileyriley
I do this by inheritance. In Lua, I have a simple table called Object. If I want to make, say, a ball, I make a new table called Ball, copy all metadata from Object into it (see the inheritance examples in the lua manual), and voila! I have a table called Ball that has all the properties of Object and can be accessed from C++ in the same way. It has been working wonderfully so far - sometimes it how seamless it is makes me giddy :)

At the same time, though, I don't use Lua as an authority on many of the attributes of my Lua objects. The lua objects can store whatever extra attributes they want, and can do whatever they want with them, but I can't trust lua to keep track of my really important things like position, velocity, color, geometry, etc. If Lua wants to know those things it has to call accessor functions, and those branch into C++ to get the real data. I think that might be the only way you can guarantee that certain properties are present.

Here is some example code from my engine:

playerBall = DefaultPlayerBall:new() --DefaultPlayerBall 'inherits' from Object, and takes care of things like loading the model of a ball, instantiating the proper shape for the physics engine, etc
playerBall:SetPosition(10, 10, 0)
playerBall:SetColor(1, 0, 0)

if I want it to do something crazy, I can add new attributes:
playerBall.name = "Riley"

I can access these new attributes from Lua, and during each simulation step I give lua an opportunity to run whatever threads it likes, so it can use these attributes to move objects around, rotate them, send network messages, etc. C++ is only doing drawing and physics, really, so all C++ cares about are positions and shapes. This sounds similar to what you'd like to get with your sprite classes.


This all works from within a single lua state. I use Lunar (sorry, no link) to link userdata and functions semi-automatically so that the accessor functions are called on the proper C++ instances.

I also had to make a proxy class that could be deleted at any time by lua, but you may not need to worry about that.

Hope this helps~
--Riley
Derakon
Derakon
Well, I managed to get some face time with one of the more knowledgeable Lua people in my circle of acquantances, which was a big help. I now have a mostly-working cleaned system, with only one lua_State, and no creating/destroying tables every time I need to call a Lua function. More importantly, I better understand what's going on between C and Lua and in Lua itself. A few of the more important bits of wisdom that were imparted to me:

Lua files read by lua_loadfile are functions. They may declare other functions, but they are functions themselves. They return tables that contain everything that was declared in them, and those contents are all merged into _G, the table of global values.

The state and functions associated with a given game object ("sprite" in my engine's parlance, which is admittedly inaccurate) can be cleanly stored as a table. What I want to do is create a function that causes a given table to gain access to another set of functions, as well as object-specific state variables. This will, in effect, create an object-specific instance of a game object "class".

_G is a table just like everything else. You can cause indices in _G to point to anything you fancy. In particular, if your game objects have unique identifiers in the C++ engine, then you can cause _G[uniqueId] to point to the table holding the Lua state of that game object. (Note: I still haven't determined what happens here when I try to kill the sprite; I suspect I'll need to manually clear the _G index before the table vanishes, so this is a lurking memory leak).

Here's a sample Lua script under the new implementation:
Balloon = {}function Balloon.new()    print ("And another balloon is created...\n")    local R = {}    setmetatable(R, {__index = Balloon})    return Rendfunction Balloon.init(self)    self.y = 300    self.x = 300    return selfendfunction Balloon.update(self)    self.y = self.y - 1    return selfendfunction Balloon.react(self, collision)    if (collision.code == 2) then --Oops! Hit the ground      self.dead = true    end    return selfendfunction Balloon.respond(self, key)    if (key == "thrust") then        print "I'm a balloon. I have no engines!\n"    else        print "I'll get around to that as soon as you write the code to implement it.\n"    end    return selfend

The C++ side loads all of the Lua script files I've written at program start. When I need a new sprite, I load the Balloon.new function and execute it. I then tie the returned table into _G[uniqueId], set some default values in the table (e.g. X and Y fields are loaded from my level description file), and call the init function (and yes, I'll grant that init and new look awfully similar. Technical restrictions force me to do it this way for the moment). Later when I need to call another Lua function, I look up _G[uniqueId] to find the object's state table, put it on top of the stack, and load the function I need. Then I put the state table on top of the stack again (since the stack needs to read "function, argument1, argument2..." and argument1 should always be "self"), and put any arguments on, then do lua_pcall. Very nice and neat.

This also gave me an excuse to do some badly-needed refactoring of my Sprite class, splitting out almost all of the Lua-specific code into a global singleton ScriptMaster class.
Jetblade: an open-source 2D platforming game in the style of Metroid and Castlevania, with procedurally-generated levels

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.