Preserve Python subclass identity for entities in grids (reopens #266)
The Phase 3 fix for #266 removed UIEntity::self which prevented tp_dealloc from ever running. However, this also allowed Python subclass wrappers (GameEntity, ZoneExit, etc.) to be GC'd while the C++ entity lived on in a grid. Later access via grid.entities returned a base Entity wrapper, losing all subclass methods. Fix: Add UIEntity::pyobject field that holds a strong reference to the Python wrapper. Set in init(), cleared when the entity leaves a grid (die(), set_grid(None), collection removal). This keeps subclass identity alive while in a grid, but allows proper GC when the entity is removed. Added releasePyIdentity() helper called at all grid exit points. Regression test exercises Liber Noster patterns: subclass hierarchy, isinstance() checks, combat mixins, tooltip/send methods, GC survival, die(), pop(), remove(), and stress test with 20 entities. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
34c84ce50a
commit
836a0584df
4 changed files with 291 additions and 2 deletions
|
|
@ -61,6 +61,7 @@ class UIEntity
|
|||
{
|
||||
public:
|
||||
uint64_t serial_number = 0; // For Python object cache
|
||||
PyObject* pyobject = nullptr; // Strong ref: preserves Python subclass identity while in grid
|
||||
std::shared_ptr<UIGrid> grid;
|
||||
std::vector<UIGridPointState> gridstate;
|
||||
UISprite sprite;
|
||||
|
|
@ -70,7 +71,17 @@ public:
|
|||
|
||||
UIEntity();
|
||||
~UIEntity();
|
||||
|
||||
|
||||
// Release the strong reference that preserves Python subclass identity.
|
||||
// Called when entity leaves a grid (die, set_grid, collection removal).
|
||||
void releasePyIdentity() {
|
||||
if (pyobject) {
|
||||
PyObject* tmp = pyobject;
|
||||
pyobject = nullptr;
|
||||
Py_DECREF(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
// Visibility methods
|
||||
void ensureGridstate(); // Resize gridstate to match current grid dimensions
|
||||
void updateVisibility(); // Update gridstate from current FOV
|
||||
|
|
@ -136,6 +147,8 @@ namespace mcrfpydef {
|
|||
.tp_itemsize = 0,
|
||||
.tp_dealloc = [](PyObject* obj) {
|
||||
auto* self = (PyUIEntityObject*)obj;
|
||||
// Clear the identity ref without DECREF - we ARE this object
|
||||
if (self->data) self->data->pyobject = nullptr;
|
||||
if (self->weakreflist) PyObject_ClearWeakRefs(obj);
|
||||
self->data.reset();
|
||||
Py_TYPE(obj)->tp_free(obj);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue