Browse Source

Allow Fixture:setUserData and Fixture:getUserData to be called in coroutines (resolves issue #850.)

Bad things will still happen if you call them from a love Thread which didn't create the World and the Fixture object. Don't do it!
Alex Szpakowski 11 years ago
parent
commit
2a19a4444e
3 changed files with 48 additions and 22 deletions
  1. 16 6
      src/common/Reference.cpp
  2. 17 1
      src/common/Reference.h
  3. 15 15
      src/modules/physics/box2d/Fixture.cpp

+ 16 - 6
src/common/Reference.cpp

@@ -64,21 +64,31 @@ void Reference::unref()
 	}
 }
 
-void Reference::push()
+void Reference::push(lua_State *newL)
 {
 	if (idx != LUA_REFNIL)
 	{
-		luax_insist(L, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME);
-		lua_rawgeti(L, -1, idx);
-		lua_remove(L, -2);
+		luax_insist(newL, LUA_REGISTRYINDEX, REFERENCE_TABLE_NAME);
+		lua_rawgeti(newL, -1, idx);
+		lua_remove(newL, -2);
 	}
 	else
-		lua_pushnil(L);
+		lua_pushnil(newL);
 }
 
-lua_State *Reference::getL()
+void Reference::push()
+{
+	push(L);
+}
+
+lua_State *Reference::getL() const
 {
 	return L;
 }
 
+void Reference::setL(lua_State *newL)
+{
+	L = newL;
+}
+
 } // love

+ 17 - 1
src/common/Reference.h

@@ -63,6 +63,14 @@ public:
 	 **/
 	void unref();
 
+	/**
+	 * Pushes the referred value onto the stack of a different coroutine
+	 * in the same main Lua state.
+	 * THIS SHOULD NOT BE USED FOR DIFFERENT LUA STATES (created with
+	 * luaL_newstate)! Only with different coroutines!
+	 **/
+	void push(lua_State *newL);
+
 	/**
 	 * Pushes the referred value onto the stack.
 	 **/
@@ -72,7 +80,15 @@ public:
 	 * Gets the Lua state associated with this
 	 * reference.
 	 **/
-	lua_State *getL();
+	lua_State *getL() const;
+
+	/**
+	 * Associates a new Lua state with this reference.
+	 * THIS IS DANGEROUS! It is only designed to be
+	 * used with different coroutines from the same
+	 * main Lua state!
+	 **/
+	void setL(lua_State *newL);
 
 private:
 

+ 15 - 15
src/modules/physics/box2d/Fixture.cpp

@@ -39,10 +39,10 @@ namespace box2d
 
 Fixture::Fixture(Body *body, Shape *shape, float density)
 	: body(body)
-	, fixture(NULL)
+	, fixture(nullptr)
 {
 	data = new fixtureudata();
-	data->ref = 0;
+	data->ref = nullptr;
 	b2FixtureDef def;
 	def.shape = shape->shape;
 	def.userData = (void *)data;
@@ -65,13 +65,10 @@ Fixture::Fixture(b2Fixture *f)
 
 Fixture::~Fixture()
 {
-	if (data->ref != 0)
+	if (data != nullptr)
 		delete data->ref;
 
 	delete data;
-	data = NULL;
-
-	fixture = NULL;
 }
 
 Shape::Type Fixture::getType() const
@@ -127,14 +124,14 @@ Body *Fixture::getBody() const
 Shape *Fixture::getShape() const
 {
 	if (!fixture->GetShape())
-		return NULL;
+		return nullptr;
 
 	return new Shape(fixture->GetShape(), false);
 }
 
 bool Fixture::isValid() const
 {
-	return fixture != 0;
+	return fixture != nullptr;
 }
 
 void Fixture::setFilterData(int *v)
@@ -230,21 +227,24 @@ int Fixture::setUserData(lua_State *L)
 {
 	love::luax_assert_argc(L, 1, 1);
 
-	if (data->ref != 0)
+	if (data->ref != nullptr)
 	{
+		// We set the Reference's lua_State to this one before deleting it, so
+		// it unrefs using the current lua_State's stack. This is necessary
+		// if setUserData is called in a coroutine.
+		data->ref->setL(L);
 		delete data->ref;
-		data->ref = 0;
 	}
 
 	data->ref = new Reference(L);
+
 	return 0;
 }
 
 int Fixture::getUserData(lua_State *L)
 {
-	love::luax_assert_argc(L, 0, 0);
-	if (data->ref != 0)
-		data->ref->push();
+	if (data->ref != nullptr)
+		data->ref->push(L);
 	else
 		lua_pushnil(L);
 
@@ -311,10 +311,10 @@ void Fixture::destroy(bool implicit)
 		return;
 	}
 
-	if (!implicit && fixture != 0)
+	if (!implicit && fixture != nullptr)
 		body->body->DestroyFixture(fixture);
 	Memoizer::remove(fixture);
-	fixture = NULL;
+	fixture = nullptr;
 
 	// Box2D fixture destroyed. Release its reference to the love Fixture.
 	this->release();