tags:

views:

838

answers:

3

I'm lead dev for Bitfighter, and we're working with a mix of Lua and C++, using Lunar (a variant of Luna, available here) to bind them together.

I know this environment does not have good support for object orientation and inheritance, but I'd like to find some way to at least partially work around these limitations.

Here's what I have:

C++ Class Structure

    GameItem
       |---- Rock
       |---- Stone
       |---- RockyStone

    Robot

Robot implements a method called getFiringSolution(GameItem item) that looks at the position and speed of item, and returns the angle at which the robot would need to fire to hit item.

-- This is in Lua
angle = robot:getFiringSolution(rock)
if(angle != nil) then
    robot:fire(angle)
end

So my problem is that I want to pass rocks, stones, or rockyStones to the getFiringSolution method, and I'm not sure how to do it.

This works for Rocks only:

// C++ code
S32 Robot::getFiringSolution(lua_State *L) 
{
   Rock *target = Lunar<Rock>::check(L, 1);
   return returnFloat(L, getFireAngle(target));    // returnFloat() is my func
}

Ideally, what I want to do is something like this:

// This is C++, doesn't work
S32 Robot::getFiringSolution(lua_State *L) 
{
   GameItem *target = Lunar<GameItem>::check(L, 1);
   return returnFloat(L, getFireAngle(target));    
}

This potential solution does not work because Lunar's check function wants the object on the stack to have a className that matches that defined for GameItem. (For each object type you register with Lunar, you provide a name in the form of a string which Lunar uses to ensure that objects are of the correct type.)

I would settle for something like this, where I have to check every possible subclass:

// Also C++, also doesn't work
S32 Robot::getFiringSolution(lua_State *L) 
{
   GameItem *target = Lunar<Rock>::check(L, 1);
   if(!target)
       target = Lunar<Stone>::check(L, 1);
   if(!target)
       target = Lunar<RockyStone>::check(L, 1);

   return returnFloat(L, getFireAngle(target));    
}

The problem with this solution is that the check function generates an error if the item on the stack is not of the correct type, and, I believe, removes the object of interest from the stack so I only have one attempt to grab it.

I'm thinking I need to get a pointer to the Rock/Stone/RockyStone object from the stack, figure out what type it is, then cast it to the correct thing before working with it.

The key bit of Lunar which does the type checking is this:

// from Lunar.h
// get userdata from Lua stack and return pointer to T object
static T *check(lua_State *L, int narg) {
  userdataType *ud =
    static_cast<userdataType*>(luaL_checkudata(L, narg, T::className));
  if(!ud) luaL_typerror(L, narg, T::className);
  return ud->pT;  // pointer to T object
}

If I call it thusly:

GameItem *target = Lunar<Rock>::check(L, 1);

then the luaL_checkudata() checks to see if the item on the stack is a Rock. If so, everything is peachy, and it returns a pointer to my Rock object, which gets passed back to the getFiringSolution() method. If there is a non-Rock item on the stack, the cast returns null, and luaL_typerror() gets called, which sends the app off into lala land (where the error handling prints a diagnostic and terminates the robot with extreme prejudice).

Any ideas on how to move forward with this?

Many thanks!!

Best solution I've come up with... ugly, but works

Based on the suggestions below, I came up with this:

template <class T>
T *checkItem(lua_State *L)
{
   luaL_getmetatable(L, T::className);
   if(lua_rawequal(L, -1, -2))         // Lua object on stack is of class <T>
   {
      lua_pop(L, 2);                   // Remove both metatables
      return Lunar<T>::check(L, 1);    // Return our object
   }
   else                                // Object on stack is something else
   {
      lua_pop(L, 1);    // Remove <T>'s metatable, leave the other in place 
                        // for further comparison
      return NULL;
   }
}

Then, later...

S32 Robot::getFiringSolution(lua_State *L)
{
   GameItem *target;

   lua_getmetatable(L, 1);    // Get metatable for first item on the stack

   target = checkItem<Rock>(L);

   if(!target)
      target = checkItem<Stone>(L);

   if(!target)
      target = checkItem<RockyStone>(L);

   if(!target)    // Ultimately failed to figure out what this object is.
   {
      lua_pop(L, 1);                      // Clean up
      luaL_typerror(L, 1, "GameItem");    // Raise an error
      return returnNil(L);                // Return nil, but I don't think this 
                                          // statement will ever get run
   }

   return returnFloat(L, getFireAngle(target));    
}

There are probably further optimizations I can do with this... I'd really like to figure out how to collapse this into a loop because, in reality, I will have a lot more than three classes to deal with, and this process is a bit cumbersome.

Slight improvement on the above solution

C++:

GameItem *LuaObject::getItem(lua_State *L, S32 index, U32 type)
{
  switch(type)
  {
    case RockType:
        return Lunar<Rock>::check(L, index);
    case StoneType:
        return Lunar<Stone>::check(L, index);
    case RockyStoneType:
        return Lunar<RockyStone>::check(L, index);
    default:
        displayError();
   }
}

Then, later...

S32 Robot::getFiringSolution(lua_State *L)
{
   S32 type = getInteger(L, 1);                 // My fn to pop int from stack
   GameItem *target = getItem(L, 2, type);

   return returnFloat(L, getFireAngle(target)); // My fn to push float to stack  
}

Lua helper function, included as a separate file to avoid user needing to add this manually to their code:

function getFiringSolution( item )
  type = item:getClassID()      -- Returns an integer id unique to each class
  if( type == nil ) then
     return nil
   end
  return bot:getFiringSolution( type, item )
end

User calls this way from Lua:

   angle = getFiringSolution( item )
A: 

You should tell us what exactly does not work in your code. I suppose that it is Lunar<Rock>::check(L, 1) that fails for all non-Rocks. Am I correct?

Also it would be fine if you specified which version of Lunar you use (a link to it would be great).

If it is this one, then class type is stored in the Lua object metatable (one may say that this metatable is the type).

Looks like the simplest way to check if object is a Rock without patching Lunar is to call luaL_getmetatable(L, Rock::className) to get class metatable and to compare it with lua_getmetatable(L, 1) of your first argument (note lua**L** in the first function name). This is a bit hackish, but should work.

If you fine with patching Lunar, one of possible ways is to add some __lunarClassName field to the metatable and store T::name there. Provide lunar_typename() C++ function (outside of the Lunar template class -- as we do not need T there) then, and return from it the value of that __lunarClassName field of argument's metatable. (Do not forget to check if object has metatable and that metatable has such field.) You may check Lua object type by calling lunar_typename() then.

A bit of advice from personal experience: the more of business logic you push to Lua, the better. Unless you're pressed by severe performance constraints, you probably should consider to move all that hierarchy to Lua -- your life would become much simpler.

If I may help you further, please say so.

Update: The solution you've updated your post with, looks correct.

To do the metatable-based dispatch in C, you may use, for example, a map of integral lua_topointer() value of the luaL_getmetatable() for a type to a function object/pointer which knows how to deal with that type.

But, again, I suggest to move this part to Lua instead. For example: Export type-specific functions getFiringSolutionForRock(), getFiringSolutionForStone() and getFiringSolutionForRockyStone() from C++ to Lua. In Lua, store table of methods by metatable:

dispatch =
{
  [Rock] = Robot.getFiringSolutionForRock;
  [Stone] = Robot.getFiringSolutionForStone;
  [RockyStone] = Robot.getFiringSolutionForRockyStone;
}

If I'm right, the next line should call the correct specialized method of robot object.

dispatch[getmetatable(rock)](robot, rock)
Alexander Gladysh
So :-) I added more info to the original post following your suggestions. I tried to implement your hackish solution, but was unable to do so because I don't fully understand how to do what you suggest. I am reluctant to modify Lunar because that would make upgrading more difficult should an enhanced version be released. However, I would consider doing so if I could arrive at a much cleaner/faster/or otherwise better solution.
Watusimoto
Oh, and to your point about pushing more logic into Lua. You are probably right, but my intent is to have novice programmers coding (or attempting to code) the robots, so I want to provide as much pre-packaged functionality as I can, and keep robot code as simple and clean as possible. It's already bad enough, but if I were to include blocks of boilerplate code in each robot, it would probably turn potential users off while increasing the chance of error. If you want to see what I'm after, you can see some docs <a href="http://bitfighter.org/wiki/index.php?title=Programming_robots">here</a>.
Watusimoto
This seems to be the most promising approach suggested here. I did a lot more research, and came up with the solution I've added to the end of my original question. Hackish indeed!
Watusimoto
Sorry I wasn't able to write earlier. I've updated my answer.
Alexander Gladysh
On your concern with boilerplate code: you do not need to force user to include any *code* (as text). It should be enough to simply load that code from game resources before user-supplied code is loaded.
Alexander Gladysh
Yes, this looks very good. Loading the code as suggested solves the boilerplate issue, and moving the getFiringSolution method(s) into Lua should work as well, especially as they don't need to be specialized by class (to compute the angle only requires knowing the target's pos. and vel., which are avialable for all objects using commonly-named methods). The only (small) issue remaining would be syntatical: for most methods, users will be using object:method() syntax, but for getFiringSolution, it would be object.method(). This will cause problems. Is there any way to "unify" the syntax?
Watusimoto
Can you show why you have dot calling notation?From the top of my head, two ways to fix it: (1) foo:bar(arg) is a shortcut to foo.bar(foo, arg). So eat first argument. Or (2) make low-level getFiringSolution() a "private" function, and call it from some Lua wrapper function which is made so it should be called with colon.
Alexander Gladysh
OK, now that I have this all working (sort of, see last section of original question), the dot notation issue went away. I chose not to fully implement getFiringSolution() in Lua because, contrary to what I wrote before, it relies on polymorphism and is used in other places in the game, and I prefer not to support two implementations. However, I was able to simplify determining item's class in C++ by including a Lua helper function and a bit of reflection. This simplifies the logistics significantly. Better, but still not ideal.
Watusimoto
I'm glad I was able to help. An upvote to this answer would be cool. ;-)
Alexander Gladysh
+5  A: 

I think you're trying to do the method dispatch in the wrong place. (This problem is symptomatic of a difficulty with all of these "automated" ways of making Lua interact with C or C++: with each of them, there's some magic going on behind the scenes, and it's not always obvious how to make it work. I don't understand why more people don't just use Lua's C API.)

I had a look at the Lunar web pages, and it looks to me as if you need to create a methods table on type T and then call the Luna<T>::Register method. There's a simple example on the web. If I'm reading the code correctly, none of the glue code in your question is actually the recommended way of doing things with Lunar. (I'm also assuming that you can implement these methods entirely as C++ calls.)

This is all pretty dodgy because the documentation on Lunar is thin. A sensible alternative would be to do all the work yourself, and just associate each C++ type with a Lua table containing its methods. Then you have the Lua __index metamethod consult that table, and Bob's your uncle. Lunar is doing something close to these, but it's sufficiently dressed up with C++ templates that other goo that I'm not sure how to make it work.

The template stuff is very clever. You might want either to take the time to understand deeply how it works, or to reconsider if and how you want to use it.

Summary: for each class, make an explicit methods table, and register each class using the Lunar Register method. Or roll your own.

Norman Ramsey
+1 for doing actual research, and a more relevant answer. :)
Arafangion
I've got the methods table and I'm registering everything properly, and, in most situations, it works great when I know what types to expect, which I do most of the time. The only problem is trying to figure out how to determine which type of object the Lua script pushed onto the stack when it could be one of several, which is what this question is essentially about.The reason I chose to use Lunar to help with the binding (rather than create my own) was that I find the process rather spooky and scary -- lots of oddly named methods and other weirdness. Lunar makes it slightly more friendly.
Watusimoto
Why do you need to know the type? Can you just do a regular virtual-method dispatch instead? If I really needed to know the type, I would do what Smalltalk does and define my own reflection interface (i.e., a method on each class that allows me to ask what is its type). You could probably automate this with minor tweaks to Lunar.
Norman Ramsey
A: 

I suggest that you define an object oriented system in pure lua, and then write a custom binding to C++ for that aspect of the API.

Lua is well suited for prototype OO implementations, where tables are used for emulating classes, in which one entry has a function called new, which when called returns an appropriate table of the same 'type'.

From C++, however, make a LuaClass that has a .invoke method, accepting a C string (ie, a null-terminated const char array) to specify the name of the member function you want to call, and depending on how you want to handle variable arguments, have several templated versions of this .invoke method for zero, one, two, ... N arguments as neccessary, or define a method of passing a variable number of arguments into it, and there are many ways to do that.

For Lua, I suggest making two .invoke methods, one which expects an std::vector, and another that expects an std::map, but I'll leave that up to you. :)

In my last Lua/C++ project, I used only null-terminated arrays of C-strings, requiring lua to convert the string to an appropriate value.

Enjoy.

Arafangion
I'm attempting to graft Lua onto an existing, mature game, so I don't have the luxury of doing what you suggest. If I were starting from scratch, I might do just that, but in this case, I'm committed to the existing architecture.
Watusimoto
I suggest passing some kind of 'identifier' into the lua objects, then, so that C++ can determine which is the real object. This can be done but you will need to use the documentation. (It is less trivial). If the types are POD types, you could concievably just put the entire C++ object into the lua object as well, but I'd be a bit wary of that approach. In any case, I advise you to use answers contributed by the other two people above, as they appear to be more relevant to your specific case.
Arafangion