[Date Prev][Date Next][Thread Prev][Thread Next]
[Date Index]
[Thread Index]
- Subject: Re: Inheriting from C++ Classes
- From: Ignacio Burgueño <ignaciob@...>
- Date: Fri, 23 Jan 2009 10:56:43 -0200
Nick wrote:
I'm making a game engine, which is using Lua for scripting, using Lunar
to export C++ classes to Lua.
I'd like to be able to add unique methods to individual instances in
Lua, they don't need to be accessible to C++.
As an example, I have a Sprite class that is used for a lot of the
objects in the game, I'd like the instances of these objects to define
their own Update function, but this isn't currently possible with the
set up that I seem to have (Update is NOT a function in the C++ object):
...
Update Functions can be called fine even though they don't exist in C++
however calls to PlayerSprite:Update call the same function as
EnemySprite:Update, seems like its being added to the 'class' not the
instance.
I've used Ignacio Burgueño's modifications to enable property style
access, and uncommented this part which could be the problem based on
this comment in spanish (in Lunar.h):
The wrapper you're using does not allow that. The one included here
does, so you can add methods per instance. You'll have to make the same
tweaks you surely needed to do to my other wrapper.
lcbBaseObject.h contains a lot of stuff you won't need at all. Is just
there for the RegType struct, so you can rip this:
typedef struct { T* pT; } userdataType;
typedef int (T::*mfp)(lua_State* L);
typedef struct {
const char* name;
mfp mfunc;
} RegType;
and paste it somewhere else.
Hope this helps. And sorry, the comments in the code are still in
spanish. Didn't have the time to change that.
Regards,
Ignacio Burgueño
#ifndef __luaCppBridge_HybridObjectWithProperties_h__
#define __luaCppBridge_HybridObjectWithProperties_h__
#include "LuaIncludes.h"
#include "lcbBaseObject.h"
#define LCB_HOWP_DECLARE_EXPORTABLE(classname) \
static const classname##::RegType methods[];\
static const classname##::RegType setters[];\
static const 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_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 HybridObjectWithProperties es una clase de C++ expuesta hacia Lua como una tabla, con un
userdata en el índice 0 de la misma. Esto permite que Lua agregue funciones y demás
elementos a la tabla y se pueden acceder desde el lado de 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.
*/
template <typename T, bool allowNilProperties = false> class HybridObjectWithProperties : public BaseObject<T, HybridObjectWithProperties<T> > {
private:
typedef struct ObjectWrapper {
T* wrappedObject;
bool collectable;
} ObjectWrapper;
public:
//////////////////////////////////////////////////////////////////////////
///
static void Register(lua_State* L, const char* parentClassName) {
Register(L, parentClassName, true);
}
static void Register(lua_State* L, const char* parentClassName, bool isCreatableByLua) {
int whereToRegister = g_luaCppBridge_config.libraryTablePosition;
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");
// lunar mio
lua_pushliteral(L, "__index");
lua_newtable(L);
int index = lua_gettop(L);
for(const RegType* 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, T::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, T::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
}
// 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(!lua_istable(L, base)) {
lua_settop(L, base-1); // drop table and args
luaL_error(L, "not a valid %s table", T::className);
return -1;
}
/*if(!luaL_checkudata(L, base, T::className)) {
lua_settop(L, base-1); // drop userdata and args
lua_pushfstring(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(!lua_istable(L, base)) {
lua_settop(L, base-1); // drop table and args
luaL_error(L, "not a valid %s table", 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_pcall(L, 1 + nargs, nresults); // call method
return lua_gettop(L) - base + 1; // number of results
}
// push onto the Lua stack a table containing a pointer to a T object at index 0
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);
}
// stack: metatabla
int metatable = lua_gettop(L);
subtable(L, metatable, "userdata", "v");
// stack: metatabla, tabla userdata
int newTable = pushtable(L, obj); // pongo la tabla que le voy a devolver a Lua en el Stack
// stack: metatabla, tabla userdata, tabla nueva
lua_pushnumber(L, 0); // y en el índice 0 guardo un puntero al objeto
// creo un userdata, cuyo valor es un wrapper liviano a mi objeto
ObjectWrapper* wrapper = static_cast<ObjectWrapper*>(lua_newuserdata(L, sizeof(ObjectWrapper)));
wrapper->wrappedObject = obj;
wrapper->collectable = gc;
// y le asigno la metatabla (solo me interesa el método __gc)
lua_pushvalue(L, metatable);
lua_setmetatable(L, -2);
lua_settable(L, newTable);
lua_pushvalue(L, metatable); // copio la metatabla
lua_setmetatable(L, -2); // y la asigno como metatable a la tabla Lua
lua_replace(L, metatable); // dejo la tabla nueva en lugar de la metatabla en el stack
lua_settop(L, metatable);
return metatable; // index of new table
}
static T* checkopt(lua_State* L, int narg) {
if(!lua_isnil(L, narg)) {
return check(L, narg);
}
return NULL;
};
// get table from Lua stack and return pointer to T object
static T* check(lua_State* L, int narg) {
luaL_checktype(L, narg, LUA_TTABLE);
//userdataType *ud = static_cast<userdataType*>(luaL_checkudata(L, narg, T::className));
//if(!ud) luaL_typerror(L, narg, T::className);
lua_pushnumber(L, 0);
lua_rawget(L, narg);
luaL_checktype(L, -1, LUA_TUSERDATA);
T* pT = static_cast<ObjectWrapper*>(lua_touserdata(L, -1))->wrappedObject;
if(!pT) {
luaL_typerror(L, narg, T::className);
}
lua_pop(L, 1);
return pT; // pointer to T object
}
protected:
static int thunk_index(lua_State* L) {
// stack: tabla, clave
const T* obj = check(L, 1); // get 'self', or if you prefer, 'this'
lua_pushvalue(L, 2); // stack: tabla, 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: tabla, clave (argumentos??)
lua_pushvalue(L, 2); // tabla, 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
if(allowNilProperties) {
lua_pushnil(L);
}
else {
luaL_error(L, "__index: el valor '%s' no existe para objetos de tipo %s", lua_tostring(L, 2), T::className);
}
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) {
// entro con stack: tabla, clave, valor
const T* obj = check(L, 1); // get 'self', or if you prefer, 'this'
// busco en la tabla de setters si hay una propiedad con esta clave
lua_pushvalue(L, 2); // stack: tabla, clave, valor, clave
lua_rawget(L, lua_upvalueindex(1)); // upvalue 1 = tabla con setters
if(!lua_isnil(L, -1)) {
// stack: tabla, clave, valor, setter
RegType* p = static_cast<RegType*>(lua_touserdata(L, -1));
lua_pop(L, 1); // stack: tabla, clave, valor
lua_insert(L, -2); // stack: tabla, valor, clave
lua_pop(L, 1); // stack: tabla, valor
return (obj->*(p->mfunc))(L); // call member function
}
else {
// seteo el valor en la propia tabla. luego de hacer esto Lua ya no va a pasar
// más por acá a buscar este valor
lua_pop(L, 1); // stack: tabla, clave, valor
lua_rawset(L, 1);
}
return 0;
}
// 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
int newTable = push(L, obj, true); // gc_T will delete this object
if(s_trackingEnabled) {
obj->KeepTrack(L);
}
// si me llamaron con una tabla como parámetro, copio los valores de la misma a la nueva tabla
if(lua_gettop(L) == 2 && lua_istable(L, 1)) {
lua_pushnil(L);
while(lua_next(L, 1)) { // stack: tabla, clave, valor
lua_pushvalue(L, -2); // stack: tabla, clave, valor, clave
lua_insert(L, -2); // stack: tabla, clave, clave, valor
lua_settable(L, newTable); // stack: tabla, clave
}
}
// un último paso en la creación del objeto. llamo a un método para que pueda acceder a la tabla
// que se la va a pasar a Lua
obj->PostConstruct(L);
return 1; // userdata containing pointer to T object
}
// garbage collection metamethod, viene con un userdata al tope del stack
static int gc_T(lua_State* L) {
#ifdef ENABLE_TRACE
OutputDebugString("attempting to collect object of type ");
OutputDebugString(T::className);
OutputDebugString("\n");
#endif
ObjectWrapper* wrapper = static_cast<ObjectWrapper*>(lua_touserdata(L, -1));
if(wrapper->collectable && wrapper->wrappedObject) {
#ifdef ENABLE_TRACE
char buff[256];
sprintf(buff, "collected %s (%p)\n", T::className, wrapper->wrappedObject);
OutputDebugString(buff);
#endif
delete wrapper->wrappedObject; // call destructor for wrapped objects
}
return 0;
}
static int tostring_T(lua_State* L) {
// cuidado, tanto el userdata como la tabla comparten este método
char buff[32];
if(lua_istable(L, 1)) {
lua_pushnumber(L, 0);
lua_rawget(L, 1);
luaL_checktype(L, -1, LUA_TUSERDATA);
}
const T* pT = static_cast<ObjectWrapper*>(lua_touserdata(L, -1))->wrappedObject;
sprintf(buff, "%p", pT);
lua_pushfstring(L, "%s (%s)", T::className, buff);
return 1;
}
public:
void PostConstruct(lua_State* L) {};
};
}; // fin del namespace
#endif
#ifndef __luaCppBridge_BaseObject_h__
#define __luaCppBridge_BaseObject_h__
#include "LuaIncludes.h"
#include "lcbBridgeConfig.h"
#if defined(ENABLE_TRACE)
#include <typeinfo>
#endif
namespace LuaCppBridge {
typedef void* ReferenceKey;
/**
Un BaseObject es la clase base para los wrappers hacia Lua.
Se instancia con T (la clase a exponer) y Base (el wrapper a usar).
Por defecto, de todos los objetos creados se guarda una referencia en Lua, de forma que desde
C++ se pueda acceder al objeto Lua y operar sobre él.
Si no se quiere que una determinada clase guarde referencias, antes de Registrarla llamar a
'EnableTracking' con false.
*/
template <typename T, typename Base>
class BaseObject {
public:
typedef struct { T* pT; } userdataType;
typedef int (T::*mfp)(lua_State* L);
typedef struct {
const char* name;
mfp mfunc;
} RegType;
static bool s_trackingEnabled;
static void EnableTracking(bool value) {
s_trackingEnabled = value;
}
// se encarga de hacer el new, el push hacia Lua y hacer el tracking
static T* Construct(lua_State* L, bool gc = false) {
T* newObject = new T(L); // call constructor for T objects
int newTable = Base::push(L, newObject, gc); // gc_T will delete this object
if(s_trackingEnabled) {
newObject->KeepTrack(L);
}
return newObject;
}
protected:
static int forbidden_new_T(lua_State* L) {
luaL_error(L, "Constructing objects of type '%s' is not allowed from the Lua side", T::className);
return 1;
}
// member function dispatcher
static int thunk_methods(lua_State* L) {
// stack has userdata, followed by method args
T* obj = Base::check(L, 1); // get 'self', or if you prefer, 'this'
//lua_remove(L, 1); // remove self so member function args start at index 1
// get member function from upvalue
RegType* l = static_cast<RegType*>(lua_touserdata(L, lua_upvalueindex(1)));
return (obj->*(l->mfunc))(L); // call member function
}
// garbage collection metamethod, viene con un userdata al tope del stack
static int gc_T(lua_State* L) {
#ifdef ENABLE_TRACE
OutputDebugString("attempting to collect object of type ");
OutputDebugString(T::className);
OutputDebugString("\n");
#endif
if(luaL_getmetafield(L, 1, "do not trash")) {
lua_pushvalue(L, 1); // dup userdata
lua_gettable(L, -2);
if(!lua_isnil(L, -1)) {
return 0; // do not delete object
}
}
const T* pT = *(static_cast<const T**>(lua_touserdata(L, -1)));
#ifdef ENABLE_TRACE
char buff[256];
sprintf(buff, "collected %s (%p)\n", T::className, pT);
OutputDebugString(buff);
#endif
if(pT) delete pT; // call destructor for T objects
return 0;
}
static void set(lua_State* L, int table_index, const char* key) {
lua_pushstring(L, key);
lua_insert(L, -2); // swap value and key
lua_settable(L, table_index);
}
static void weaktable(lua_State* L, const char* mode) {
lua_newtable(L);
lua_pushvalue(L, -1); // table is its own metatable
lua_setmetatable(L, -2);
lua_pushliteral(L, "__mode");
lua_pushstring(L, mode);
lua_settable(L, -3); // metatable.__mode = mode
}
// a la tabla que está en 'tindex' le agrega una subtabla weak (según mode) de nombre 'name'
// y la retorna en el stack.
static void subtable(lua_State* L, int tindex, const char* name, const char* mode) {
lua_pushstring(L, name);
lua_gettable(L, tindex);
if (lua_isnil(L, -1)) {
#ifdef ENABLE_TRACE
char buffer[128];
const char* typeName = typeid(T).name();
sprintf(buffer, "creating new subtable (%s) with mode '%s' for '%s'\n", name, mode, typeName);
OutputDebugString(buffer);
#endif
lua_pop(L, 1);
lua_checkstack(L, 3);
weaktable(L, mode);
lua_pushstring(L, name);
lua_pushvalue(L, -2);
lua_settable(L, tindex);
}
}
// se fija en la tabla al tope del stack si existe algún elemento de clave 'key'
// si no, crea un userdata nuevo y lo agrega con dicha clave.
// Deja el userdata al tope del stack y lo devuelve.
static void* pushuserdata(lua_State* L, void* key, size_t sz) {
void* ud = 0;
lua_pushlightuserdata(L, key);
lua_gettable(L, -2); // lookup[key]
if(lua_isnil(L, -1)) {
lua_pop(L, 1); // drop nil
lua_checkstack(L, 3);
ud = lua_newuserdata(L, sz); // create new userdata
lua_pushlightuserdata(L, key);
lua_pushvalue(L, -2); // dup userdata
lua_settable(L, -4); // lookup[key] = userdata
}
return ud;
}
// se fija en la tabla al tope del stack si existe algún elemento de clave 'key'
// si no, crea una tabla nueva y la agrega con dicha clave.
// Deja la tabla al tope del stack y devuelve su índice.
static int pushtable(lua_State* L, void* key) {
lua_pushlightuserdata(L, key);
lua_gettable(L, -2);
if(lua_isnil(L, -1)) { // si no está en la tabla
lua_pop(L, 1);
lua_checkstack(L, 3);
lua_newtable(L);
lua_pushlightuserdata(L, key);
lua_pushvalue(L, -2); // me copio la tabla
lua_settable(L, -4); // lookup[key] = table
}
return lua_gettop(L);
}
public:
mutable ReferenceKey m_selfReference;
void KeepTrack(lua_State* L) const {
m_selfReference = (ReferenceKey)this;
lua_getfield(L, g_luaCppBridge_config.trackingIndex, g_luaCppBridge_config.trackingName); // stack-> self, instances
lua_pushlightuserdata(L, m_selfReference); // uso a 'this' como clave // stack-> self, instances, clave
lua_pushvalue(L, -3); // stack-> self, instances, clave, self
lua_settable(L, -3); // stack-> self, instances
lua_pop(L, 1); // stack-> self
}
ReferenceKey GetSelfReference() const {
return m_selfReference;
}
//////////////////////////////////////////////////////////////////////////
/// Busca la tabla asociada a una instancia de la clase
void GetSelf(lua_State* L) {
if(!s_trackingEnabled) {
luaL_error(L, "class %s is not being tracked", T::className);
}
lua_getfield(L, g_luaCppBridge_config.trackingIndex, g_luaCppBridge_config.trackingName);
lua_assert(lua_istable(L, -1));
lua_pushlightuserdata(L, m_selfReference);
lua_gettable(L, -2);
if(lua_isnil(L, -1)) {
luaL_error(L, "'%p' has no bound userdata or table", m_selfReference);
}
lua_remove(L, -2); // saco la tabla de instancias
// me quedo con la tabla (o userdata) asociada con this en el tope del stack
}
};
template <typename T, typename Base>
bool BaseObject<T, Base>::s_trackingEnabled = true;
//////////////////////////////////////////////////////////////////////////
/// Busca la tabla asociada a una clave y la deja en el stack
static void GetReference(lua_State* L, ReferenceKey key) {
//char buffer[128];
//const char* n = typeid(T).name();
//sprintf(buffer, "retrieving reference to '%p' as type '%s'\n", key, n);
//OutputDebugString(buffer);
lua_getfield(L, g_luaCppBridge_config.trackingIndex, g_luaCppBridge_config.trackingName);
lua_pushlightuserdata(L, key);
lua_gettable(L, -2);
if(lua_isnil(L, -1)) {
/*while(lua_next(L, -2) != 0) {
char clave[256];
char valor[256];
switch(lua_type(L, -2)) {
case LUA_TSTRING:
strcpy(clave, lua_tostring(L, -2));
break;
case LUA_TNUMBER:
sprintf(clave, "%d", (int)lua_tonumber(L, -2));
//itoa((int)lua_tonumber(L, -2), clave, 10);
break;
case LUA_TTABLE:
sprintf(clave, "table: (%p)", lua_topointer(L, -2));
break;
case LUA_TUSERDATA:
sprintf(clave, "userdata: (%p)", lua_topointer(L, -2));
break;
case LUA_TLIGHTUSERDATA:
sprintf(clave, "lightuserdata: (%p)", lua_topointer(L, -2));
break;
default:
strcpy(clave, lua_typename(L, lua_type(L, -2)));
break;
}
switch(lua_type(L, -1)) {
case LUA_TSTRING:
strcpy(valor, lua_tostring(L, -1));
break;
case LUA_TNUMBER:
sprintf(valor, "%d", (int)lua_tonumber(L, -1));
//itoa((int)lua_tonumber(L, -1), valor, 10);
break;
case LUA_TTABLE:
sprintf(valor, "table: (%p)", lua_topointer(L, -1));
break;
case LUA_TUSERDATA:
sprintf(valor, "userdata: (%p)", lua_topointer(L, -1));
break;
case LUA_TLIGHTUSERDATA:
sprintf(valor, "lightuserdata: (%p)", lua_topointer(L, -1));
break;
default:
strcpy(valor, lua_typename(L, lua_type(L, -1)));
break;
}
sprintf(buffer, "'%s', '%s'\n", clave, valor);
OutputDebugString(buffer);
lua_pop(L, 1);
}*/
luaL_error(L, "'%p' has no bound userdata or table", key);
}
lua_remove(L, -2); // saco la tabla de instancias
// me quedo con la tabla (o userdata) asociada con this en el tope del stack
}
}; // fin del namespace
#endif