Crash on deleting physics bodies that are colliding?

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Thustrust
Prole
Posts: 12
Joined: Sun Mar 07, 2010 5:29 am

Crash on deleting physics bodies that are colliding?

Post by Thustrust »

Hopefully posting another topic so soon isn't too annoying, as I have another question...

It seems that when a physics body is deleted, either when explicitly removing it with shape:destroy() and body:destroy() or when the garbage collector removes it, if that body is colliding with something it causes LOVE to freeze and crash. Does anyone have a method for removing a physics object/body that will not potentially cause this crash? There has to be some method I can call on a shape or body that will "prepare" it for deletion so it stops sending collision information or something...

Could this problem potentially be related to the collision callback functions, which I am using?


Edit: Well it turns out that the crash IS due to the collision callback functions, in case anyone was wondering. I think it's bad to mess with box2d within the collision callbacks. I need to find a way to get collision info but only deal with it later...
pekka
Party member
Posts: 206
Joined: Thu Jan 07, 2010 6:48 am
Location: Oulu, Finland
Contact:

Re: Crash on deleting physics bodies that are colliding?

Post by pekka »

Yes, the callbacks are not a safe place to change the physics world. The Box2D library assumes certain things about their behavior, and many of these things are spelled out in the Box2D manual proper. You can also read the LÖVE source for how it deals with contact objects internally, if you're familiar with C++.

Anyway, if you can't do something directly, the usual solution in programming is to defer it to when it is safe to do. Just promise to do it later and put a little note on the object so you'll know to do it.

That is to say, you should have some flags in your physics objects to mark actions you will do to them after the physics update is finished, but before the next one starts. Sometimes you also don't need to destroy your bodies, but can--for example--just let them fall off-screen (or move them yourself there!) and hit the edge of the world, where they will be frozen. This can only be used if you are not constantly creating new ones over a long period of time, though, because that would lead to memory filling up with unused stuff.

LATER ADDITION: Of course, the latter thing only makes sense if you stop drawing the removed object in subsequent frames and set it not to collide with anything in the world. This is kind of troublesome, so there is probably no use in trying it at all.
Thustrust
Prole
Posts: 12
Joined: Sun Mar 07, 2010 5:29 am

Re: Crash on deleting physics bodies that are colliding?

Post by Thustrust »

Thank you very much for the help. Sadly though it looks like there may be a bug of some sort...

After doing a little more testing I was able to determine that calling the "world:setCallbacks()" method in love.load()--even with the callback functions set up properly and completely EMPTY--causes LOVE to lock up and crash when colliding objects are removed. Just as before, explicitly destroying them or letting the garbage collector handle them will both cause a crash if they are colliding with another body when they are removed. This ONLY happens if I have done the setCallbacks method, however. Simply commenting that line out makes the crashes go away.

I did look into just letting the bodies leave the world, but for my purposes it would be much better if I could directly create and destroy physics objects. As I mentioned above, the crash happens even when the callback functions are empty, which is why I'm considering this to be a bug in LOVE rather than an error that I may have made...
pekka
Party member
Posts: 206
Joined: Thu Jan 07, 2010 6:48 am
Location: Oulu, Finland
Contact:

Re: Crash on deleting physics bodies that are colliding?

Post by pekka »

If you can post a minimal program (it's OK to remove the graphics for example) that crashes like your program, we can look at it and see if we can rewrite it safely, or there really is an issue worth putting up on the tracker. I still think the removals can be done safely in a variety of ways, but I cannot test and post any code of my own right now.

Funnily enough, most of the LÖVE programs I've written so far have used a constant amount of physics bodies in them. And not for any particular reason, but just because they work well that way :)
Thustrust
Prole
Posts: 12
Joined: Sun Mar 07, 2010 5:29 am

Re: Crash on deleting physics bodies that are colliding?

Post by Thustrust »

Alright, here's a nice complete LOVE main.lua program that I wrote to illustrate the problem I'm having. As you can see, the line in love.load() buffered by the comment saying "COMMENT THIS LINE OUT AND THE PROGRAM WILL NOT CRASH" is what is causing problems. I'd like to encourage everyone who sees this to run this code and see if they have the same problem.

To replicate the problem, simply left click to make a bunch of random rectangles. Once the rectangles are resting on each other, right click to remove the oldest rectangle. If the aforementioned line is not commented then LOVE will crash, even though the callback functions are completely empty. Comment the line out and everything will work, smooth as butter.

Code: Select all

function love.load()
	math.randomseed(os.time())
	math.random()
	love.graphics.setBackgroundColor(50, 80, 120, 255)
	local width = love.graphics.getWidth()
	local height = love.graphics.getHeight()
	
	--Create a world in Box2d:
	TheWorld = love.physics.newWorld(10000, 10000)
	TheWorld:setGravity(0, 700)
	
	--COMMENT THIS LINE OUT AND THE PROGRAM WILL NOT CRASH!
	TheWorld:setCallbacks(Coll_Add, Coll_Persist, Coll_Rem, Coll_Result)
	-------------------------------------------------------
	
	--Create a floor box for things to rest on:
	TheWorldBody = love.physics.newBody(TheWorld, width/2, height/2, 0, 0)
	TheWorldShape = love.physics.newRectangleShape(TheWorldBody, 0, width/3, width*2, 100, 0)
	
	--Create the table that will hold the rects:
	RectTable = {}
end

function love.update(dt)
	TheWorld:update(dt)
end

function love.draw()
	DrawRects()
	DrawFloor()
	DrawBodyCount(25, 25, 0)
end

-------------EMPTY CALLBACK FUNCTIONS--------
function Coll_Add(a, b, coll)
	--Empty.
end
function Coll_Persist(a, b, coll)
	--Empty.
end
function Coll_Rem(a, b, coll)
	--Empty.
end
function Coll_Result(a, b, coll)
	--Empty.
end
---------------------------------------------

function love.mousepressed(x, y, button)
	if(button == "l") then
		AddRect(x, y, math.random(5, 50), math.random(5, 50), math.random(0, 2*math.pi))
	elseif(button == "r") then
		KillRect(1)		--Removes the oldest existing rectangle.
	end
end

function AddRect(x, y, w, h, rot)	--Adds a new rectangle to RectTable.
	local addTable = {}
		addTable.body = love.physics.newBody(TheWorld, x, y, 1, 1)
		addTable.shape = love.physics.newRectangleShape(addTable.body, 0, 0, w, h, rot)
		addTable.body:setMassFromShapes()
	table.insert(RectTable, addTable)
end
function KillRect(index)	--Removes the rectangle at position 'index' in RecTable.
	local rect = RectTable[index]
	if(rect) then
		rect.shape:destroy()
		rect.body:destroy()
		rect.shape = nil
		rect.body = nil
		table.remove(RectTable, index)
	end
end

function DrawRects()	--This draws the rectangles in the table 'RectTable'
	for i=#RectTable, 1, -1 do
		love.graphics.setColor(255, 255, 255, 255)
		love.graphics.polygon("fill", RectTable[i].shape:getPoints())	--white inside.
		love.graphics.setColor(0, 0, 0, 255)
		love.graphics.polygon("line", RectTable[i].shape:getPoints())	--black outline.
	end
end
function DrawBodyCount(x, y, rot)	--Prints the current number of bodies as seen by Box2d.
	love.graphics.setColor(255, 255, 255, 255)
	local num = (TheWorld:getBodyCount() - 1)		--I'm subracting one from the body count because Box2d counts the world as a body.
	local t = "Bodies: " .. tostring(num)
	love.graphics.print(t, x, y, rot, 1, 1)
end
function DrawFloor()
	love.graphics.setColor(255, 255, 255, 255)
	love.graphics.polygon("fill", TheWorldShape:getPoints())	--white inside.
	love.graphics.setColor(0, 0, 0, 255)
	love.graphics.polygon("line", TheWorldShape:getPoints())	--black outline.
end
Thanks for all your help! Hopefully we can work this out and I can have some callback functions in my LOVE games.
Attachments
main.lua
(2.96 KiB) Downloaded 196 times
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Crash on deleting physics bodies that are colliding?

Post by kikito »

I can't give this a completete test right now, but I think I see one problem.

On the function that creates the rectangles:

Code: Select all

function AddRect(x, y, w, h, rot)   --Adds a new rectangle to RectTable.
   local addTable = {}
      addTable.body = love.physics.newBody(TheWorld, x, y, 1, 1)
      addTable.shape = love.physics.newRectangleShape(addTable.body, 0, 0, w, h, rot)
      addTable.body:setMassFromShapes()
   table.insert(RectTable, addTable)
end
The shape doesn't have any data attached to it. Try adding a line that says

Code: Select all

  addTable.shape:setData(1) 
You can replace the "1" with whatever you want, as long as it is not nil.

Does the error still happen after this?
When I write def I mean function.
pekka
Party member
Posts: 206
Joined: Thu Jan 07, 2010 6:48 am
Location: Oulu, Finland
Contact:

Re: Crash on deleting physics bodies that are colliding?

Post by pekka »

I can confirm that it does crash. On my computer it happens when a rect is killed when it is in contact with something.

What I did was change the right mouse button to kill the last rect in the RectTable by making the call KillRect(#RectTable) in love.mousepressed. I then ran the program several times and found that as long as I deleted teh last rect to be created while it was still falling, the program did not segfault. However, when it touched the ground or another rectangle that touched the ground, it crashed. The reason would be either any contact or indirect contact with the ground; and I can't say which one it is yet.

I'd say this happens consistently. When I have had more time to test this, I will post again.

I can't say anything about Kikito's view yet. I will look at it too.
pekka
Party member
Posts: 206
Joined: Thu Jan 07, 2010 6:48 am
Location: Oulu, Finland
Contact:

Re: Crash on deleting physics bodies that are colliding?

Post by pekka »

Wow, it did not take long to localize the problem. On my end replacing the second to last callback with nil makes the crashes go away. Please try to confirm this. To be clear, the line that sets the callbacks should be:

Code: Select all

TheWorld:setCallbacks(Coll_Add, Coll_Persist, nil, Coll_Result)
With this code in place it does not segfault. How about you?

ADDED LATER: I think the problem is with the Remove callback causing a crash when a shape still in contact with something is destroyed. Here is a workaround that might work. Please report your results.

The way this works is that when a shape is to be destroyed, the code sets it to not collide with nothing at all. Then after the next physics update is run, this shape is no longer in contact with anything, and can then be destroyed without triggering a segfault. And by it working I am really just saying that it did not seem to crash for me.

This seems like an issue that should be on tracker. EDIT: And it si now, because I added it.

Code: Select all

function love.load()
   math.randomseed(os.time())
   math.random()
   love.graphics.setBackgroundColor(50, 80, 120, 255)
   local width = love.graphics.getWidth()
   local height = love.graphics.getHeight()

   --Create a world in Box2d:
   TheWorld = love.physics.newWorld(10000, 10000)
   TheWorld:setGravity(0, 700)

   --COMMENT THIS LINE OUT AND THE PROGRAM WILL NOT CRASH!
   TheWorld:setCallbacks(Coll_Add, Coll_Persist, Coll_Rem, Coll_Result)
   -------------------------------------------------------

   --Create a floor box for things to rest on:
   TheWorldBody = love.physics.newBody(TheWorld, width/2, height/2, 0, 0)
   TheWorldShape = love.physics.newRectangleShape(TheWorldBody, 0, width/3, width*2, 100, 0)

   --Create the table that will hold the rects:
   RectTable = {}
end

function love.update(dt)
   TheWorld:update(dt)
   if GetRid then
   	GetRid.shape:destroy()
	GetRid.body:destroy()
	GetRid = nil
   end
end

function love.draw()
   DrawRects()
   DrawFloor()
   DrawBodyCount(25, 25, 0)
end

-------------EMPTY CALLBACK FUNCTIONS--------
function Coll_Add(a, b, coll)
   --Empty.
end
function Coll_Persist(a, b, coll)
   --Empty.
end
function Coll_Rem(a, b, coll)
   --Empty.
end
function Coll_Result(a, b, coll)
   --Empty.
end
---------------------------------------------

function love.mousepressed(x, y, button)
   if(button == "l") then
      AddRect(x, y, math.random(5, 50), math.random(5, 50), math.random(0, 2*math.pi))
   elseif(button == "r") then
      KillRect(1)      --Removes the oldest existing rectangle.
   end
end

function AddRect(x, y, w, h, rot)   --Adds a new rectangle to RectTable.
   local addTable = {}
      addTable.body = love.physics.newBody(TheWorld, x, y, 1, 1)
      addTable.shape = love.physics.newRectangleShape(addTable.body, 0, 0, w, h, rot)
      addTable.body:setMassFromShapes()
   table.insert(RectTable, addTable)
end
function KillRect(index)   --Removes the rectangle at position 'index' in RecTable.
   local rect = RectTable[index]
   if(rect) then
      rect.shape:setMask(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)
      GetRid = rect
      table.remove(RectTable, index)
   end
end

function DrawRects()   --This draws the rectangles in the table 'RectTable'
   for i=#RectTable, 1, -1 do
      love.graphics.setColor(255, 255, 255, 255)
      love.graphics.polygon("fill", RectTable[i].shape:getPoints())   --white inside.
      love.graphics.setColor(0, 0, 0, 255)
      love.graphics.polygon("line", RectTable[i].shape:getPoints())   --black outline.
   end
end
function DrawBodyCount(x, y, rot)   --Prints the current number of bodies as seen by Box2d.
   love.graphics.setColor(255, 255, 255, 255)
   local num = (TheWorld:getBodyCount() - 1)      --I'm subracting one from the body count because Box2d counts the world as a body.
   local t = "Bodies: " .. tostring(num)
   love.graphics.print(t, x, y, rot, 1, 1)
end
function DrawFloor()
   love.graphics.setColor(255, 255, 255, 255)
   love.graphics.polygon("fill", TheWorldShape:getPoints())   --white inside.
   love.graphics.setColor(0, 0, 0, 255)
   love.graphics.polygon("line", TheWorldShape:getPoints())   --black outline.
end
Last edited by pekka on Mon Mar 08, 2010 4:03 pm, edited 1 time in total.
pekka
Party member
Posts: 206
Joined: Thu Jan 07, 2010 6:48 am
Location: Oulu, Finland
Contact:

Re: Crash on deleting physics bodies that are colliding?

Post by pekka »

Sorry for making three posts in a row. I should have thought this over before posting every little thing I discovered. This is now my final posting on the topic, I hope. I will answer questions, though, if something I wrote is totally unclear. It usually is :)

I have just added this in the issue tracker, because I consider it a bug that should be fixed. It is up to the devs to assign it a priority, though.

Note that my previous message contains a workaround. I will add this workaround into the Wiki at a suitable time, so people can find it easily. I'm afraid the time is not today. I am fairly confident the workaround is good enough, and being a Wiki, it will be improved if it is not.
Thustrust
Prole
Posts: 12
Joined: Sun Mar 07, 2010 5:29 am

Re: Crash on deleting physics bodies that are colliding?

Post by Thustrust »

Thank you so much for your time pekka! I figured there would at least be some way to make shapes not collide with anything before getting rid of them. The "setMask(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)" line will most likely suffice, at least until this is properly fixed (if it ever is). If worst comes to worst then I'll probably be able to find a way to get around needing the remove() callback altogether...

Thanks again!


UPDATE:

Just in case anyone was wondering, I made a very simple patch to my original code to keep it from crashing. All it took was a slight rewrite of the "add", "kill", and "draw" functions so that each body has a "dead" variable. Bodies are now properly destroyed in the draw loop (should be in the think loop, but this small program doesn't need to use that) rather than some other time. I'm pretty sure that precautions must still be taken when messing with the callback functions so that the physics world isn't changed (i.e. bodies aren't moved or deleted) within the callbacks.

Replace the three functions from the original code I pasted with these new ones and everything will be peachy. :megagrin:

Code: Select all

function AddRect(x, y, w, h, rot)	--Adds a new rectangle to RectTable.
	local addTable = {}
		addTable.dead = false
		addTable.body = love.physics.newBody(TheWorld, x, y, 1, 1)
		addTable.shape = love.physics.newRectangleShape(addTable.body, 0, 0, w, h, rot)
		addTable.body:setMassFromShapes()
	table.insert(RectTable, addTable)
end
function KillRect(index)	--Removes the rectangle at position 'index' in RecTable.
	local rect = RectTable[index]
	if(rect) then
		rect.shape:setMask(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)
		rect.dead = true
	end
end

function DrawRects()	--This draws the rectangles in the table 'RectTable'
	for i=#RectTable, 1, -1 do
		local rect = RectTable[i]
		if(rect.dead) then		--Properly kill the rect.
			rect.shape:destroy()
			rect.body:destroy()
			rect.shape = nil
			rect.body = nil
			table.remove(RectTable, i)
		else		--Draw the rect normally.
			love.graphics.setColor(255, 255, 255, 255)
			love.graphics.polygon("fill", rect.shape:getPoints())	--white inside.
			love.graphics.setColor(0, 0, 0, 255)
			love.graphics.polygon("line", rect.shape:getPoints())	--black outline.
		end
	end
end
Post Reply

Who is online

Users browsing this forum: Google [Bot] and 2 guests