[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: property based C++ API with Lunar?
- From: Ignacio Burgueño <ignaciob@...>
- Date: Wed, 12 Nov 2008 10:20:10 -0200
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