Fix callback/timer GC: prevent premature destruction of Python callbacks
closes #251 Two related bugs where Python garbage collection destroyed callbacks that were still needed by live C++ objects: 1. **Drawable callbacks (all 8 types)**: tp_dealloc unconditionally called click_unregister() etc., destroying callbacks even when the C++ object was still alive in a parent's children vector. Fixed by guarding with shared_ptr::use_count() <= 1 — only unregister when the Python wrapper is the last owner. 2. **Timer GC prevention**: Active timers now hold a Py_INCREF'd reference to their Python wrapper (Timer::py_wrapper), preventing GC while the timer is registered in the engine. Released on stop(), one-shot fire, or destruction. mcrfpy.Timer("name", cb, 100) now works without storing the return value. Also includes audio synth demo UI fixes: button click handling (don't set on_click on Caption children), single-column slider layout, improved Animalese contrast. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
97dbec9106
commit
9718153709
15 changed files with 740 additions and 231 deletions
|
|
@ -101,6 +101,8 @@ int PyTimer::init(PyTimerObject* self, PyObject* args, PyObject* kwds) {
|
|||
it->second->stop();
|
||||
}
|
||||
Resources::game->timers[self->name] = self->data;
|
||||
// Prevent Python GC while timer is active (#251)
|
||||
self->data->retainPyWrapper((PyObject*)self);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -147,6 +149,8 @@ PyObject* PyTimer::start(PyTimerObject* self, PyObject* Py_UNUSED(ignored)) {
|
|||
}
|
||||
|
||||
self->data->start(current_time);
|
||||
// Prevent Python GC while timer is active (#251)
|
||||
self->data->retainPyWrapper((PyObject*)self);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
|
@ -218,6 +222,8 @@ PyObject* PyTimer::restart(PyTimerObject* self, PyObject* Py_UNUSED(ignored)) {
|
|||
}
|
||||
|
||||
self->data->restart(current_time);
|
||||
// Prevent Python GC while timer is active (#251)
|
||||
self->data->retainPyWrapper((PyObject*)self);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
|
@ -316,6 +322,8 @@ int PyTimer::set_active(PyTimerObject* self, PyObject* value, void* closure) {
|
|||
Resources::game->timers[self->name] = self->data;
|
||||
}
|
||||
self->data->start(current_time);
|
||||
// Prevent Python GC while timer is active (#251)
|
||||
self->data->retainPyWrapper((PyObject*)self);
|
||||
} else if (self->data->isPaused()) {
|
||||
// Resume from pause
|
||||
self->data->resume(current_time);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue