Skip to content

Commit fe322bb

Browse files
committed
Reverse dependency between shape and body, so shape has the weak ref #275
1 parent df11ae6 commit fe322bb

File tree

2 files changed

+24
-19
lines changed

2 files changed

+24
-19
lines changed

pymunk/body.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ def freebody(cp_body: ffi.CData) -> None:
224224
self._constraints: WeakSet["Constraint"] = (
225225
WeakSet()
226226
) # weak refs to any constraints attached
227-
self._shapes: WeakSet["Shape"] = WeakSet() # weak refs to any shapes attached
227+
self._shapes: dict["Shape", None] = {}
228228

229229
d = ffi.new_handle(self)
230230
self._data_handle = d # to prevent gc to collect the handle
@@ -660,10 +660,7 @@ def constraints(self) -> Set["Constraint"]:
660660

661661
@property
662662
def shapes(self) -> Set["Shape"]:
663-
"""Get the shapes attached to this body.
664-
665-
The body only keeps a weak reference to the shapes and a live
666-
body wont prevent GC of the attached shapes"""
663+
"""Get the shapes attached to this body."""
667664
return set(self._shapes)
668665

669666
def local_to_world(self, v: Tuple[float, float]) -> Vec2d:

pymunk/shapes.py

+22-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
__docformat__ = "reStructuredText"
22

3+
import weakref
34
from typing import TYPE_CHECKING, List, Optional, Sequence, Tuple
45

56
if TYPE_CHECKING:
@@ -42,18 +43,19 @@ class Shape(PickleMixin, TypingAttrMixing, object):
4243
_pickle_attrs_skip = PickleMixin._pickle_attrs_skip + ["mass", "density"]
4344

4445
_space = None # Weak ref to the space holding this body (if any)
45-
46-
_id_counter = 1
46+
_dead_ref = weakref.ref(set())
4747

4848
def __init__(self, shape: "Shape") -> None:
4949
self._shape = shape
50-
self._body: Optional["Body"] = shape.body
50+
self._body: weakref.ref = weakref.ref(shape.body)
5151

5252
def _init(self, body: Optional["Body"], _shape: ffi.CData) -> None:
53-
self._body = body
5453

5554
if body is not None:
56-
body._shapes.add(self)
55+
self._body = weakref.ref(body)
56+
body._shapes[self] = None
57+
else:
58+
self._body = Shape._dead_ref
5759

5860
def shapefree(cp_shape: ffi.CData) -> None:
5961
cp_space = cp.cpShapeGetSpace(cp_shape)
@@ -225,19 +227,25 @@ def _set_surface_velocity(self, surface_v: Tuple[float, float]) -> None:
225227

226228
@property
227229
def body(self) -> Optional["Body"]:
228-
"""The body this shape is attached to. Can be set to None to
229-
indicate that this shape doesnt belong to a body."""
230-
return self._body
230+
"""The body this shape is attached to.
231+
232+
Can be set to None to indicate that this shape doesnt belong to a body.
233+
The shape only holds a weakref to the Body, meaning it wont prevent it
234+
from being GCed.
235+
"""
236+
return self._body()
231237

232238
@body.setter
233239
def body(self, body: Optional["Body"]) -> None:
234-
if self._body is not None:
235-
self._body._shapes.remove(self)
236-
body_body = ffi.NULL if body is None else body._body
237-
cp.cpShapeSetBody(self._shape, body_body)
240+
if self.body is not None:
241+
del self.body._shapes[self]
242+
cp_body = ffi.NULL if body is None else body._body
243+
cp.cpShapeSetBody(self._shape, cp_body)
238244
if body is not None:
239-
body._shapes.add(self)
240-
self._body = body
245+
body._shapes[self] = None
246+
self._body = weakref.ref(body)
247+
else:
248+
self._body = Shape._dead_ref
241249

242250
def update(self, transform: Transform) -> BB:
243251
"""Update, cache and return the bounding box of a shape with an

0 commit comments

Comments
 (0)