Skip to content

Instantly share code, notes, and snippets.

@Quit
Created September 7, 2012 00:09
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Quit/3661692 to your computer and use it in GitHub Desktop.
Save Quit/3661692 to your computer and use it in GitHub Desktop.
Current version of the lua wrapper, + debugging stuff and a bit lacking documentation
#pragma once
#include "lua/lua.h"
#include "lua/lauxlib.h"
#include <cassert>
namespace gle {
//! Wrapper base class - no need to initiate those.
class LuaObjectBase {
private:
LuaObjectBase(const LuaObjectBase& b) {}
protected:
LuaObjectBase() {}
public:
virtual ~LuaObjectBase() {}
};
//! The wrapper. Holds an object of type T.
template<typename T> class LuaObject : public LuaObjectBase {
T* m_ptr;
bool m_weak, m_valid;
lua_State* m_L;
private:
LuaObject(const LuaObject<T>& b) {}
public:
//! The name of this class - used in errors. Has to be the same as the metatable one.
static const char* NAME;
//! Creates a new LuaObject.
//! if "weak" is set to "false", the object will be deleted upon invalidation.
LuaObject(lua_State* L, T& object, bool weak = true):
m_L(L), m_ptr(&object), m_weak(weak), m_valid(true)
{
}
//! Destructor. o7 wrapper.
virtual ~LuaObject() {
// If we aren't weak and valid, invalidate us.
// This will erase any lua information stored about this object.
if (!m_weak)
invalidate();
// otherwise, if we are still valid, remove the ptr reference.
else if (m_valid) {
// Remove the wrapper from the wrapper list.
// -0, +1 ; X + 1
lua_pushliteral(m_L, "LuaObjects");
lua_rawget(m_L, LUA_REGISTRYINDEX);
if (lua_isnil(m_L, -1))
luaL_error(m_L, "LuaObjects registry does not exist - did you forget to call LUA_OBJECT_OPEN?");
// -0, +2 ; X + 3
lua_pushlightuserdata(m_L, m_ptr);
lua_pushnil(m_L);
// -2, +0 ; X + 1
lua_rawset(m_L, -3);
// -1, +0 ; X
lua_pop(m_L, 1);
/*
// Simply remove the wrapper for now.
// -0, +1 ; X + 1
lua_pushlightuserdata(m_L, m_ptr);
// -1, +1 ; X + 1
lua_rawget(m_L, LUA_REGISTRYINDEX);
// -0, +1 ; X + 2
lua_pushliteral(m_L, "__wrapper");
// -0, +1 ; X + 3
lua_pushnil(m_L);
// -2, +0 ; X + 1
lua_rawset(m_L, -3);
// -1, +0 ; X
lua_pop(m_L, 1);
*/
}
}
//! Returns the object.
//! WARNING: If we aren't valid, this will be an ACCESS VIOLATION.
T* object() { return m_ptr; }
//! Accesses the object.
//! WARNING: If we aren't valid, this will be an ACCESS VIOLATION.
T* operator->() { return m_ptr; }
//! Invalidates this wrapper.
void invalidate() {
if (!m_valid)
return;
// First things first, INVALIDATE.
m_valid = false;
// If we aren't weak, kill our object now.
if (!m_weak) {
// We still want to do stuff with the wrapper though - assure that we still exist.
// Set our value to a lightuserdata.
lua_pushliteral(m_L, "LuaObjects");
lua_rawget(m_L, LUA_REGISTRYINDEX);
//lua_pushlightuserdata(m_L, m_ptr);
//lua_pushlightuserdata(m_L, this);
//lua_rawset(m_L, -3);
delete m_ptr;
lua_pushlightuserdata(m_L, m_ptr);
lua_pushnil(m_L);
lua_rawset(m_L, -3);
lua_pop(m_L, 1);
}
// Get rid of our userdata.
lua_pushlightuserdata(m_L, m_ptr);
// Push NIL.
lua_pushnil(m_L);
lua_rawset(m_L, LUA_REGISTRYINDEX);
// Get the wrapper table.
// -0, +1 ; X + 1
lua_pushliteral(m_L, "LuaObjects");
lua_rawget(m_L, LUA_REGISTRYINDEX);
// Delete the entry for this userdata.
// -0, +0 ; X + 1
lua_pushlightuserdata(m_L, m_ptr);
lua_pushnil(m_L);
lua_rawset(m_L, -3);
// Pop.
// -1, +0 ; X + 1
lua_pop(m_L, 1);
// Officially dead now.
m_ptr = NULL;
}
//! Returns if this object is still valid.
bool valid() const { return m_valid; }
//! Returns the state.
lua_State* state() const { return m_L; }
};
//! Tries to convert a lua value to a LuaObject of that type.
//! May throw an argerror.
//! [ -0, +0, e ]
template<typename T> LuaObject<T>& getObject(lua_State* L, int index) {
if (!lua_isuserdata(L, index)) {
const char *msg = lua_pushfstring(L, "%s expected, got %s", LuaObject<T>::NAME, luaL_typename(L, index));
luaL_argerror(L, index, msg);
}
LuaObjectBase* base = static_cast<LuaObjectBase*>(lua_touserdata(L, index));
LuaObject<T>* object = dynamic_cast<LuaObject<T>*>(base);
if (object == NULL) {
const char *msg = lua_pushfstring(L, "%s expected, got %s", LuaObject<T>::NAME, objectName(L, index).c_str());
luaL_argerror(L, index, msg);
}
return *object;
}
//! Pushes the wrapper for this object onto the stack.
//! Should the object already exist within lua, it is simply pushed by value.
//! Returns the wrapper.
//! [ -0, +1, e ]
//! `weak': If set to "false", invalidating the object will delete it from C++.
template<typename T> LuaObject<T>& pushObject(lua_State* L, T& object, bool weak = true) {
// COMMENT FORMAT:
// -popping, +pushing ; index after operation
// Start:
// -0, +0 ; X (unknown stack location)
// First step: Check if we already exist.
// Push the pointer to lua.
// -0, +1 ; X+1
lua_pushlightuserdata(L, &object);
// Get the registry.
// -1, +1 ; X+1
lua_rawget(L, LUA_REGISTRYINDEX);
// Is the registry entry available?
if (lua_isnil(L, -1)) {
// Negative - create one.
// Pop nil.
// -1, 0 ; X
lua_pop(L, 1);
// Make a key.
// -0, +1 ; X+1
lua_pushlightuserdata(L, &object);
// Create a new table.
// -0, +1 ; X+2
lua_newtable(L);
// Set it.
// -2, +0 ; X
lua_rawset(L, LUA_REGISTRYINDEX);
// Push the table again if we are weak
if (weak) {
lua_pushlightuserdata(L, &object);
lua_rawget(L, LUA_REGISTRYINDEX);
}
}
else if (!weak) {
// We are at X + 1. The registry table is at X + 1.
// Now that we know that the registry index exists, pop it.
lua_pop(L, 1);
}
// Try to get the object.
// NEW CODE. Get LuaObjects.
// -0, +1 ; X + Y + 1
lua_pushliteral(L, "LuaObjects");
lua_rawget(L, LUA_REGISTRYINDEX);
if (lua_isnil(L, -1))
luaL_error(L, "LuaObjects registry does not exist - did you forget to call LUA_OBJECT_OPEN?");
// Get the object for that object. (hurr.)
// -0, +1 ; X + Y + 2
lua_pushlightuserdata(L, &object);
lua_rawget(L, -2);
// nil?
if (lua_isnil(L, -1)) {
// Pop.
// -1, +0 ; X + Y + 1
lua_pop(L, 1);
// Push the key - again.
// -0, +1 ; X + Y + 2
lua_pushlightuserdata(L, &object);
// Push the space for the wrapper and create it.
// -0, +1 ; X + Y + 3
LuaObject<T>* lObject = new (lua_newuserdata(L, sizeof(LuaObject<T>))) LuaObject<T>(L, object, weak);
// Set the metatable for the wrapper.
// -0, +0 ; X + Y + 3
luaL_setmetatable(L, LuaObject<T>::NAME);
// Associate _wrapper with the object.
// -2, +0 ; X + Y + 1
lua_rawset(L, -3);
// We have to get the object again - we can't simply copy it because rawset is stoopid.
// -0, +1 ; X + Y + 2
lua_pushlightuserdata(L, &object);
lua_rawget(L, -2);
// the wrapper is now at X + Y + 2 = -1
// If we were weak, we have one element above this one - the registry table.
if (weak) {
lua_pushliteral(L, "__wrapper");
lua_pushvalue(L, -2);
// -1 is the wrapper, -2 is "__wrapper", -3 is the wrapper, -4 is LuaObjects, -5 is the object registry.
lua_rawset(L, -5);
// -1 is the wrapper, -2 LuaObjects, -3 the object registry
lua_remove(L, -3);
}
// Remove LuaObjects.
// -1, +0 ; X + 1
lua_remove(L, -2);
return *lObject;
}
// The wrapper exists and is now at X + Y + 2 = -1.
else {
// Remove the registry table.
// -1, +0 ; X + Y + 1
lua_remove(L, -2);
// If we were weak, do magic.
if (weak) {
// -1 is the wrapper, -2 the LuaObjects table
lua_pushlightuserdata(L, &object);
lua_pushvalue(L, -2);
lua_rawset(L, -4);
}
// Simply cast the object - if it's userdata. Lightuserdata?
//if (lua_islightuserdata(L, -1))
//return **static_cast<LuaObject<T>**>(lua_touserdata(L, -1));
// WARNING: This is assuming that nobody messes around with the registry.
// If somebody does, you're going to have a bad time.
// Return the userdata.
// -0, +0 ; X + 1
return *static_cast<LuaObject<T>*>(lua_touserdata(L, -1));
}
}
//! Frees any space that wrappers may hold.
//! If there is a chance that this object was passed to lua, it is recommended
//! to call this function in the dtor to properly clean up.
//! `markInvalid': In case the object isn't valid anymore (ex. you are calling freeObject in a destructor),
//! it is necessary to inform the wrapper to NOT delete the element - only to remove its data.
template<typename T> void freeObject(lua_State* L, T& object) {
// Get the wrapper.
lua_pushliteral(L, "LuaObjects");
lua_rawget(L, LUA_REGISTRYINDEX);
lua_pushlightuserdata(L, &object);
lua_rawget(L, -2);
// Already removed...?
if (lua_isnil(L, -1)) {
// Make sure by also deleting the entry.
lua_pushlightuserdata(L, &object);
lua_pushnil(L);
lua_rawset(L, LUA_REGISTRYINDEX);
lua_pop(L, 1);
return;
}
// That object is to be terminated.
// Invalidate it.
LuaObject<T>& lObject = getObject<T>(L, -1);
// Clean up. LuaObjects and the object.
lua_pop(L, 2);
// Terminate the object.
lObject.invalidate();
}
//! A function that can be added to __cfuncs. Returns if the wrapper is still valid.
template<typename T> int LOC_IsValid(lua_State* L) {
// Get the wrapper.
LuaObject<T>& object = getObject<T>(L, 1);
lua_pushboolean(L, object.valid() ? 1 : 0);
return 1;
}
//! The __index function to be used with LuaObjects inside their metatable.
template<typename T> int LOM__index(lua_State* L) {
// 1: the userdata that was indexed
// 2: the string/number/whatsoever that was indexed
// Get the wrapper.
// -0, +0 ; 2
LuaObject<T>& object = getObject<T>(L, 1);
// Object was invalid => we return nil.
if (!object.valid()) {
lua_pushnil(L);
return 1;
}
//// Assure it's not '__wrapper'.
//// -0, +1 ; 3
//lua_pushliteral(L, "__wrapper");
//// -0, +0 ; 3
//if (lua_compare(L, 2, 3, LUA_OPEQ))
// luaL_error(L, "attempt to access __wrapper on LuaObject<%s>!", LuaObject<T>::NAME);
//// Pop _wrapper again.
//// -1, +0 ; 2
//lua_pop(L, 1);
// Push the lightuserdata.
// -0, +1 ; 3
lua_pushlightuserdata(L, object.object());
// Get the registry entry for this object.
// -1, +1 ; 3
lua_rawget(L, LUA_REGISTRYINDEX);
// nil?
if (lua_isnil(L, -1))
luaL_error(L, "LuaObject<%s> has no valid registry entry anmyore", LuaObject<T>::NAME);
// Check if there's a field in the registry entry for it.
// Reuse the string.
// -0, +1 ; 4
lua_pushvalue(L, 2);
// Look it up in the registry entry.
// -1, +1 ; 4
lua_rawget(L, -2);
// not nil? return it.
if (!lua_isnil(L, -1))
return 1;
// Get the metatable.
// -0, +1 ; 5
if (!lua_getmetatable(L, 1))
luaL_error(L, "LuaObject<%s> has no metatable anymore", LuaObject<T>::NAME);
// Search within "__cdef"
// -0, +1 ; 6
lua_pushliteral(L, "__cdef");
// -1, +1 ; 6
lua_rawget(L, -2);
if (lua_isnil(L, -1))
luaL_error(L, "LuaObject<%s> has no __cdef anymore", LuaObject<T>::NAME);
// Push the index, again.
// -0, +1 ; 7
lua_pushvalue(L, 2);
// Rawget
// -1, +1 ; 7
lua_rawget(L, -2);
// Return whatever this may is now.
return 1;
}
//! The __newindex to be used with LuaObjects inside their metatable.
template<typename T> int LOM__newindex(lua_State* L) {
// Get the object.
// -0, +0 ; 3
LuaObject<T>& object = getObject<T>(L, 1);
if (!object.valid())
luaL_error(L, "attempt to set index on invalid LuaObject<%s>!", LuaObject<T>::NAME);
//// Make sure it isn't __wrapper.
//// -0, +1 ; 4
//lua_pushliteral(L, "__wrapper");
//if (lua_compare(L, 2, -1, LUA_OPEQ))
// luaL_error(L, "attempt to set __wrapper on LuaObject<%s>!", LuaObject<T>::NAME);
//// Pop it.
//// -1, 0 ; 3
//lua_pop(L, 1);
// 1: object
// 2: key
// 3: value
// Push the userdata to the registry.
// -0, +1 ; 4
lua_pushlightuserdata(L, object.object());
// Get the entry.
// -1, +1 ; 4
lua_rawget(L, LUA_REGISTRYINDEX);
// Copy the key
// -0, +1 ; 5
lua_pushvalue(L, 2);
// Copy the value.
// -0, +1 ; 6
lua_pushvalue(L, 3);
// set
lua_rawset(L, -3);
return 0;
}
//! The __gc to be used with LuaObjects inside their metatable.
template<typename T> int LOM__gc(lua_State* L) {
// Get the object.
// -0, +0 ; 1
LuaObject<T>& object = getObject<T>(L, 1);
// Resurrect the item for now
if (object.valid()) {
lua_pushliteral(L, "LuaObjects");
lua_rawget(L, LUA_REGISTRYINDEX);
assert(lua_istable(L, -1));
lua_pushlightuserdata(L, object.object());
lua_pushvalue(L, 1);
lua_rawset(L, -3);
lua_pop(L, 1);
}
// Call the destructor.
object.~LuaObject();
// This will have invalidated the object already.
return 0;
}
//! Default __tostring function. Optional; has to be included in __cdef manually.
template<typename T> int LOM__tostring(lua_State* L) {
// Get the name. Simple as that.
lua_pushstring(L, LuaObject<T>::NAME);
return 1;
}
//! Adds tostring for this class - assumes that the last index (-1) is the opened metatable.
# define LUA_OBJECT_ADD_TOSTRING(L, _class) lua_pushliteral(L, "__tostring"); lua_pushcfunction(L, gle::LOM__tostring< _class >); lua_rawset(L, -3);
//! Returns the name of an userdata object.
std::string objectName(lua_State* L, int idx) {
// Make sure it's an userobject.
if (!lua_isuserdata(L, idx))
luaL_error(L, "cannot determine name of non-userdata");
// Get its metatable.
// +1 ; 1
if (!lua_getmetatable(L, idx))
luaL_error(L, "userdata has no metatable");
// Get __name.
// +1 ; 2
lua_pushliteral(L, "__name");
lua_rawget(L, -2);
std::string result(lua_tostring(L, -1));
// Pop.
lua_pop(L, 2);
return result;
}
//! Pushes the method for that object and its wrapper onto the stack.
//! If available, it will search for the lua-overwritten-method first.
//! If that is not available, it will search in the metatable's cdef for one.
//! Pushes the function onto the stack if available, otherwise nothing.
//! If either the lua-defined or the metatable aren't functions, an exception is thrown.
//! If neither are defined or the object hasn't been exported to lua yet, this function returns false.
//! TODO: This function has several, severe flaws:
//! If called on non-weak objects and the wrapper has already been garbage collected, there is a chance that this fails.
//! as in "lua error" - I think that should return false. Definitely ...
bool objectCall(lua_State* L, const void* object, const char* functionName) {
// Get the registry.
// -0, +1 ; X + 1
lua_pushlightuserdata(L, const_cast<void*>(object));
lua_rawget(L, LUA_REGISTRYINDEX);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
return false;
}
// Check if there's a field called like that.
// 0, +1 ; X + 2
lua_pushstring(L, functionName);
lua_rawget(L, -2);
if (!lua_isnil(L, -1)) {
// Get LuaObjects. X + 3
lua_pushliteral(L, "LuaObjects");
lua_rawget(L, LUA_REGISTRYINDEX);
if (lua_isnil(L, -1))
luaL_error(L, "LuaObjects registry does not exist - did you forget to call LUA_OBJECT_OPEN?");
// Get the Wrapper. X + 4
lua_pushlightuserdata(L, const_cast<void*>(object));
lua_rawget(L, -2);
if (lua_isnil(L, -1))
luaL_error(L, "objectCall failed - object does not exist anymore");
// Function? -1 = wrapper, -2 = LuaObjects, -3 = function
if (lua_isfunction(L, -3)) {
lua_remove(L, -2);
// Now that we have the function, push the object.
return true;
}
luaL_error(L, "objectCall in " LUA_QS " failed: lua defined " LUA_QS " is not a function", objectName(L, -1).c_str(), functionName);
}
// It's nil, drop it.
lua_pop(L, 1);
// X + 1; the registry table.
// Get the wrapper.
// -1, +0 ; X
lua_pop(L, 1);
// Get the wrapper from LuaObjects.
// -0, +2 ; X + 2
lua_pushliteral(L, "LuaObjects");
lua_rawget(L, LUA_REGISTRYINDEX);
if (lua_isnil(L, -1))
luaL_error(L, "LuaObjects registry does not exist - did you forget to call LUA_OBJECT_OPEN?");
lua_pushlightuserdata(L, const_cast<void*>(object));
lua_rawget(L, -2);
if (lua_isnil(L, -1))
luaL_error(L, "objectCall failed - object does not exist anymore");
// X + 2; the __wrapper.
// Get the metatable.
if (!lua_getmetatable(L, -1))
luaL_error(L, "objectCall failed - object does not have a metatable anymore!");
// X + 3; the metatable of -wrapper.
// Get __cdef
lua_pushliteral(L, "__cdef");
lua_rawget(L, -2);
if (lua_isnil(L, -1))
luaL_error(L, "objectCall failed - " LUA_QS " has no cdefs anymore", objectName(L, -3));
// X + 4; __cdef.
lua_pushstring(L, functionName);
lua_rawget(L, -2);
// X + 5; the function.
if (!lua_isnil(L, -1)) {
if (lua_isfunction(L, -1)) {
// Move X + 5 to X + 1
lua_replace(L, -5);
// X+4, X+3, X+2, X+1 exist. X+1 is the function, X+2 the wrapper; therefore, delete X+4 and X+3 (metatable of wrapper and cdef)
lua_pop(L, 2);
return true;
}
// Get the name as X+6
lua_getfield(L, -3, "__name");
luaL_error(L, "objectCall in " LUA_QS " failed: C defined " LUA_QS " is not a function!", lua_tostring(L, -1), functionName);
}
// Clean up.
lua_pop(L, 5);
return false;
}
//! Registers a name for LuaObject for that class.
# define LUA_OBJECT_REGISTER(_class, _name) const char* gle::LuaObject< _class >::NAME = _name ;
//! Returns the name of a LuaObject for that class.
//! For objects, use the objectName() function.
# define LUA_OBJECT_NAME(_class) gle::LuaObject < _class >::NAME
//! Opens LuaObject (like "normal" libraries). Needs to be called before anything with objects is done.
# define LUA_OBJECT_OPEN(L) gle::_LUA_OBJECT_OPEN(L);
void _LUA_OBJECT_OPEN(lua_State* L) {
lua_pushliteral(L, "LuaObjects");
// The table that will contain the wrappers.
// -0, +1 ; 2
lua_newtable(L);
// The metatable.
// -0, +1 ; 3
lua_newtable(L);
// __mode
// -0, +1 ; 4
lua_pushliteral(L, "__mode");
// kv
// -0, +1 ; 5
lua_pushliteral(L, "kv");
// set the metatable's __mode to kv. -- in theory, just having v would be enough, as it's lightuserdata => userdata.
// -2, +0 ; 3
lua_rawset(L, -3);
// 3; the new metatable. set it. (3 metatable, 2 the table we're using for wrappers, 1 the key)
// -1, +0 ; 2
lua_setmetatable(L, -2);
// 2 the table we're using, 1 the key LuaObjects
// -2, +0 ; 0
lua_rawset(L, LUA_REGISTRYINDEX);
}
//! Registers a new metatable for this class. This is requiring that you LUA_OBJECT_REGISTERed that class before.
//! It will also fill all cfunctions you pass in cdefReg to be used, along with IsValid.
//! This function leaves the metatable on the stack - for you to use! For free!
// [ -0, +1, - ]
template<typename T> void registerObject(lua_State* L, const luaL_Reg *cdefReg) {
// Create the metatable.
// -0, +1 ; X + 1
luaL_newmetatable(L, LUA_OBJECT_NAME(T));
// Fill the metatable.
// __index
// -0, +0 ; X + 1
lua_pushliteral(L, "__index");
lua_pushcfunction(L, LOM__index<T>);
lua_rawset(L, -3); // set __index
// __newindex
// -0, +0 ; X + 1
lua_pushliteral(L, "__newindex");
lua_pushcfunction(L, LOM__newindex<T>);
lua_rawset(L, -3); // set __newindex
// __gc
// -0, +0 ; X + 1
lua_pushliteral(L, "__gc");
lua_pushcfunction(L, LOM__gc<T>);
lua_rawset(L, -3); // set __gc
// __name
// -0, +0 ; X + 1
lua_pushliteral(L, "__name");
lua_pushstring(L, LUA_OBJECT_NAME(T));
lua_rawset(L, -3); // set __name
// Create the name already
// -0, +1 ; X + 2
lua_pushliteral(L, "__cdef");
// -0, +1 ; X + 3
// Import the lib.
luaL_newlib(L, cdefReg);
// Import IsValid
// -0, +0 ; X + 3
lua_pushliteral(L, "IsValid");
lua_pushcfunction(L, LOC_IsValid<T>);
lua_rawset(L, -3); // set IsValid
// Push the literal "BaseClass"
// -0, +1 ; X + 4
lua_pushliteral(L, "BaseClass");
// -0, +1 ; X + 5
lua_pushvalue(L, -2); // push the metatable, kinda hacky but it works
// -2, +0 ; X + 3
lua_rawset(L, -3); // set BaseClass
// -2, +0 ; X + 1
lua_rawset(L, -3); // set __cdef
// Leave the metatable on the stack.
}
//! Creates a new function to be used as method for objects of class `_class' with name `name'.
//! In usual lua manner, the lua_State* is called L, the wrapper self.
//! For example: LUA_OBJECT_FUNCTION(
# define LUA_OBJECT_FUNCTION(_class, name) int name (lua_State* L) { gle::LuaObject< _class >& self = gle::getObject< _class >(L, 1); /##/
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment