-
Notifications
You must be signed in to change notification settings - Fork 191
Description
Space has a _locked attribute so that during a step, the add() and remove() methods may automatically delay until the step finishes.
This _locked attribute is being corrupted during the step which can lead to a Chipmunk error when using add or remove.
The _locked attribute is temporarily set to True for separate collision handlers as it may occur outside of a step if the colliding shapes are removed, but afterwards it is set back to False, even in the middle of a step where it should be True. So if a collision handler (that is not separate) adds or removes later in the same step, a Chipmunk error is raised that the operation is illegal. (This isn't much of a common scenario.)
Lines 220 to 227 in ca53085
| try: | |
| # this try is needed since a separate callback will be called | |
| # if a colliding object is removed, regardless if its in a | |
| # step or not. | |
| handler._space._locked = True | |
| handler._separate(Arbiter(_arb, handler._space), handler._space, handler.data) | |
| finally: | |
| handler._space._locked = False |
The easy fix is to set the _locked attribute back to the original _locked value instead of False literal.
Minimal reproducible example:
import pymunk as pm
def get_locked(self):
return self._locked_debug
def set_locked(self, value: bool):
print("SET _locked", value)
self._locked_debug = value
pm.Space._locked = property(get_locked, set_locked) # type: ignore[assignment]
space = pm.Space()
space.gravity = 0, -100
body1 = pm.Body()
shape1 = pm.Circle(body1, 20)
shape1.density = 5
shape1.collision_type = 222
floor1 = pm.Segment(space.static_body, (-100,-30), (100,-30), 1)
body2 = pm.Body()
body2.position = 500,0
shape2 = pm.Circle(body2, 20)
shape2.density = 5
shape2.collision_type = 333
floor2 = pm.Segment(space.static_body, (400,-30), (600,-30), 1)
space.add(body1, shape1, body2, shape2, floor1, floor2)
separate_occurred = False
def separate(arbiter: pm.Arbiter, space: pm.Space, data):
global separate_occurred
separate_occurred = True
print('SEPARATE')
def post_solve(arbiter: pm.Arbiter, space: pm.Space, data):
print('POST SOLVE')
if separate_occurred:
print('ADDING/REMOVING')
space.add(pm.Circle(space.static_body, 5))
space.add_wildcard_collision_handler(222).separate = separate
space.add_wildcard_collision_handler(333).post_solve = post_solve
for i in range(60):
print('step START', i)
space.step(1/60)
print('step END', i)
if i == 30:
body1.apply_impulse_at_local_point((0,1000000))(I was implementing a bullet that destroyed itself on impact, but it gave an error during removal, specifically when the bullet was shot point blank into an enemy. I also encountered a separate minor bug while debugging this that deserves a separate issue.)