Page 1 of 1

Lua Class Wrapper Tutorial

Posted: Sat Aug 18, 2012 11:58 am
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! ;)

Re: Lua Class Wrapper Tutorial

Posted: Tue Aug 21, 2012 8:53 am
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.

Re: Lua Class Wrapper Tutorial

Posted: Tue Aug 21, 2012 10:31 am
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:

Re: Lua Class Wrapper Tutorial

Posted: Tue Aug 28, 2012 2:18 am
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:

Re: Lua Class Wrapper Tutorial

Posted: Tue Aug 28, 2012 3:21 am
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?

Re: Lua Class Wrapper Tutorial

Posted: Thu Aug 30, 2012 12:54 am
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 :)

Re: Lua Class Wrapper Tutorial

Posted: Thu Aug 30, 2012 4:42 pm
by 101MUDman101
Would you like to share the code? I would love to see how you have acclomplished it! :lol: