Remove entity self-reference cycle

UIEntity::init() stored self->data->self = (PyObject*)self with
Py_INCREF(self), creating a reference cycle that prevented entities
from ever being freed. The matching Py_DECREF never existed.

Fix: Remove the `self` field from UIEntity entirely. Replace all
read sites (iter next, getitem, get_perspective, entities_in_radius)
with PythonObjectCache lookups using serial_number, which uses weak
references and doesn't prevent garbage collection.

Also adds tp_dealloc to PyUIEntityType to properly clean up the
shared_ptr and weak references when the Python wrapper is freed.

Closes #266, closes #275

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2026-03-07 23:22:58 -05:00
commit a12e035a71
5 changed files with 123 additions and 30 deletions

View file

@ -60,7 +60,6 @@ PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>&
class UIEntity
{
public:
PyObject* self = nullptr; // Reference to the Python object (if created from Python)
uint64_t serial_number = 0; // For Python object cache
std::shared_ptr<UIGrid> grid;
std::vector<UIGridPointState> gridstate;
@ -135,6 +134,12 @@ namespace mcrfpydef {
.tp_name = "mcrfpy.Entity",
.tp_basicsize = sizeof(PyUIEntityObject),
.tp_itemsize = 0,
.tp_dealloc = [](PyObject* obj) {
auto* self = (PyUIEntityObject*)obj;
if (self->weakreflist) PyObject_ClearWeakRefs(obj);
self->data.reset();
Py_TYPE(obj)->tp_free(obj);
},
.tp_repr = (reprfunc)UIEntity::repr,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_doc = PyDoc_STR("Entity(grid_pos=None, texture=None, sprite_index=0, **kwargs)\n\n"