C++ class derivation in Lua

(1/6) > >>

diwil:
Dear TIGers, I am here to demonstrate you the means to create a base class in C++ and using the luabind library, implement said class in your Lua, and enable you to create subclasses of such class. This will enable you to create a basic entity in your game engine code, and specify different logic for each of the subclasses.

First off, lets start with getting Lua, and luabind fired up:
Code:

// The Setup
lua_State* L = lua_open(); // create the Lua state
luaL_openlibs(L); // open all of the basic libraries so we can use them
luabind::open(L); // register luabind to our Lua state

I've omitted any error checking from above, but I assume you'll know how to implement those yourself. The code should be pretty self-explanatory.

Next, lets define our class:
Code:

class Foo {
public:
// The Constructor
// @self the reference to our Lua instance so we may reference to our Lua object in C++
// and call the Lua class methods via luabind::call_member<void>(m_Self, "function");
// @name an arbitrary test value, to identify our objects
Foo(luabind::object self, const char* name) : m_Self(self), m_Name(name) {
// Declare ourselves openly!
std::cout << m_Name << " created." << std::endl;
}

// The Destructor
~Foo(void) {
// Declare to the world that we no longer exist
std::cout << m_Name << " destroyed." << std::endl;
}

// This method will be overridden in Lua
void Think() {
std::cout << m_Name << " is thinking." << std::endl; // Output something generic
}

// This method will enable us to reference to another object, and alter it's values
// @target the reference to another Foo object
void Kill(luabind::object target) {
// Check if our target is a valid object
if (target && luabind::type(target) != LUA_TNIL) {
// Convert the luabind::object into our own class type
// notice the pointer form so we'll get just that, rather than a copy
Foo* target_foo = luabind::object_cast<Foo*>(target);
std::cout << m_Name << " attacks " << target_foo->m_Name << "!" << std::endl;

// Lets alter the target's properties
target_foo->m_Name = "Dead";
}
}

// Class attributes
luabind::object m_Self; // Reference to our own Lua object
std::string m_Name; // Our object's name
};
The code above is also pretty self-explanatory, as I attempted to explain everything in the comments. The things you need to know are the type luabind::object, which is a wrapper for all of Lua's data types, so we can access those types in C++.

The basic constructor takes two parameters, a Lua object, and a string for a name. This default class constructor is called from Lua via the function super(), which lets us access parent methods in classes. Next off, lets see how we can bind this class into Lua with the marvelous luabind:
Code:

luabind::module(L) [
luabind::class_<Foo>("Foo")
.def(luabind::constructor<luabind::object, const char*>())
.def("Think", &Foo::Think)
.def("Kill", &Foo::Kill)
.def_readwrite("name", &Foo::m_Name)
];
Now, this piece of code needs a little explaining to do. By luabind::module(), we can define functions, classes and variables to Lua and it's namespaces. I highly recommend reading through luabind's documentation to understand how all of this works.

But what we're doing now, is we first bind the class Foo into the Lua state L, via the luabind::class_<>() template method, followed by a chain of def() and def_readwrite() calls to specify the classes functionality and variables. The important row to check out is the line defining the constructor, where you'll have to specify the exact same parameters into the template; I wasn't able to create multiple constructors in C++, and I'm not sure if this is even possible. The member attribute m_Name will take the form of name in Lua, and so-forth.

Now, onto the Lua code:
Code:

-- First, define the two new subclasses of Foo
class 'Bar' (Foo)
class 'Baz' (Foo)

-- Bar's constructor, notice the super() method on the first row
function Bar:__init(name)
super(self, name); -- Call for Foo::Foo(self, name)
print("A " .. self.name .. " a day keeps the doctor away.");
end

-- Bar's overloading method for Think()
function Bar:Think()
print(self.name .. " is cool.");
end

-- Baz's constructor, the same method for super() applies as to Bar
function Baz:__init(name)
super(self, name); -- Call for Foo::Foo(self, name)
print("A " .. self.name .. " is a wonderful thing, don't you think?");
end

-- Create a bar
bar = Bar("Barry");
bar:Think();

-- Create a baz
baz = Baz("Bazzy");
baz:Think();

-- Battle to the DEATH!
baz:Kill(bar);
bar:Think();
Now, this segment I'll explain in greater detail. First off, we declare our two new classes derived from Foo, the class Bar and Baz. We then define a new constructor for Bar through Bar:__init(), which calls for Foo's constructor through the function super(), initializing the class in C++ and setting the name and whatnot. After this, we display a small message to see if our new derived constructor worked.

We also define a new Think() method for Bar, by simply declaring an overloading function for it. The same is applied for Baz, minus the overloading Think() method, which will cause Baz to call Foo's Think() method. Quite simple, really.

Now, you can run the Lua script with the function luaL_dofile(), as such:
Code:

if (luaL_dofile(L, "foo.lua")) {
std::cout << "We hit a little snug: " << lua_tostring(g_LuaState, -1) << std::endl; // Print out the error message
}
Now, if everything is correct, when you execute the program built from these little snippets, your output would look something like this:
Code:

Barry created.
A Barry a day keeps the doctor away.
Barry is cool.
Bazzy created.
A Bazzy is a wonderful thing, don't you think?
Bazzy is thinking.
Bazzy attacks Barry!
Dead is cool.
Bazzy destroyed.
Dead destroyed.
So, we created our new Bar, "Barry", and our Baz, "Bazzy", then went on to have Bazzy attack Barry, resulting in Barry changing his name to "Dead."

It is important to notice, that the object destructors are called only during the garbage collection phase, so make sure you close your Lua state before exiting the program, otherwise you'll encounter some memory loss and bad things will happen.

Now, that was all for now, I hope that was helpful to some of you and if you have any questions, suggestions for improvements or errors to correct, please do. :)

Hideous:
I didn't even know you could use classes in Lua. I thought it was not object-oriented?

diwil:
Lua does have OOP capabilities, but not in the traditional sense. The typing in Lua is dynamic, so things can be many things at once; an object in Lua basically has the same capabilities as a structure in pure C.

This is one of the reasons why I like Lua as a language; the flexibility enables me to do things with it that would rely on heavy templating with C++.

Moosader:
Oh shit!  Hi, Santa.  Didn't know you had joined. :P

diwil:
Hey Spoonie, yeah I joined a little time ago. :)

Navigation

[0] Message Index

[#] Next page