Extending Moai in C++

Moai's technique of enabling C++ class instances to have arbitrary Lua data tacked onto them is great. The fact their framework also allows objects to live until both Lua and C++ agree the object memory is no longer needed is even better.

What's not so good about Moai's system is that its bulky. For every class you want to expose, you have to write a helper class inheriting from MOAILuaObject. If you want to see an example, take a look at MOAIFoo included as part of the moaicore source. It's fairly straightforward ( a couple of magic macros worth exploring ), but it doesn't directly solve the question of what to do when you already have your own hierarchy of classes.

To solve the impedance mismatch between an existing class hierarchy and theirs, at least one of their docs recommends using multiple inheritance to merge the two hierarchies together. From an architectural point of view, I believe the game's code should be able to treat Moai as a separable library that helps "get stuff done". Moai's classes should be seen and used by the rest of the code base only when absolutely needed. Multiple inheritance, unfortunately, tends to lead exactly the opposite direction.

All I really want(tm) is to take an arbitrary class, and expose it in Lua with all the goodness Moai classes have.

Rocking. Let's do it.


The End.

Rather than going in-order, step by step, I'll start where I wound up, and in a later post will try to talk more about what's going on under the hood, both with Moai proper and this this extension.  Once I clean up the code, I'll also put up a zip, or maybe a GitHub tree.

Lets start ( end? ) by defining some test classes. This extension supports everything Moai already does including: virtual methods, virtual base classes, and multiple inheritance, but let's keep the example super simple:
struct Base {
  Base():i(5) {}
  int i;
};
struct Derived: Base {
  Derived():j(7){}
  int j;
};
Given those classes, what we'd like to say in Lua are things like:
-- create arbitrary user types with all the moai goodness
base,derived= LBase.new(),LDerived.new()

-- the methods of Base and Derived
--> "5   7"
print( base:get_i(), derived:get_j() ) 

-- derived can also call base functions
-- "5"
print( derived:get_i()  )

-- and, we can assign arbitrary data to our objects
base.fee, derived.fie= "foe", "fum"
The first part of making this happen is letting Moai know about our Base and Derived types.

Internally, Moai identifies an object type with a system it calls "RTTI". It's perhaps slightly more like objective-c's internal message tables than C++ runtime type identification, but it exists to help verify the right methods are being called on the right objects. It also helps adjust pointer offsets when derived classes call base class methods.

The Moai RTTI system is embedded in the Moai class hierarchy ( the RTTIBase class is the base class of all its moai objects ), but it's essential to Lua binding, so we need to gain to it. Normally, we'd do so by inheriting from MOAILuaObject, and in our constructor use macros to define the type. But, defining a MOAILuaObject is precisely what we're trying to avoid.

TProxyType<> is new code to help make this possible. To declare our needed RTTI, all we need to do is declare static instances of the ProxyType template:
// define RTTI for our arbitrary classes, "LBase" becomes the Lua class name 
// this is similar to RTTI_SINGLE()
static TProxyType<Base> sBaseType("LBase");

// derived types can take as many ( okay up to three ) base classes as needed.
// this is similar to RTTI_BEGIN(),RTTI_EXTEND(),RTTI_END()
static TProxyType<Derived,Base> sDerivedType("LDerived", &sBaseType );
Given those proxies, we can now define the Lua functions used with those types. I'm using the registration helper I posted yesterday, but it's not necessary. You can handle the declaration however you normally would. The key here is just the ability to gain safe access to the C++ instance(s).

If you derive from MoaiLuaObject, then you can use the macro MOAI_LUA_SETUP to that end. Here, we use our previously declared ProxyType(s):
// define a group of related lua functions; doesn't have to be called "LBase" 
// that's just a handy name to show these are meant for the LBase type.
LUA_FUNCTIONS( LBase ); 

int LUA_FUNCTION( LBase, get_i )( lua_State*L ) {
  int ret=0
  // if the caller passed this function a 'self' other than 'Base'
  // (or 'Derived'), Check() logs that fact, and returns NULL.
  Base* self= sBaseType.CheckCall( L, "U" ); 
  if (self) {
    lua_pushinteger(L, self->i );
    ret= 1;
  }
  return ret;
}

// define a second group of related lua functions;
// this time meant for use with the derived type.
LUA_FUNCTIONS( LDerived );

int LUA_FUNCTION( LDerived, get_j )( lua_State*L ) {
  int ret=0;
  // the "U" means user data is expected in the first parameter (b/c of self)
  // I don't know the complete list of opcodes, but N is number, S is string,
  // and so on.
  Derived* self= sDerivedType.CheckCall( L, "U" ); 
  if (self) {
    lua_pushinteger(L, self->j );
    ret= 1;
  }
  return ret;
}
Of course, we also have to get ready to register the functions we've just declared. In normal Moai, you'd override a method in your custom MoaiLuaObject class. The way we do that for arbitrary types is (slightly) more typical Lua: we define helper functions to register the Lua functions.
int RegisterBase( lua_State* L ) {
  Register_LBase(L);
  return 0;
}

// Just like in Moai's normal usage pattern, derived classes must manually 
// register both their own functions, and their base class functions.
int RegisterDerived( lua_State*L ) {
  Register_LBase(L);
  Register_LDerived(L);
  return 0; 
}
Moai has two standard methods for exposing objects and their functions into Lua: Factories, and Singletons. Factories allow the Lua code to create our C++ objects. Singletons allow the Lua code access to a single shared instance of our C++ class.

Examples of these two different methods in practice are MOAILayer and MOAISim. MOAILayer is a factory type. That means, in Lua, you can create new layers by writing: layer= MOAILayer.new(). MOAISim, on the other hand, is a singleton. There's only one shared Sim, and you never call new() on it. Instead, you call Sim methods directly. For instance, to add a newly created layer to the shared simulation object, you write: MOAISim.pushRenderPass(layer).

In normal Moai, to create a factory you add the macro DECL_LUA_FACTORY() to your custom MOAILuaObject class. For a singleton, you instead use DECL_LUA_SINGLETON().  I haven't needed singletons yet, but here is the declaration for two factories of arbitrary type:
void RegisterTest(lua_State*L)
{
  // Define a factory that can create objects of our desired type.
  // Notice we're passing in both our previously declared rtti proxy,
  // and our previously declared registration function. 
  // The factory welds these two things together to form an instance.
  static TProxyFactory baseFactory( sBaseType, RegisterBase ); 

  // We also need to let Lua know about the factory we just declared.
  baseFactory.Register(L);

  // Ditto for the derived type.
  static TProxyFactory derivedFactory( sDerivedType, RegisterDerived ); 
  derivedFactory.Register(L);
}
Believe it or not, that's it. We're done. Moai now knows about our arbitrary types, and their relationship to each other. Lua code can now create new instances of each type, and they will automatically bind to the functions we've declared. Further, Moai code -- accessed via the ProxyType -- will help us safely verify the right objects are getting sent to the right functions. The system also treats memory like we want; just as if we'd derived from MOAILuaObject.

For a subsequent post, I'll try to get the code up, and then dive into how it all works. It does (unfortunately) require adding the proxy class(es) as friend(s) to the RTTI types. The key code needed on the MOAI side is defined as private, and even other MOAI classes which need access are written as friends, so that same pattern was needed to extend the code.

0 comments: