Browse Source

Added Object:release. Calling it is the equivalent of removing all Lua-side references to an object and doing a full garbage collection cycle (thus deleting the object from memory if nothing else in LÖVE's code is referencing it).

Any attempt to call a method on the object after it has been released will result in an error. Object:release() returns true if it was successful, or false if not (if it has already been released previously).

--HG--
branch : minor
Alex Szpakowski 8 years ago
parent
commit
37520bd18b

+ 7 - 5
src/common/Variant.cpp

@@ -85,7 +85,8 @@ Variant::Variant(love::Type udatatype, void *userdata)
 	{
 		Proxy *p = (Proxy *) userdata;
 		data.userdata = p->object;
-		p->object->retain();
+		if (p->object)
+			p->object->retain();
 	}
 	else
 		data.userdata = userdata;
@@ -105,7 +106,7 @@ Variant::Variant(const Variant &v)
 {
 	if (type == STRING)
 		data.string->retain();
-	else if (type == FUSERDATA)
+	else if (type == FUSERDATA && data.userdata != nullptr)
 		((love::Object *) data.userdata)->retain();
 	else if (type == TABLE)
 		data.table->retain();
@@ -127,7 +128,8 @@ Variant::~Variant()
 		data.string->release();
 		break;
 	case FUSERDATA:
-		((love::Object *) data.userdata)->release();
+		if (data.userdata != nullptr)
+			((love::Object *) data.userdata)->release();
 		break;
 	case TABLE:
 		data.table->release();
@@ -141,14 +143,14 @@ Variant &Variant::operator = (const Variant &v)
 {
 	if (v.type == STRING)
 		v.data.string->retain();
-	else if (v.type == FUSERDATA)
+	else if (v.type == FUSERDATA && v.data.userdata != nullptr)
 		((love::Object *) v.data.userdata)->retain();
 	else if (v.type == TABLE)
 		v.data.table->retain();
 
 	if (type == STRING)
 		data.string->release();
-	else if (type == FUSERDATA)
+	else if (type == FUSERDATA && v.data.userdata != nullptr)
 		((love::Object *) v.data.userdata)->release();
 	else if (type == TABLE)
 		data.table->release();

+ 38 - 2
src/common/runtime.cpp

@@ -42,7 +42,11 @@ namespace love
 static int w__gc(lua_State *L)
 {
 	Proxy *p = (Proxy *) lua_touserdata(L, 1);
-	p->object->release();
+	if (p->object != nullptr)
+	{
+		p->object->release();
+		p->object = nullptr;
+	}
 	return 0;
 }
 
@@ -72,7 +76,35 @@ static int w__eq(lua_State *L)
 {
 	Proxy *p1 = (Proxy *)lua_touserdata(L, 1);
 	Proxy *p2 = (Proxy *)lua_touserdata(L, 2);
-	luax_pushboolean(L, p1->object == p2->object);
+	luax_pushboolean(L, p1->object == p2->object && p1->object != nullptr);
+	return 1;
+}
+
+static int w__release(lua_State *L)
+{
+	Proxy *p = (Proxy *) lua_touserdata(L, 1);
+	Object *object = p->object;
+
+	if (object != nullptr)
+	{
+		p->object = nullptr;
+		object->release();
+
+		// Fetch the registry table of instantiated objects.
+		luax_getregistry(L, REGISTRY_OBJECTS);
+
+		if (lua_istable(L, -1))
+		{
+			// loveobjects[object] = nil
+			lua_pushlightuserdata(L, object);
+			lua_pushnil(L);
+			lua_settable(L, -3);
+		}
+
+		lua_pop(L, 1);
+	}
+
+	luax_pushboolean(L, object != nullptr);
 	return 1;
 }
 
@@ -329,6 +361,10 @@ int luax_register_type(lua_State *L, love::Type type, const char *name, ...)
 	lua_pushcfunction(L, w__typeOf);
 	lua_setfield(L, -2, "typeOf");
 
+	// Add release
+	lua_pushcfunction(L, w__release);
+	lua_setfield(L, -2, "release");
+
 	va_list fs;
 	va_start(fs, name);
 	for (const luaL_Reg *f = va_arg(fs, const luaL_Reg *); f; f = va_arg(fs, const luaL_Reg *))

+ 9 - 1
src/common/runtime.h

@@ -452,6 +452,9 @@ T *luax_checktype(lua_State *L, int idx, love::Type type)
 		luax_typerror(L, idx, name);
 	}
 
+	if (u->object == nullptr)
+		luaL_error(L, "Cannot use object after it has been released.");
+
 	return (T *)u->object;
 }
 
@@ -513,7 +516,12 @@ T *luax_optmodule(lua_State *L, love::Type type)
 template <typename T>
 T *luax_totype(lua_State *L, int idx, love::Type /*type*/)
 {
-	return (T *)(((Proxy *)lua_touserdata(L, idx))->object);
+	T *o = (T *)(((Proxy *)lua_touserdata(L, idx))->object);
+
+	if (o == nullptr)
+		luaL_error(L, "Cannot use object after it has been released.");
+
+	return o;
 }
 
 Type luax_type(lua_State *L, int idx);

+ 6 - 0
src/modules/image/wrap_ImageData.lua

@@ -161,6 +161,7 @@ local _getWidth = ImageData.getWidth
 local _getHeight = ImageData.getHeight
 local _getDimensions = ImageData.getDimensions
 local _getFormat = ImageData.getFormat
+local _release = ImageData.release
 
 -- Table which holds ImageData objects as keys, and information about the objects
 -- as values. Uses weak keys so the ImageData objects can still be GC'd properly.
@@ -270,5 +271,10 @@ function ImageData:getFormat()
 	return objectcache[self].format
 end
 
+function ImageData:release()
+	objectcache[self] = nil
+	return _release(self)
+end
+
 -- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string.
 --)luastring"--"

+ 1 - 1
src/modules/math/wrap_RandomGenerator.cpp

@@ -126,7 +126,7 @@ static FFI_RandomGenerator ffifuncs =
 	[](Proxy *p) -> double // random()
 	{
 		// FIXME: We need better type-checking...
-		if (p == nullptr || !typeFlags[p->type][MATH_RANDOM_GENERATOR_ID])
+		if (p == nullptr || p->object == nullptr || !typeFlags[p->type][MATH_RANDOM_GENERATOR_ID])
 			return 0.0;
 
 		RandomGenerator *rng = (RandomGenerator *) p->object;

+ 2 - 0
src/modules/math/wrap_RandomGenerator.lua

@@ -72,6 +72,8 @@ local ffifuncs = ffi.cast("FFI_RandomGenerator *", ffifuncspointer)
 -- Overwrite some regular love.math functions with FFI implementations.
 
 function RandomGenerator:random(l, u)
+	-- TODO: This should ideally be handled inside ffifuncs.random
+	if self == nil then error("bad argument #1 to 'random' (RandomGenerator expected, got no value)", 2) end
 	local r = tonumber(ffifuncs.random(self))
 	return getrandom(r, l, u)
 end

+ 6 - 0
src/modules/sound/wrap_SoundData.lua

@@ -46,6 +46,7 @@ local _getSampleCount = SoundData.getSampleCount
 local _getSampleRate = SoundData.getSampleRate
 local _getChannels = SoundData.getChannels
 local _getDuration = SoundData.getDuration
+local _release = SoundData.release
 
 -- Table which holds SoundData objects as keys, and information about the objects
 -- as values. Uses weak keys so the SoundData objects can still be GC'd properly.
@@ -133,5 +134,10 @@ function SoundData:getDuration()
 	return objectcache[self].duration
 end
 
+function SoundData:release()
+	objectcache[self] = nil
+	return _release(self)
+end
+
 -- DO NOT REMOVE THE NEXT LINE. It is used to load this file as a C++ string.
 --)luastring"--"