Skip to content

Commit d9f6de8

Browse files
committed
Reworked collision handlers wip #280
1 parent 23fff81 commit d9f6de8

14 files changed

+162
-237
lines changed

CHANGELOG.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Changelog
44
Pymunk 7.0.0 (2025-04-10)
55
-------------------------
66

7+
TODO: Add note of new collision handler logic!
8+
79
**Many improvements, with some breaking changes!**
810

911
This is a big cleanup release with several breaking changes. If you upgrade from an older version, make sure to pay attention, especially the Space.bodies, Space.shapes and shape.constraints updates can break silently!
@@ -47,6 +49,9 @@ Other improvements
4749
Extra thanks for aetle for a number of suggestions for improvements in this pymunk release
4850

4951

52+
53+
54+
5055
Pymunk 6.11.1 (2025-02-09)
5156
--------------------------
5257

benchmarks/pymunk-collision-callback.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
b = pymunk.Body(1,10)
99
c = pymunk.Circle(b, 5)
1010
s.add(b, c)
11-
h = s.add_default_collision_handler()
11+
h = s.add_global_collision_handler()
1212
def f(arb, s, data):
1313
return False
1414
h.pre_solve = f

pymunk/_callbacks.py

Lines changed: 4 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import logging
22
import math
3-
import warnings
43

54
from ._chipmunk_cffi import ffi, lib
65
from .arbiter import Arbiter
@@ -202,51 +201,17 @@ def ext_cpMarchSampleFunc(point: ffi.CData, data: ffi.CData) -> float:
202201
@ffi.def_extern()
203202
def ext_cpCollisionBeginFunc(
204203
_arb: ffi.CData, _space: ffi.CData, data: ffi.CData
205-
) -> bool:
204+
) -> None:
206205
handler = ffi.from_handle(data)
207-
x = handler._begin(Arbiter(_arb, handler._space), handler._space, handler.data)
208-
if isinstance(x, bool):
209-
return x
210-
211-
func_name = handler._begin.__code__.co_name
212-
filename = handler._begin.__code__.co_filename
213-
lineno = handler._begin.__code__.co_firstlineno
214-
215-
warnings.warn_explicit(
216-
"Function '" + func_name + "' should return a bool to"
217-
" indicate if the collision should be processed or not when"
218-
" used as 'begin' or 'pre_solve' collision callback.",
219-
UserWarning,
220-
filename,
221-
lineno,
222-
handler._begin.__module__,
223-
)
224-
return True
206+
handler._begin(Arbiter(_arb, handler._space), handler._space, handler.data)
225207

226208

227209
@ffi.def_extern()
228210
def ext_cpCollisionPreSolveFunc(
229211
_arb: ffi.CData, _space: ffi.CData, data: ffi.CData
230-
) -> bool:
212+
) -> None:
231213
handler = ffi.from_handle(data)
232-
x = handler._pre_solve(Arbiter(_arb, handler._space), handler._space, handler.data)
233-
if isinstance(x, bool):
234-
return x
235-
236-
func_name = handler._pre_solve.__code__.co_name
237-
filename = handler._pre_solve.__code__.co_filename
238-
lineno = handler._pre_solve.__code__.co_firstlineno
239-
240-
warnings.warn_explicit(
241-
"Function '" + func_name + "' should return a bool to"
242-
" indicate if the collision should be processed or not when"
243-
" used as 'begin' or 'pre_solve' collision callback.",
244-
UserWarning,
245-
filename,
246-
lineno,
247-
handler._pre_solve.__module__,
248-
)
249-
return True
214+
handler._pre_solve(Arbiter(_arb, handler._space), handler._space, handler.data)
250215

251216

252217
@ffi.def_extern()

pymunk/arbiter.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,26 @@ def __init__(self, _arbiter: ffi.CData, space: "Space") -> None:
3939
self._arbiter = _arbiter
4040
self._space = space
4141

42+
@property
43+
def process_collision(self) -> bool:
44+
"""Decides if the collision should be processed or rejected.
45+
46+
Set this during a begin() or pre_solve() callback to override
47+
the default (True) value.
48+
49+
Set this to true to process the collision normally or
50+
false to cause pymunk to ignore the collision entirely. If you set it to
51+
false from a `begin` callback, the `pre_solve` and `post_solve` callbacks will never be run,
52+
but you will still recieve a separate event when the shapes stop
53+
overlapping.
54+
55+
"""
56+
return lib.cpArbiterGetProcessCollision(self._arbiter)
57+
58+
@process_collision.setter
59+
def process_collision(self, v: bool) -> None:
60+
lib.cpArbiterSetProcessCollision(self._arbiter, v)
61+
4262
@property
4363
def contact_point_set(self) -> ContactPointSet:
4464
"""Contact point sets make getting contact information from the

pymunk/collision_handler.py

Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
from ._chipmunk_cffi import ffi, lib
99
from .arbiter import Arbiter
1010

11-
_CollisionCallbackBool = Callable[[Arbiter, "Space", Dict[Any, Any]], bool]
12-
_CollisionCallbackNoReturn = Callable[[Arbiter, "Space", Dict[Any, Any]], None]
11+
_CollisionCallback = Callable[[Arbiter, "Space", Dict[Any, Any]], None]
1312

1413

1514
class CollisionHandler(object):
@@ -42,10 +41,10 @@ def __init__(self, _handler: Any, space: "Space") -> None:
4241
self._handler.userData = self._userData
4342

4443
self._space = space
45-
self._begin: _CollisionCallbackBool = CollisionHandler.always_collide
46-
self._pre_solve: _CollisionCallbackBool = CollisionHandler.always_collide
47-
self._post_solve: _CollisionCallbackNoReturn = CollisionHandler.do_nothing
48-
self._separate: _CollisionCallbackNoReturn = CollisionHandler.do_nothing
44+
self._begin: _CollisionCallback = CollisionHandler.do_nothing
45+
self._pre_solve: _CollisionCallback = CollisionHandler.do_nothing
46+
self._post_solve: _CollisionCallback = CollisionHandler.do_nothing
47+
self._separate: _CollisionCallback = CollisionHandler.do_nothing
4948

5049
self._data: Dict[Any, Any] = {}
5150

@@ -61,10 +60,10 @@ def data(self) -> Dict[Any, Any]:
6160
return self._data
6261

6362
@property
64-
def begin(self) -> _CollisionCallbackBool:
63+
def begin(self) -> _CollisionCallback:
6564
"""Two shapes just started touching for the first time this step.
6665
67-
``func(arbiter, space, data) -> bool``
66+
``func(arbiter, space, data)``
6867
6968
Return true from the callback to process the collision normally or
7069
false to cause pymunk to ignore the collision entirely. If you return
@@ -75,39 +74,38 @@ def begin(self) -> _CollisionCallbackBool:
7574
return self._begin
7675

7776
@begin.setter
78-
def begin(self, func: _CollisionCallbackBool) -> None:
77+
def begin(self, func: _CollisionCallback) -> None:
7978
self._begin = func
8079

81-
if self._begin == CollisionHandler.always_collide:
82-
self._handler.beginFunc = ffi.addressof(lib, "AlwaysCollide")
80+
if self._begin == CollisionHandler.do_nothing:
81+
self._handler.beginFunc = ffi.addressof(lib, "DoNothing")
8382
else:
8483
self._handler.beginFunc = lib.ext_cpCollisionBeginFunc
8584

8685
@property
87-
def pre_solve(self) -> _CollisionCallbackBool:
86+
def pre_solve(self) -> _CollisionCallback:
8887
"""Two shapes are touching during this step.
8988
90-
``func(arbiter, space, data) -> bool``
89+
``func(arbiter, space, data)``
9190
92-
Return false from the callback to make pymunk ignore the collision
93-
this step or true to process it normally. Additionally, you may
91+
Additionally, you may
9492
override collision values using Arbiter.friction, Arbiter.elasticity
9593
or Arbiter.surfaceVelocity to provide custom friction, elasticity,
9694
or surface velocity values. See Arbiter for more info.
9795
"""
9896
return self._pre_solve
9997

10098
@pre_solve.setter
101-
def pre_solve(self, func: _CollisionCallbackBool) -> None:
99+
def pre_solve(self, func: _CollisionCallback) -> None:
102100
self._pre_solve = func
103101

104-
if self._pre_solve == CollisionHandler.always_collide:
105-
self._handler.preSolveFunc = ffi.addressof(lib, "AlwaysCollide")
102+
if self._pre_solve == CollisionHandler.do_nothing:
103+
self._handler.preSolveFunc = ffi.addressof(lib, "DoNothing")
106104
else:
107105
self._handler.preSolveFunc = lib.ext_cpCollisionPreSolveFunc
108106

109107
@property
110-
def post_solve(self) -> _CollisionCallbackNoReturn:
108+
def post_solve(self) -> _CollisionCallback:
111109
"""Two shapes are touching and their collision response has been
112110
processed.
113111
@@ -120,18 +118,16 @@ def post_solve(self) -> _CollisionCallbackNoReturn:
120118
return self._post_solve
121119

122120
@post_solve.setter
123-
def post_solve(self, func: _CollisionCallbackNoReturn) -> None:
121+
def post_solve(self, func: _CollisionCallback) -> None:
124122
self._post_solve = func
125123

126124
if self._post_solve == CollisionHandler.do_nothing:
127125
self._handler.postSolveFunc = ffi.addressof(lib, "DoNothing")
128126
else:
129127
self._handler.postSolveFunc = lib.ext_cpCollisionPostSolveFunc
130128

131-
self._handler.postSolveFunc = lib.ext_cpCollisionPostSolveFunc
132-
133129
@property
134-
def separate(self) -> _CollisionCallbackNoReturn:
130+
def separate(self) -> _CollisionCallback:
135131
"""Two shapes have just stopped touching for the first time this
136132
step.
137133
@@ -144,7 +140,7 @@ def separate(self) -> _CollisionCallbackNoReturn:
144140
return self._separate
145141

146142
@separate.setter
147-
def separate(self, func: _CollisionCallbackNoReturn) -> None:
143+
def separate(self, func: _CollisionCallback) -> None:
148144
self._separate = func
149145

150146
if self._separate == CollisionHandler.do_nothing:
@@ -161,14 +157,3 @@ def do_nothing(arbiter: Arbiter, space: "Space", data: Dict[Any, Any]) -> None:
161157
do nothing method.
162158
"""
163159
return
164-
165-
@staticmethod
166-
def always_collide(arbiter: Arbiter, space: "Space", data: Dict[Any, Any]) -> bool:
167-
"""The default method used for the begin and pre_solve callbacks.
168-
169-
It will always return True, meaning the collision should not be ignored.
170-
171-
Note that its more efficient to set this method than to define your own
172-
return True method.
173-
"""
174-
return True

0 commit comments

Comments
 (0)