Lua’d Bayts

Filed Under Everything |

Before I start, I want to share some pretty important news. On January 8, 2008 at 7:45 PM Central, my son Sean was born. He was about 3 weeks early, but is nonetheless a healthy and (apparently) happy baby. You can read more about him at my wife’s blog, yellowpop.

Lua is a very popular scripting system for games. There are a lot of very good reasons to use Lua in your game. It’s fast, lightweight, and free. Embedding the Lua runtime in your application is supposed to be very easy. Today, we’ll take a look at doing just that. We’re going to embed Lua into Bayts, making it possible to script new steering behaviors in Lua. If you want to compile the code for this example, follow the appropriate “Getting Started” link in the heading of this page. When it’s time to check code out of SVN, you’ll want to use the URL https://programmicon.googlecode.com/svn/tags/LuaScripting-0.02. If you’ve already checked out earlier code, you can save yourself some time by switching to the new tag instead. Inside your workspace:

  1. svn switch https://programmicon.googlecode.com/svn/tags/LuaScripting-0.02

This will bring your local workspace up to date. Unfortunately, because I did some restructuring of the ThirdParty directory between IntroducingBayts-0.01 and LuaScripting-0.02, you’ll still end up re-grabbing all of the third party libraries. I promise I won’t do it again, so in the future you won’t have to!

Lua is distributed as a library written in C. It can be built for many platforms, and is pretty easy to configure for your specific needs (for example, you can replace the underlying C type used to represent numbers to an integral type, for use on an embedded platform that doesn’t have hardware floating point number support).

Integrating Lua into your code is pretty straightforward. There is a relatively simple interface for calling Lua functions from C, and calling C functions from Lua. Arguments and return values in both cases are pushed onto a stack that Lua maintains. I won’t go into the specifics of how this is done, because there already exists excellent documentation for doing so. Instead, I’ll focus on the specifics of integrating Lua into Bayts.

Design

The ultimate goal of this exercise is to make it possible to create new steering behaviors for Bayts using the Lua scripting language. It should be as easy as possible for the script writer to create new steering scripts. Fortunately, the task is fairly simple, so creating a clean interface in this case is easy. We will require the Lua script to have just one function, which will take as arguments the bayt in question, as well as the list of friendly bayts and (for future expansion) the list of enemy bayts. The function definition in Lua might look like this:

  1. function SteeringBehavior(bayt, friends, enemies)

Bayts will be represented by a Lua table, with fields for each of the publicly accessible class member variables of the C++ representation of the bayt. The bayts will be mutable, but we will be passing them by value, so changes made to a bayt in Lua will be discarded.

Vectors will also be represented by Lua tables, and will have a functional interface for performing operations on them. For instance, to add two vectors, the Lua scripter would call a function called vectorAdd:

  1. newVector = vectorAdd(vectorA, vectorB)

The function must return a vector, which will be the behavior’s contribution to the updated velocity vector.

Implementation, Vectors

Since pushing bayts on to the Lua stack will require us to push vectors onto the Lua stack, we’ll cover vectors first. As stated in the design, vectors will be represented as a Lua table, which will be indexed by number, like a C array. We could index it by the coordinate names, or even use both. Lua tables are versatile enough that a single table can be indexed by both numbers and strings. However, for simplicity’s sake, we’ll stick with just the numbers. Contrary to what is apparently Lua tradition, the table will be 0-based, like a C array. So, the x-coordinate will be at index 0, y at 1, and z at 2, which is the same representation as the C++ vector class we’re using.

With this in mind, the code to push and pop a vector onto and off the Lua stack is simple enough for me to present it here in whole:

  1. void lua_pushvector(lua_State* L, const BaytVector& v)
  2. {
  3.     lua_createtable(L, 3, 0);
  4.  
  5.     int tableIdx = lua_gettop(L);
  6.  
  7.     lua_pushnumber(L, 0);
  8.     lua_pushnumber(L, v[0]);
  9.     lua_settable(L, tableIdx);
  10.  
  11.     lua_pushnumber(L, 1);
  12.     lua_pushnumber(L, v[1]);
  13.     lua_settable(L, tableIdx);
  14.  
  15.     lua_pushnumber(L, 2);
  16.     lua_pushnumber(L, v[2]);
  17.     lua_settable(L, tableIdx);
  18. }
  19.  
  20. BaytVector lua_tovector(lua_State* L, int index)
  21. {
  22.     BaytVector retval(3);
  23.  
  24.     lua_pushnumber(L, 0);
  25.     lua_gettable(L, index);
  26.     retval[0] = lua_tonumber(L, lua_gettop(L));
  27.     lua_pop(L, 1);
  28.  
  29.     lua_pushnumber(L, 1);
  30.     lua_gettable(L, index);
  31.     retval[1] = lua_tonumber(L, lua_gettop(L));
  32.     lua_pop(L, 1);
  33.  
  34.     lua_pushnumber(L, 2);
  35.     lua_gettable(L, index);
  36.     retval[2] = lua_tonumber(L, lua_gettop(L));
  37.     lua_pop(L, 1);
  38.    
  39.     return retval;
  40. }

If you’ve looked at the Lua API documentation, you’ll see that we’re following the API’s naming convention for functions to push and pop values onto and off of the Lua stack. This is purely for consistency, and is not a requirement of the Lua API.

Our Lua vector library provides a fairly complete set of mathematical operations, which are implemented C++ functions, which we make available to Lua. Here is the function to add two vectors, for example:

  1. extern "C" int lua_vectorAdd(lua_State *L)
  2. {
  3.     int n = lua_gettop(L);
  4.     BaytVector v(3, 0.0);
  5.     for (int i = 1; i <= n; ++i)
  6.     {
  7.         v+= lua_tovector(L, i);
  8.     }
  9.     lua_pushvector(L, v);
  10.     return 1;
  11. }

This function, and others like it, actually take a variable number of arguments, so you can add up several vectors all in one function call. You can see we use the lua_tovector function listed above to convert from the Lua vector arguments to the C++ vectors we can actually operate on. The final result is then pushed back onto the stack, using the lua_pushvector function, and the function returns 1, telling Lua that we’ve placed a single return value on the stack.

The other functions in the vector library take this same basic form.

Implementation, Bayts

Pushing bayts onto the Lua stack is pretty much the same as doing the vectors, only with a whole lot more information. We put every value that is available to C++ steering behaviors in the Lua table, indexed by sensible names such as “velocity” and “desired_speed”.

There are really no operations other than data retrieval that the Lua script can perform on a bayt, so the only functionality provided is to push and pop bayts onto and off of the Lua stack.

Implementation, Nearby bayt lists

In order to create a worthwhile steering behavior, the script needs to be able to access information about other nearby bayts. We’ll provide a list each for nearby friendly bayts and nearby enemy bayts (for future use).

As with the vectors and bayts, the bayt lists will be implemented as a table. We’ll take advantage of the fact that Lua tables can be indexed by both strings and numbers for this purpose. All of the bayts in the list will be indexed by number, starting with 0, like a C array. Other information, such as the number of nearby bayts, their indexes in the bayt list, and their distances from the bayt in question have a string index, like records.

Implementation, Lua Script Manager

To tie everything together, we’ll create a Lua Script Manager. This script manager will take on the task of loading Lua scripts and making them available to the steering engine. For the purposes of this exercise, the script manager will be exceedingly simple. It will load all of the Lua scripts it finds in a specified directory. It will also provide a means by which a Lua steering behavior can be added to a bayt flock. For now, the names of Lua steering behavior functions will have to be hard coded in the C++ code. This will be addressed at some time in the future.

Implementation, The Code

All of the Lua specific functionality is isolated in its own directory in the source tree, called Script/Lua. Look there for further details about the C++ implementation of the integration of Lua into Bayts.

Lua Steering Behavior

For the purposes of this example, I rewrote the MatchHeadingBehavior behavior in Lua. You’ll see in main.cpp that I’ve commented out the C++ version. You’ll find the Lua version in the LuaScripts directory, in a file called test.lua. Here’s how it looks:

  1. function SteeringTest(bayt, friends, enemies)
  2.    local change = { [0] = 0, [1] = 0, [2] = 0 }
  3.    local i
  4.  
  5.    for i = 1, friends.NearbyBaytCount - 1, 1 do
  6.       local index = friends.indexes[i]
  7.  
  8.       if friends[index].id ~= bayt.id then
  9.         change = vectorAdd(change, friends[index].velocity)
  10.       end
  11.    end
  12.  
  13.    change = setVectorMagnitude(change, bayt.minUrgency)
  14.  
  15.    return change
  16. end

We start by creating a Lua table which mimics the form we use for Bayt vectors, initialized to 0. I’d say this is actually a design flaw - the scripter shouldn’t have to know how the data types are represented.

Next, we loop through the nearby bayts, and add their velocity to our contribution. Just like in the C++ version, we finish up by making sure the result vector has a magnitude equal to the bayt’s minimum urgency, and returning the contribution.

Some Notes

The very first thing you will probably notice when you run this version of Bayts is that it is slow. We’re talking go-get-a-cup-of-coffee-between-frames slow. There are several very good reasons for this performance issue. In my next post, we’ll tackle the performance and see if we can’t get it back up to a more reasonable level.


Comments

Name

Speak your mind

*
To prove that you're not a bot, enter this code
Anti-Spam Image

Check Spelling
Activate Spell Check while Typing