lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Michael Bauroth wrote:
Hi,

can eventually someone point me to a short example for how to use __index and __newIndex (eventually in context Lunar) to access C++ methods like properties?


Attached is part of what I use. Take a look at the functions 'thunk_index', 'thunk_newindex' and the place were those are registered.

Basically, you when your table is indexed, you llok for a matching method and if none you look for a matching property. There are some files missing from my example, but you essentially, the code you'd want to rip is here.

Example of use:

class A : public LuaCppBridge::RawObjectWithProperties<A> {
public:
...
[snip]

LCB_ROWP_DECLARE_EXPORTABLE(A);

LCB_DECL_SETGET(value);

}

const LuaCppBridge::RawObjectWithProperties<A>::RegType A::setters[] = {
	LCB_ADD_SET(value),
	{0}
};

const LuaCppBridge::RawObjectWithProperties<A>::RegType A::getters[] = {
	LCB_ADD_GET(value),
	{0}
};


LCB_IMPL_GET(A, value) {
	lua_pushstring(L, "hello world!");
	return 1;
}

LCB_IMPL_SET(A, value) {
	// stack pos #1 is 'self'
	const char* theValue = luaL_checkString(L, 2);
	return 0;
}


Lua code:
 a = A()	-- or A:new()
 print(a.value)
 a.value = "hi!"
 print(a.value)


Hope that helps.

Regards,
Ignacio Burgueño


#ifndef __luaCppBridge_RawObjectWithProperties_h__
#define __luaCppBridge_RawObjectWithProperties_h__

#include "LuaIncludes.h"
#include "lcbBaseObject.h"

#define LCB_ROWP_DECLARE_EXPORTABLE(classname) \
	static const LuaCppBridge::RawObjectWithProperties<##classname>::RegType methods[];\
	static const LuaCppBridge::RawObjectWithProperties<##classname>::RegType setters[];\
	static const LuaCppBridge::RawObjectWithProperties<##classname>::RegType getters[];\
	static const char* className;

/**
Algunos macros útiles más que nada para la definición de propiedades.
*/
#define LCB_DECL_SETGET(fieldname) int set_##fieldname (lua_State* L); int get_##fieldname (lua_State*L);
#define LCB_DECL_GET(fieldname) int get_##fieldname (lua_State* L);
#define LCB_DECL_SET(fieldname) int set_##fieldname (lua_State* L);
#define LCB_ADD_SET(fieldname) { #fieldname , set_##fieldname }
#define LCB_ADD_GET(fieldname) { #fieldname , get_##fieldname }
#define LCB_IMPL_SET(classname, fieldname) int classname::set_##fieldname (lua_State* L)
#define LCB_IMPL_GET(classname, fieldname) int classname::get_##fieldname (lua_State* L)

namespace LuaCppBridge {

/**
  Un RawObjectWithProperties es una clase de C++ expuesta hacia Lua como un userdata. 
Esto impide que desde Lua se agreguen cosas. Sólo se pueden utilizar las funciones provistas 
desde C++. Además, se puede definir un conjunto de propiedades (con sus respectivos 
setters y getters) para acceder desde Lua.

TO-DO:
Con esta clase NO se puede hacer herencia. Desde Lua no logré que se viesen las propiedades del padre. 
Sí conseguí que se pudiese acceder a los métodos del padre nomás, pero no tenía sentido habilitarlo si no se 
puede acceder a las propiedades.
Y en el caso que pudiese lograrlo, hay que aplicar el arreglo que se haga a RawObject.
*/
template <typename T> class RawObjectWithProperties : public BaseObject<T, RawObjectWithProperties> {
public:
	//////////////////////////////////////////////////////////////////////////
	///
	static void Register(lua_State* L) {
		Register(L, true);
	}

	static void Register(lua_State* L, bool isCreatableByLua) {
		int whereToRegister = g_luaCppBridge_config.libraryTablePosition;
		const RegType* l;
		lua_newtable(L);
		int methods = lua_gettop(L);
		
		luaL_newmetatable(L, T::className);
		int metatable = lua_gettop(L);
		
		// store method table in globals so that scripts can add functions written in Lua.
		lua_pushvalue(L, methods);
		set(L, whereToRegister, T::className);
		
		// hide metatable from Lua getmetatable()
		lua_pushvalue(L, methods);
		set(L, metatable, "__metatable");
		
		lua_pushliteral(L, "__index");
		lua_newtable(L);
		int index = lua_gettop(L);
		for (l = T::getters; l->name; l++) {
			lua_pushstring(L, l->name);
			lua_pushlightuserdata(L, (void*)l);
			lua_settable(L, index);
		}
		lua_pushvalue(L, methods);
		lua_pushcclosure(L, thunk_index, 2);
		lua_settable(L, metatable);
		
		lua_pushliteral(L, "__newindex");
		lua_newtable(L);
		int newindex = lua_gettop(L);
		for (l = T::setters; l->name; l++) {
			lua_pushstring(L, l->name);
			lua_pushlightuserdata(L, (void*)(l));
			lua_settable(L, newindex);
		}
		
		lua_pushvalue(L, methods);
		lua_pushcclosure(L, thunk_newindex, 2);
		lua_settable(L, metatable);
		
		
		lua_pushcfunction(L, tostring_T);
		set(L, metatable, "__tostring");
		
		lua_pushcfunction(L, gc_T);
		set(L, metatable, "__gc");

		if(isCreatableByLua) {
			// hago que llamando al nombre de la clase, me construya un objeto
			lua_newtable(L);                // mt for method table
			lua_pushcfunction(L, new_T);
			lua_pushvalue(L, -1);           // dup new_T function
			set(L, methods, "new");         // add new_T to method table
			set(L, -3, "__call");           // mt.__call = new_T
			lua_setmetatable(L, methods);
		}
		else {
			// hago que llamando al nombre de la clase, me salte un error
			lua_newtable(L);                // mt for method table
			lua_pushcfunction(L, forbidden_new_T);
			lua_pushvalue(L, -1);           // dup new_T function
			set(L, methods, "new");         // add new_T to method table
			set(L, -3, "__call");           // mt.__call = new_T
			lua_setmetatable(L, methods);
		}
		
		// fill method table with methods from class T
		for(l = T::methods; l->name; l++) {
			lua_pushstring(L, l->name);
			lua_pushlightuserdata(L, (void*)l);
			lua_pushcclosure(L, thunk_methods, 1);
			lua_settable(L, methods);
		}
		
		lua_pop(L, 2);  // drop metatable and method table
	}
	// create a new T object and push onto the Lua stack a userdata containing a pointer to T object
	static int new_T(lua_State* L) {
		lua_remove(L, 1);   // use classname:new(), instead of classname.new()
		T* obj = new T(L);  // call constructor for T objects
		push(L, obj, true); // gc_T will delete this object
		if(s_trackingEnabled) {
			obj->KeepTrack(L);
		}
		return 1;           // userdata containing pointer to T object
	}

	// pcall named lua method from userdata method table
	static int pcall(lua_State* L, const char* method, int nargs = 0, int nresults = LUA_MULTRET, int errfunc = 0)
	{
		int base = lua_gettop(L) - nargs;  // userdata index
		if(!luaL_checkudata(L, base, T::className)) {
			lua_settop(L, base - 1);           // drop userdata and args
			luaL_error(L, "not a valid %s userdata", T::className);
			return -1;
		}
		
		lua_pushstring(L, method);        // method name
		lua_gettable(L, base);            // get method from userdata
		if(lua_isnil(L, -1)) {            // no method?
			lua_settop(L, base - 1);      // drop userdata and args
			//lua_pushfstring(L, "%s missing method '%s'", T::className, method);
			return -1;
		}
		lua_insert(L, base);               // put method under userdata, args
		
		int status = lua_pcall(L, 1 + nargs, nresults, errfunc);  // call method
		if(status) {
			const char* msg = lua_tostring(L, -1);
			if(msg == NULL) {
				msg = "(error with no message)";
			}
			lua_pushfstring(L, "%s:%s status = %d\n%s", T::className, method, status, msg);
			lua_remove(L, base);             // remove old message
			return -1;
		}
		return lua_gettop(L) - base + 1;   // number of results
	}

	// call named lua method from userdata method table
	static int call(lua_State* L, const char* method, int nargs = 0, int nresults = LUA_MULTRET)
	{
		int base = lua_gettop(L) - nargs;  // userdata index
		if(!luaL_checkudata(L, base, T::className)) {
			lua_settop(L, base - 1);           // drop userdata and args
			luaL_error(L, "not a valid %s userdata", T::className);
			return -1;
		}
		
		lua_pushstring(L, method);        // method name
		lua_gettable(L, base);            // get method from userdata
		if(lua_isnil(L, -1)) {            // no method?
			lua_settop(L, base - 1);      // drop userdata and args
			//lua_pushfstring(L, "%s missing method '%s'", T::className, method);
			return -1;
		}
		lua_insert(L, base);               // put method under userdata, args
		
		lua_call(L, 1 + nargs, nresults);  // call method
		return lua_gettop(L) - base + 1;   // number of results
	}

	// push onto the Lua stack a userdata containing a pointer to T object
	static int push(lua_State* L, T* obj, bool gc = false) {
		if(!obj) {
			lua_pushnil(L);
			return 0;
		}
		luaL_getmetatable(L, T::className);  // lookup metatable in Lua registry
		if(lua_isnil(L, -1)) {
			luaL_error(L, "%s missing metatable", T::className);
		}
		int mt = lua_gettop(L);
		subtable(L, mt, "userdata", "v");
		userdataType *ud = static_cast<userdataType*>(pushuserdata(L, obj, sizeof(userdataType)));
		if(ud) {
			ud->pT = obj;  // store pointer to object in userdata
			lua_pushvalue(L, mt);
			lua_setmetatable(L, -2);
			if(gc == false) {
				lua_checkstack(L, 3);
				subtable(L, mt, "do not trash", "k");
				lua_pushvalue(L, -2);
				lua_pushboolean(L, 1);
				lua_settable(L, -3);
				lua_pop(L, 1);
			}
		}
		lua_replace(L, mt);
		lua_settop(L, mt);
		return mt;  // index of userdata containing pointer to T object
	}

	static T* checkopt(lua_State* L, int narg) {
		if(!lua_isnil(L, narg)) {
			return check(L, narg);
		}
		return NULL;
	};
	
	// 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
	}

	static int tostring_T(lua_State* L) {
		char buff[32];
		userdataType* ud = static_cast<userdataType*>(lua_touserdata(L, 1));
		T* obj = ud->pT;
		sprintf(buff, "%p", obj);
		lua_pushfstring(L, "%s (%s)", T::className, buff);
		return 1;
	}

	static int thunk_index(lua_State* L) {
		// stack: userdata, clave
		T* obj = check(L, 1);  // get 'self', or if you prefer, 'this'
		lua_pushvalue(L, 2);	// stack: userdata, clave clave
		lua_rawget(L, lua_upvalueindex(1));	// upvalue 1 = tabla con getters
		if(lua_isnil(L, -1)) {	// no es una propiedad, buscar en los métodos
			lua_pop(L, 1);	// stack: userdata, clave (argumentos??)
			lua_pushvalue(L, 2);	// userdata, clave, argumentos ... clave
			lua_rawget(L, lua_upvalueindex(2));
			if(!lua_isnil(L, -1)) {
				// le dejo la función thunk en el stack y que lua la llame
				// no la puedo llamar derecho porque patea (con que intenté indexar un string o algo así)
				return 1;
			}
			else {
				lua_pop(L, 1);
				// Aca debería seguir buscando para arriba en la metatabla del padre (si es que estoy)
				// heredando, pero NPI de cómo se hace, así que queda por esta
				// Mando un error y que se vayan a cagar
				luaL_error(L, "__index: el valor '%s' no existe", lua_tostring(L, 2));
				return 1; // para que el compilador no joda
			}
		}
		else {
			// stack: clave getter
			RegType* l = static_cast<RegType*>(lua_touserdata(L, -1));
			lua_settop(L, 0);
			return (obj->*(l->mfunc))(L);  // call member function
		}
		return 0;
	}
	
	static int thunk_newindex(lua_State* L) {
		// stack: userdata, clave, valor
		T* obj = check(L, 1);  // get 'self', or if you prefer, 'this'
		lua_pushvalue(L, 2);	// stack: userdata, clave, valor, clave
		lua_rawget(L, lua_upvalueindex(1));	// upvalue 1 = tabla con setters
		if(!lua_isnil(L, -1)) {
			// stack: userdata, clave, valor, setter
			RegType* p = static_cast<RegType*>(lua_touserdata(L, -1));
			lua_pop(L, 1);	// stack: userdata, clave, valor
			return (obj->*(p->mfunc))(L);  // call member function
		}
		else {
			luaL_error(L, "__newindex: el valor '%s' no existe", lua_tostring(L, 2));
			/*// Esto es opcional. Si lo dejo, le puedo agregar funciones
			// a un objeto trabajando sobre una instancia, sino tengo que agregarlas
			// a la clase
			if(lua_type(L, 3)  == LUA_TFUNCTION) {
				lua_pop(L, 1);	// stack: userdata, clave, valor
				lua_rawset(L, lua_upvalueindex(2));
			}
			else {
				lua_pop(L, 1);	// stack: userdata, clave, valor
				lua_rawset(L, lua_upvalueindex(2));
			}*/
		}
		return 0;
	}
};

}; // fin del namespace

#endif