From 63cb157197b85125c8edaad863c5e6c63e67cd25 Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Fri, 29 Sep 2006 05:07:17 +0000 Subject: [PATCH] simplify LuaBinding, move more code out of the template --- stepmania/src/LuaBinding.cpp | 204 ++++++++++++++++++++++++++++++++--- stepmania/src/LuaBinding.h | 182 +++++++++---------------------- 2 files changed, 238 insertions(+), 148 deletions(-) diff --git a/stepmania/src/LuaBinding.cpp b/stepmania/src/LuaBinding.cpp index d303dab19e..997896e2ea 100644 --- a/stepmania/src/LuaBinding.cpp +++ b/stepmania/src/LuaBinding.cpp @@ -3,6 +3,119 @@ #include "LuaReference.h" #include "RageUtil.h" +#include "SubscriptionManager.h" +static SubscriptionManager m_Subscribers; + +namespace +{ + void RegisterTypes( lua_State *L ) + { + if( m_Subscribers.m_pSubscribers == NULL ) + return; + + /* Register base classes first. */ + map mapToRegister; + FOREACHS( LuaBinding*, *m_Subscribers.m_pSubscribers, p ) + mapToRegister[(*p)->GetClassName()] = (*p); + + set setRegisteredAlready; + + while( !mapToRegister.empty() ) + { + /* Look at the first class. If it has a base class that needs to be registered, + * go there first. */ + LuaBinding *pBinding = mapToRegister.begin()->second; + while(1) + { + if( !pBinding->IsDerivedClass() ) + break; + + RString sBase = pBinding->GetBaseClassName(); + map::const_iterator it = mapToRegister.find(sBase); + if( it != mapToRegister.end() ) + { + pBinding = it->second; + continue; + } + + /* If the base class wasn't found, and hasn't been registered already, then + * a base class registration is missing. */ + if( setRegisteredAlready.find(sBase) != setRegisteredAlready.end() ) + break; + + FAIL_M( ssprintf("Base class of \"%s\" not registered: \"%s\"", + pBinding->GetClassName().c_str(), + sBase.c_str()) ); + } + + pBinding->Register( L ); + setRegisteredAlready.insert( pBinding->GetClassName() ); + mapToRegister.erase( pBinding->GetClassName() ); + } + } +}; +REGISTER_WITH_LUA_FUNCTION( RegisterTypes ); + +LuaBinding::LuaBinding() +{ + m_Subscribers.Subscribe( this ); +} + +LuaBinding::~LuaBinding() +{ + m_Subscribers.Unsubscribe( this ); +} + +void LuaBinding::Register( lua_State *L ) +{ + /* Create the methods table, if it doesn't already exist. */ + LuaBinding::CreateMethodsTable( L, GetClassName() ); + int methods = lua_gettop( L ); + + /* Create a metatable for the userdata objects. */ + luaL_newmetatable( L, GetClassName() ); + int metatable = lua_gettop( L ); + + // We use the metatable to determine the type of the table, so don't + // allow it to be changed. + lua_pushstring( L, "(hidden)" ); + lua_setfield( L, metatable, "__metatable" ); + + lua_pushvalue( L, methods ); + lua_setfield( L, metatable, "__index" ); + + lua_pushcfunction( L, PushEqual ); + lua_setfield( L, metatable, "__eq" ); + + /* Create a metatable for the methods table. */ + lua_newtable( L ); + int methods_metatable = lua_gettop( L ); + + // Hide the metatable. + lua_pushstring( L, "(hidden)" ); + lua_setfield( L, methods_metatable, "__metatable" ); + + // If this type has a base class, set the __index of this type + // to the base class. + if( IsDerivedClass() ) + { + lua_getfield( L, LUA_GLOBALSINDEX, GetBaseClassName() ); + lua_setfield( L, methods_metatable, "__index" ); + } + + lua_pushstring( L, GetClassName() ); + lua_setfield( L, methods_metatable, "type" ); + + /* Set and pop the methods metatable. */ + lua_setmetatable( L, methods ); + + /* Allow the derived class to populate the method table, and set any other + * metatable fields. */ + Register( L, methods, metatable ); + + lua_pop( L, 2 ); // drop metatable and method table +} + // If defined, type checks for functions will be skipped. These add // up and can become expensive (performed for every function dispatch), // but without them it's possible to call functions on incompatible @@ -11,16 +124,65 @@ void LuaBinding::CreateMethodsTable( lua_State *L, const RString &sName ) { - lua_pushlstring( L, sName.data(), sName.size() ); - lua_rawget( L, LUA_GLOBALSINDEX ); + lua_getfield( L, LUA_GLOBALSINDEX, sName ); if( !lua_isnil(L, -1) ) return; lua_pop( L, 1 ); lua_newtable( L ); - lua_pushlstring( L, sName.data(), sName.size() ); - lua_pushvalue( L, -2 ); - lua_rawset( L, LUA_GLOBALSINDEX ); + lua_pushvalue( L, -1 ); + lua_setfield( L, LUA_GLOBALSINDEX, sName ); +} + +int LuaBinding::PushEqual( lua_State *L ) +{ + lua_pushboolean( L, Equal(L) ); + return 1; +} + +bool LuaBinding::Equal( lua_State *L ) +{ + int iArg1 = lua_gettop( L ) - 1; + int iArg2 = lua_gettop( L ); + + int iType = lua_type( L, iArg1 ); + if( iType != lua_type(L, iArg2) ) + return false; + + if( iType != LUA_TTABLE && iType != LUA_TUSERDATA ) + return false; + + /* Use the regular method for tables. If an object's table is + * kept around after the actual object has been destroyed, the + * table is still valid, and the pointer no longer exists. */ + if( iType == LUA_TTABLE ) + return !!lua_rawequal( L, iArg1, iArg2 ); + + // This checks that they're the same type. it does not check + // that it's actually a LuaBinding type. If iArg1 is a non-LuaBinding + // type, this function should not be called and the return value is + // undefined, but the lua_objlen check below will prevent us from crashing. + if( !lua_getmetatable(L, iArg1) ) + return false; + if( !lua_getmetatable(L, iArg2) ) + { + lua_pop( L, 1 ); + return false; + } + + bool bSameType = !!lua_rawequal( L, -1, -2 ); + lua_pop( L, 2 ); + if( !bSameType ) + return false; + + if( lua_objlen(L, iArg1) != sizeof(void *) ) + return false; + if( lua_objlen(L, iArg2) != sizeof(void *) ) + return false; + + void **pData1 = (void **) lua_touserdata( L, iArg1 ); + void **pData2 = (void **) lua_touserdata( L, iArg2 ); + return *pData1 == *pData2; } /* @@ -114,7 +276,6 @@ void LuaBinding::ApplyDerivedType( Lua *L, const RString &sClassName, void *pSel { void *pData = lua_touserdata( L, -1 ); ASSERT( pSelf == pData ); - ASSERT( pSelf == lua_touserdata(L, -1) ); lua_settop( L, iTable ); return; @@ -136,19 +297,32 @@ void LuaBinding::ApplyDerivedType( Lua *L, const RString &sClassName, void *pSel #include "RageUtil_AutoPtr.h" REGISTER_CLASS_TRAITS( LuaClass, new LuaClass(*pCopy) ) -void *LuaBinding::GetUserdataFromGlobalTable( Lua *L, const char *szType, int iArg ) +void *LuaBinding::GetPointerFromStack( Lua *L, const RString &sType, int iArg ) { - GetGlobalTable( L ); + iArg = LuaHelpers::AbsIndex( L, iArg ); - lua_pushvalue( L, iArg ); - lua_rawget( L, -2 ); - if( lua_isnil(L, -1) ) - luaL_error( L, "stale %s referenced (object used but no longer exists)", szType ); + /* The stack has a userdata or a table. If it's a table, look up the associated userdata. */ + if( lua_istable(L, iArg) ) + { + GetGlobalTable( L ); - void *pRet = lua_touserdata( L, -1 ); - lua_pop( L, 2 ); + lua_pushvalue( L, iArg ); + lua_rawget( L, -2 ); + if( lua_isnil(L, -1) ) + luaL_error( L, "stale %s referenced (object used but no longer exists)", sType.c_str() ); - return pRet; + void *pRet = lua_touserdata( L, -1 ); + lua_pop( L, 2 ); + + return pRet; + } + else if( lua_isuserdata(L, iArg) ) + { + void **pData = (void **) lua_touserdata( L, iArg ); + return *pData; + } + else + return NULL; } /* Tricky: when an instance table is copied, we want to do a deep diff --git a/stepmania/src/LuaBinding.h b/stepmania/src/LuaBinding.h index 2b54d3cc53..65719601fd 100644 --- a/stepmania/src/LuaBinding.h +++ b/stepmania/src/LuaBinding.h @@ -6,16 +6,34 @@ #include "LuaManager.h" class LuaReference; -namespace LuaBinding +class LuaBinding { - void CreateMethodsTable( lua_State *L, const RString &szName ); - bool CheckLuaObjectType( lua_State *L, int narg, const char *szType, bool bOptional=false ); - void *GetUserdataFromGlobalTable( Lua *L, const char *szType, int iArg ); - void ApplyDerivedType( Lua *L, const RString &sClassname, void *pSelf ); +public: + LuaBinding(); + ~LuaBinding(); + void Register( lua_State *L ); + + static void RegisterTypes( lua_State *L ); + + bool IsDerivedClass() const { return GetClassName() != GetBaseClassName(); } + virtual const RString &GetClassName() const = 0; + virtual const RString &GetBaseClassName() const = 0; + + static bool CheckLuaObjectType( lua_State *L, int narg, const char *szType, bool bOptional=false ); + static void ApplyDerivedType( Lua *L, const RString &sClassname, void *pSelf ); + +protected: + virtual void Register( Lua *L, int iMethods, int iMetatable ) = 0; + + static void CreateMethodsTable( lua_State *L, const RString &szName ); + static void *GetPointerFromStack( Lua *L, const RString &sType, int iArg ); + + static bool Equal( lua_State *L ); + static int PushEqual( lua_State *L ); }; template -class Luna +class Luna: public LuaBinding { protected: typedef Type T; @@ -27,74 +45,28 @@ protected: binding_t *mfunc; }; -public: - Luna() + void Register( Lua *L, int iMethods, int iMetatable ) { - LUA->Register( Register ); - } - - static void Register( Lua *L ) - { - /* Create the methods table, if it doesn't already exist. */ - LuaBinding::CreateMethodsTable( L, m_sClassName ); - - int methods = lua_gettop( L ); - - /* Create a metatable for the userdata objects. */ - luaL_newmetatable( L, m_sClassName ); - int metatable = lua_gettop( L ); - - // We use the metatable to determine the type of the table, so don't - // allow it to be changed. - lua_pushstring( L, "(hidden)" ); - lua_setfield( L, metatable, "__metatable" ); - - lua_pushvalue( L, methods ); - lua_setfield( L, metatable, "__index" ); - lua_pushcfunction( L, tostring_T ); - lua_setfield( L, metatable, "__tostring" ); - - lua_pushcfunction( L, equal ); - lua_setfield( L, metatable, "__eq" ); + lua_setfield( L, iMetatable, "__tostring" ); // fill method table with methods from class T - for( unsigned i=0; s_pvMethods && i < s_pvMethods->size(); i++ ) + for( unsigned i=0; i < m_aMethods.size(); i++ ) { - const RegType *l = &(*s_pvMethods)[i]; + const RegType *l = &m_aMethods[i]; lua_pushstring( L, l->szName ); lua_pushlightuserdata( L, (void*) l->mfunc ); lua_pushcclosure( L, thunk, 1 ); - lua_settable( L, methods ); + lua_settable( L, iMethods ); } - - /* Create a metatable for the methods table. */ - lua_newtable( L ); - int methods_metatable = lua_gettop( L ); - - // Hide the metatable. - lua_pushstring( L, "(hidden)" ); - lua_setfield( L, methods_metatable, "__metatable" ); - - if( strcmp(m_sBaseClassName, "none") ) - { - // If this type has a base class, set the __index of this type - // to the base class. If the base class doesn't exist, we probably - // were called before the Register() of the base class; we'll fill - // it in when we get to it. - LuaBinding::CreateMethodsTable( L, m_sBaseClassName ); - lua_setfield( L, methods_metatable, "__index" ); - } - - lua_pushstring( L, m_sClassName ); - lua_setfield( L, methods_metatable, "type" ); - - /* Set and pop the methods metatable. */ - lua_setmetatable( L, methods ); - - lua_pop( L, 2 ); // drop metatable and method table } +public: + virtual const RString &GetClassName() const { return m_sClassName; } + virtual const RString &GetBaseClassName() const { return m_sBaseClassName; } + static RString m_sClassName; + static RString m_sBaseClassName; + // Get userdata from the Lua stack and return a pointer to T object. static T *check( lua_State *L, int narg, bool bIsSelf = false ) { @@ -111,34 +83,20 @@ public: static T *get( lua_State *L, int narg ) { - /* The stack has a userdata or a table. If it's a table, look up the associated userdata. */ - if( lua_istable(L, narg) ) - { - return (T *) LuaBinding::GetUserdataFromGlobalTable( L, m_sClassName, narg ); - } - else if( lua_isuserdata(L, narg) ) - { - void **pData = (void **) lua_touserdata( L, narg ); - return (T *) *pData; - } - else - return NULL; + return (T *) GetPointerFromStack( L, m_sClassName, narg ); } /* Push a table or userdata for the given object. This is called on the * base class, so we pick up the instance of the base class, if any. */ - static void PushObject( Lua *L, T* p ); + static void PushObject( Lua *L, const RString &sDerivedClassName, T* p ); - static void AddMethod( const char *szName, int (*pFunc)(T *p, lua_State *L) ) +protected: + void AddMethod( const char *szName, int (*pFunc)(T *p, lua_State *L) ) { - if( s_pvMethods == NULL ) - s_pvMethods = new RegTypeVector; - RegType r = { szName, pFunc }; - s_pvMethods->push_back(r); + m_aMethods.push_back(r); } - private: // member function dispatcher static int thunk( Lua *L ) @@ -150,48 +108,9 @@ private: binding_t *pFunc = (binding_t *) lua_touserdata( L, lua_upvalueindex(1) ); return pFunc( obj, L ); // call member function } - - /* Two objects are equal if the underlying object is the same. */ - static int equal( lua_State *L ) - { - int iType = lua_type( L, 1 ); - if( lua_type(L, 2) != iType ) - { - lua_pushboolean( L, false ); - return 1; - } - /* Use the regular method for tables. If an object's table is - * kept around after the actual object has been destroyed, the - * table is still valid, and the pointer no longer exists. */ - if( iType == LUA_TTABLE ) - { - int iEqual = lua_rawequal( L, 1, 2 ); - lua_pushboolean( L, iEqual ); - return 1; - } + vector m_aMethods; - if( !LuaBinding::CheckLuaObjectType(L, 1, m_sClassName) || - !LuaBinding::CheckLuaObjectType(L, 2, m_sClassName) ) - { - lua_pushboolean( L, false ); - } - else - { - const void *pData1 = get( L, 1 ); - const void *pData2 = get( L, 2 ); - lua_pushboolean( L, pData1 != NULL && pData1 == pData2 ); - } - - return 1; - } - - typedef vector RegTypeVector; - static RegTypeVector *s_pvMethods; - - static const char *m_sClassName; - static const char *m_sBaseClassName; - static int tostring_T( lua_State *L ) { char buff[32]; @@ -220,24 +139,21 @@ public: * Lua table). Derived classes simply call the base class's Push function, * specifying a different class name, so they don't need to know about it. */ #define LUA_REGISTER_INSTANCED_BASE_CLASS( T ) \ - LUA_REGISTER_CLASS_BASIC( T, none ) \ - template<> void Luna::PushObject( Lua *L, T* p ) { p->m_pLuaInstance->PushSelf( L ); } \ - void T::PushSelf( lua_State *L ) { Luna::PushObject( L, this ); LuaBinding::ApplyDerivedType( L, #T, this ); } + LUA_REGISTER_CLASS_BASIC( T, T ) \ + template<> void Luna::PushObject( Lua *L, const RString &sDerivedClassName, T* p ) { p->m_pLuaInstance->PushSelf( L ); LuaBinding::ApplyDerivedType( L, sDerivedClassName, p ); } #define LUA_REGISTER_CLASS( T ) \ - LUA_REGISTER_CLASS_BASIC( T, none ) \ - template<> void Luna::PushObject( Lua *L, T* p ) { void **pData = (void **) lua_newuserdata( L, sizeof(void *) ); *pData = p; } \ - void T::PushSelf( lua_State *L ) { Luna::PushObject( L, this ); LuaBinding::ApplyDerivedType( L, #T, this ); } + LUA_REGISTER_CLASS_BASIC( T, T ) \ + template<> void Luna::PushObject( Lua *L, const RString &sDerivedClassName, T* p ) { void **pData = (void **) lua_newuserdata( L, sizeof(void *) ); *pData = p; LuaBinding::ApplyDerivedType( L, sDerivedClassName, p ); } #define LUA_REGISTER_DERIVED_CLASS( T, B ) \ LUA_REGISTER_CLASS_BASIC( T, B ) \ - template<> void Luna::PushObject( Lua *L, T* p ) { Luna::PushObject( L, p ); } \ - void T::PushSelf( lua_State *L ) { Luna::PushObject( L, this ); LuaBinding::ApplyDerivedType( L, #T, this ); } + template<> void Luna::PushObject( Lua *L, const RString &sDerivedClassName, T* p ) { Luna::PushObject( L, sDerivedClassName, p ); } #define LUA_REGISTER_CLASS_BASIC( T, B ) \ - template<> const char *Luna::m_sClassName = #T; \ - template<> const char *Luna::m_sBaseClassName = #B; \ - template<> Luna::RegTypeVector* Luna::s_pvMethods = NULL; \ + template<> RString Luna::m_sClassName = #T; \ + template<> RString Luna::m_sBaseClassName = #B; \ + void T::PushSelf( lua_State *L ) { Luna::PushObject( L, Luna::m_sClassName, this ); } \ static Luna##T registera##T; \ /* Call PushSelf, so we always call the derived Luna::Push. */ \ namespace LuaHelpers { template<> void Push( lua_State *L, T *pObject ) { pObject->PushSelf( L ); } }