Lua Class Wrapper Tutorial

Whether you're a newbie or an experienced programmer, any questions, help, or just talk of any language will be welcomed here.

Moderator: Coders of Rage

Post Reply
User avatar
101MUDman101
Chaos Rift Newbie
Chaos Rift Newbie
Posts: 38
Joined: Fri Aug 17, 2012 12:36 pm
Current Project: Elemental Dawn (2D RPG)
Favorite Gaming Platforms: PC, NES
Programming Language of Choice: C/++, Lua, Perl
Location: England
Contact:

Lua Class Wrapper Tutorial

Post by 101MUDman101 »

I think I should contribute to the community by making this tutorial, I assume it is the only one of it's kind as I spent hours searching for the code snippet. Basically if you use C++ and want to integrate your classes into Lua but don't know how then I have an 81 line solution for you. Keep in mind, I wont go over setting up lua as there is a tutorial on this forum here: viewtopic.php?f=6&t=6564&p=76794&hilit= ... %3B#p76794

Requirments:
  • - A C++ IDE
    - Lua setup up with your IDE
    - A Brain (Optional)
The code is a template class that utilizes lua's powerful 'table' methods, please note I take no ownership of this code.

To use the class make a header file and paste the following code in:

Code: Select all

template<class T> class Luna {
  public:
    static void Register(lua_State *L) {
      lua_pushcfunction(L, &Luna<T>::constructor);
      lua_setglobal(L, T::className);

      luaL_newmetatable(L, T::className);
      lua_pushstring(L, "__gc");
      lua_pushcfunction(L, &Luna<T>::gc_obj);
      lua_settable(L, -3);
    }

    static int constructor(lua_State *L) {
      T* obj = new T(L);

      lua_newtable(L);
      lua_pushnumber(L, 0);
      T** a = (T**)lua_newuserdata(L, sizeof(T*));

      *a = obj;
      luaL_getmetatable(L, T::className);
      lua_setmetatable(L, -2);
      lua_settable(L, -3); // table[0] = obj;

      for (int i = 0; T::Register[i].name; i++) {
        lua_pushstring(L, T::Register[i].name);
        lua_pushnumber(L, i);
        lua_pushcclosure(L, &Luna<T>::thunk, 1);
        lua_settable(L, -3);
      }
      return 1;
    }

    static int thunk(lua_State *L) {
      int i = (int)lua_tonumber(L, lua_upvalueindex(1));
      lua_pushnumber(L, 0);
      lua_gettable(L, 1);

      T** obj = static_cast<T**>(luaL_checkudata(L, -1, T::className));
      lua_remove(L, -1);
      return ((*obj)->*(T::Register[i].mfunc))(L);
    }

    static int gc_obj(lua_State *L) {
      T** obj = static_cast<T**>(luaL_checkudata(L, -1, T::className));
      delete (*obj);
      return 0;
    }

    struct RegType {
      const char *name;
      int(T::*mfunc)(lua_State*);
    };

	// Directly add the new class
	static T* RegisterTable(lua_State *L)
	{
		luaL_newmetatable(L, T::className);
		lua_pushstring(L, "__gc");
		lua_pushcfunction(L, &Luna<T>::gc_obj);
		lua_settable(L, -3);

		T* obj = new T(L);
		lua_newtable(L);
		lua_pushnumber(L, 0);
		T** a = (T**)lua_newuserdata(L, sizeof(T*));
		*a = obj;
		luaL_getmetatable(L, T::className);
		lua_setmetatable(L, -2);
		lua_settable(L, -3); // table[0] = obj;
		for (int i = 0; T::Register[i].name; i++)
		{
			lua_pushstring(L, T::Register[i].name);
			lua_pushnumber(L, i);
			lua_pushcclosure(L, &Luna<T>::thunk, 1);
			lua_settable(L, -3);
		}
		lua_setglobal(L, T::className);
		return obj;
	}
};

The code may look a bit complicated but essentially, it is quite simple, the 'Register' function takes your class and all it's functions and passes them into a metatable. The 'contructor' function is used to let lua know what your classes contructor is.

The functions 'thunk' and 'RegisterTable' I have not used yet, you may experiment with them at your own will.

Now this is the important part, you must now setup a lua class, here is an example of a class I am using to draw a sprite from lua:

Code: Select all

class LuaSprite {
private:
	SDL_Surface* surface;
	SDL_Rect rect;
	int xvel, yvel;
	std::string tag;
	int alpha;
public:
	LuaSprite(lua_State* L) {
	
	}

	int GetSurface(lua_State *L) { 
		lua_pushlightuserdata(L, (void*)surface);

		return 1; 
	}
	int GetRect(lua_State *L) { 
		lua_pushlightuserdata(L, (void*)&rect);

		return 1;
	}
	int GetWidth(lua_State *L) {
		lua_pushnumber(L, rect.w);

		return 1;
	}
	int GetHeight(lua_State *L) {
		lua_pushnumber(L, rect.h);

		return 1;
	}
	int GetAlpha(lua_State *L) {
		lua_pushnumber(L, alpha);

		return 1;
	}
	//~LuaSprite() {
	//	SDL_FreeSurface(surface);
	//}
	int Update(lua_State *L);
	int Show(lua_State *L)
	{
		SDL_SetAlpha(surface, SDL_SRCALPHA, alpha);

		SDL_BlitSurface(surface, NULL, SDL_GetVideoSurface(), &rect);
	
		return 1;
	}
	int LuaSprite::LoadImage(lua_State* L)
	{
		SDL_Surface* o;
		o = IMG_Load(luaL_checkstring(L, 2));

		SDL_DisplayFormatAlpha(o);

		SDL_SetColorKey(o, SDL_SRCCOLORKEY | SDL_RLEACCEL, 0xFF00FF);

		alpha = 255;

		surface = o;

		return 1;
	}
	int SetX(lua_State *L) {
		rect.x = (int)luaL_checknumber(L, 2);

		return 1;
	}
	int SetY(lua_State *L) { 
		rect.y = (int)luaL_checknumber(L, 2);

		return 1;
	}
	int SetAlpha(lua_State *L) {
		alpha = (int)luaL_checknumber(L, 2);

		return 1;
	}
	int SetTag(lua_State* L) {
		tag = luaL_checkstring(L, 2);

		return 1;
	}
	int GetX(lua_State *L) { 
		lua_pushnumber(L, rect.x);

		return 1;
	}
	int GetY(lua_State *L) { 
		lua_pushnumber(L, rect.y);

		return 1;
	}
	int GetTag(lua_State *L) { 
		lua_pushstring(L, tag.c_str());

		return 1; 
	}

	static const char className[];
	static const Luna<LuaSprite>::RegType Register[];
};
Two things to notice, all the functions MUST have an integer return type, the second thing is that if you look at the bottom of the class you will see these two lines:

Code: Select all

static const char className[];
static const Luna<LuaSprite>::RegType Register[];
These lines are for setting up our class with lua, the only line that will matter the most is the first one as we will be telling lua what are class is called using that variable.

So now we need to move onto telling lua what are class does, I recommend putting this in your main.cpp are where you have setup lua, also you do not need to put it in a function like 'main'. The following snippet tells lua the functions of the class above:

Code: Select all

const char LuaSprite::className[] = "Sprite";
const Luna<LuaSprite>::RegType LuaSprite::Register[] = {
	{ "GetX", &LuaSprite::GetX },
	{ "GetY", &LuaSprite::GetY },
	{ "GetWidth", &LuaSprite::GetWidth },
	{ "GetHeight", &LuaSprite::GetHeight },
	{ "GetAlpha", &LuaSprite::GetAlpha },
	{ "GetTag", &LuaSprite::GetTag },
	{ "SetX", &LuaSprite::SetX },
	{ "SetY", &LuaSprite::SetY },
	{ "SetAlpha", &LuaSprite::SetAlpha },
	{ "SetTag", &LuaSprite::SetTag },
	{ "GetRect", &LuaSprite::GetRect },
	{ "Show", &LuaSprite::Show },
	{ "LoadImage", &LuaSprite::LoadImage },
	{ 0 }
};
Now to explain, the first line uses the 'className[]' variable from the class definition, here we are setting it to 'Sprite', this is what we will type into lua to create the object. The following line just gets ready to tell lua the functions. You may notice that it looks a bit like an 'enum' definition', lets take the first function as an example:

Code: Select all

{ "GetX", &LuaSprite::GetX },
Firstly, the 'GetX' in double quotations is what we will type into in lua to use the function, you do not have to call it exactly what the function is called, but I recommend you do so to avoid future confusion. The second part is getting a reference of the function from the class, notice that you don't need the brackets after the function.

Dont worry, we are almost there!

Now in a function, preferably 'main' and after you have setup a 'lua_State', we need to register our class, this will send the 'Register[]' array, from the snippet above, to lua. Here is the line of code:

Code: Select all

Luna<LuaSprite>::Register(L);
You may now realise why we need to put all this code where we have setup lua, we are registering the class to a lua_State, mine is called 'L', you may call yours something different.

Finally (Yes!) I will show you how to use our class in lua, before hand make sure you are calling your script from C++ using 'luaL_dofile' before doing this:

Code: Select all

mysprite = Sprite() --Make a Sprite object

function SomeFunction()
    mysprite:LoadImage("imagepath")
    mysprite:SetX(45)
    --etc...
end
I hope this snippet is self explanitory, the only thing you need to keep in mind is the ':' before you type in the function.

That's it! Also, please note the class example I have given you may work, I am not sure, it would be best to use your own. Hope this helps! ;)
Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

Damn Right.

Channel: http://www.youtube.com/user/101MUDman101
User avatar
bbguimaraes
Chaos Rift Junior
Chaos Rift Junior
Posts: 294
Joined: Wed Apr 11, 2012 4:34 pm
Programming Language of Choice: c++
Location: Brazil
Contact:

Re: Lua Class Wrapper Tutorial

Post by bbguimaraes »

That's a nice way of creating the integration, although I can't comment much, because Lua integration is still on my TOLEARN list.

Have you checked tolua or similar libraries? They have a totally different approach, where you provide a file with a definition of the classes and functions you want to export to Lua and the library utilities will parse it and generate c++ code similar to yours.
User avatar
101MUDman101
Chaos Rift Newbie
Chaos Rift Newbie
Posts: 38
Joined: Fri Aug 17, 2012 12:36 pm
Current Project: Elemental Dawn (2D RPG)
Favorite Gaming Platforms: PC, NES
Programming Language of Choice: C/++, Lua, Perl
Location: England
Contact:

Re: Lua Class Wrapper Tutorial

Post by 101MUDman101 »

I must say this is quicker than toLua or luaBind as it does not need to create any C++ files or convert anything, it just has to create a few metatables with a few functions and make sure they are global. Plus it's only 81 Lines :lol: I tryed aimlessly for at least a few days to get luaBind to work and I had to download boost and then link them and then include the headers, it was a hassle.

I owe Luna my life :worship:
Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

Damn Right.

Channel: http://www.youtube.com/user/101MUDman101
User avatar
Light-Dark
Dreamcast Developer
Dreamcast Developer
Posts: 307
Joined: Sun Mar 13, 2011 7:57 pm
Current Project: 2D RPG & NES Platformer
Favorite Gaming Platforms: NES,SNES,N64,Genesis,Dreamcast,PC,Xbox360
Programming Language of Choice: C/++
Location: Canada

Re: Lua Class Wrapper Tutorial

Post by Light-Dark »

Damn, i notice people use luabind,tolua++,etc,etc but i can't be the only one who wraps his classes without these utilities? :lol:
<tpw_rules> LightDark: java is a consequence of inverse moore's law: every 18 months, the average program will be twice as slow. therefore, computers always run at the same percevied speed. java's invention was a monumental step
Image
User avatar
101MUDman101
Chaos Rift Newbie
Chaos Rift Newbie
Posts: 38
Joined: Fri Aug 17, 2012 12:36 pm
Current Project: Elemental Dawn (2D RPG)
Favorite Gaming Platforms: PC, NES
Programming Language of Choice: C/++, Lua, Perl
Location: England
Contact:

Re: Lua Class Wrapper Tutorial

Post by 101MUDman101 »

Light-Dark wrote:Damn, i notice people use luabind,tolua++,etc,etc but i can't be the only one who wraps his classes without these utilities? :lol:
So you have made your own custom wrapper?
Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

Damn Right.

Channel: http://www.youtube.com/user/101MUDman101
User avatar
Light-Dark
Dreamcast Developer
Dreamcast Developer
Posts: 307
Joined: Sun Mar 13, 2011 7:57 pm
Current Project: 2D RPG & NES Platformer
Favorite Gaming Platforms: NES,SNES,N64,Genesis,Dreamcast,PC,Xbox360
Programming Language of Choice: C/++
Location: Canada

Re: Lua Class Wrapper Tutorial

Post by Light-Dark »

101MUDman101 wrote:
Light-Dark wrote:Damn, i notice people use luabind,tolua++,etc,etc but i can't be the only one who wraps his classes without these utilities? :lol:
So you have made your own custom wrapper?
Pretty much :)
<tpw_rules> LightDark: java is a consequence of inverse moore's law: every 18 months, the average program will be twice as slow. therefore, computers always run at the same percevied speed. java's invention was a monumental step
Image
User avatar
101MUDman101
Chaos Rift Newbie
Chaos Rift Newbie
Posts: 38
Joined: Fri Aug 17, 2012 12:36 pm
Current Project: Elemental Dawn (2D RPG)
Favorite Gaming Platforms: PC, NES
Programming Language of Choice: C/++, Lua, Perl
Location: England
Contact:

Re: Lua Class Wrapper Tutorial

Post by 101MUDman101 »

Would you like to share the code? I would love to see how you have acclomplished it! :lol:
Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

Damn Right.

Channel: http://www.youtube.com/user/101MUDman101
Post Reply