[Bugfix] A GC'd Python subclass wrapper resurrects as its base type (residual gap from #369) #373
Labels
No labels
Alpha Release Requirement
Bugfix
Demo Target
Documentation
Major Feature
Minor Feature
priority:tier1-active
priority:tier2-foundation
priority:tier3-future
priority:tier4-deferred
Refactoring & Cleanup
system:animation
system:documentation
system:grid
system:input
system:performance
system:procgen
system:python-binding
system:rendering
system:ui-hierarchy
Tiny Feature
workflow:blocked
workflow:needs-benchmark
workflow:needs-documentation
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
john/McRogueFace#373
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Spun out of #369, which is otherwise closed.
What #369 fixed
.parent,mcrfpy.find()/find_all(), andUIEntityCollectionconcat/slice each hand-rolled their owntp_allocswitch instead of consultingPythonObjectCache, sochild.parent is parentwas alwaysFalse. They now all route throughUIDrawable::pyobject_for()(andconvertEntityToPython()for entities), so object identity holds and a Python subclass reached through those paths comes back intact.What it did NOT fix
PythonObjectCachestores weakrefs. Object identity therefore only survives as long as someone in Python still holds a reference to the wrapper.If a Python subclass wrapper is garbage-collected while only C++ still holds the object, the next lookup misses the cache and allocates a fresh base-type wrapper. The subclass, and any attributes the user set on it, are gone:
Why it wasn't fixed in #369
The entity path solves this with a pin:
UIEntity::pyobject(#266) is a strong ref from the C++ object back to its wrapper, so the cache weakref can never die while the entity is in a grid. That works because entity lifetime has a clear boundary — grid membership — andreleasePyIdentity()breaks the cycle at every exit point.Drawables have no equivalent boundary. They live in scene UI collections, frames, grid overlays; "leaving" is diffuse. An unconditional self-pin would leak every drawable ever created, since the wrapper→C++ and C++→wrapper refs form a cycle that nothing would break.
Possible directions
UIGridView::get_grid: the owningUICollectionpins wrappers for drawables whoseis_python_subclassflag is set, and releases on removal. Bounded (only subclassed drawables pay), but needs a release at every removal path — the same auditreleasePyIdentity()needed.removeFromParent()/ collection-removal as the release point.is_python_subclassalready exists onUIDrawable(UIDrawable.h:270), so the discrimination in (1)/(2) is available today.Test
tests/regression/issue_369_parent_identity_test.pycovers the live-wrapper case. A test for this issue shoulddelthe only Python reference,gc.collect(), and assert the subclass and its attributes survive a.parentround trip.