timer fixes: timers managed by engine can run in the background. Closes #180
This commit is contained in:
parent
2c20455003
commit
b0b17f4633
4 changed files with 57 additions and 22 deletions
|
|
@ -911,6 +911,32 @@ PyObject* McRFPy_API::_setScene(PyObject* self, PyObject* args) {
|
|||
return Py_None;
|
||||
}
|
||||
|
||||
// #180: Create a Python wrapper for an existing C++ Timer (for orphaned timers)
|
||||
static PyObject* createTimerWrapper(std::shared_ptr<Timer> timer)
|
||||
{
|
||||
// Allocate new PyTimerObject
|
||||
PyTypeObject* type = &mcrfpydef::PyTimerType;
|
||||
PyTimerObject* obj = (PyTimerObject*)type->tp_alloc(type, 0);
|
||||
if (!obj) return nullptr;
|
||||
|
||||
// Initialize - use placement new for std::string
|
||||
new(&obj->name) std::string(timer->name);
|
||||
obj->data = timer; // Share the existing C++ Timer
|
||||
obj->weakreflist = nullptr;
|
||||
|
||||
// Register in cache for future lookups
|
||||
if (timer->serial_number == 0) {
|
||||
timer->serial_number = PythonObjectCache::getInstance().assignSerial();
|
||||
}
|
||||
PyObject* weakref = PyWeakref_NewRef((PyObject*)obj, NULL);
|
||||
if (weakref) {
|
||||
PythonObjectCache::getInstance().registerObject(timer->serial_number, weakref);
|
||||
Py_DECREF(weakref);
|
||||
}
|
||||
|
||||
return (PyObject*)obj;
|
||||
}
|
||||
|
||||
// #173: Get all timers as a tuple of Python Timer objects
|
||||
PyObject* McRFPy_API::api_get_timers()
|
||||
{
|
||||
|
|
@ -918,15 +944,25 @@ PyObject* McRFPy_API::api_get_timers()
|
|||
return PyTuple_New(0);
|
||||
}
|
||||
|
||||
// Count timers that have Python wrappers
|
||||
std::vector<PyObject*> timer_objs;
|
||||
for (auto& pair : game->timers) {
|
||||
auto& timer = pair.second;
|
||||
if (timer && timer->serial_number != 0) {
|
||||
PyObject* timer_obj = PythonObjectCache::getInstance().lookup(timer->serial_number);
|
||||
if (timer_obj && timer_obj != Py_None) {
|
||||
timer_objs.push_back(timer_obj);
|
||||
}
|
||||
if (!timer) continue;
|
||||
|
||||
PyObject* timer_obj = nullptr;
|
||||
|
||||
// Try to find existing Python wrapper
|
||||
if (timer->serial_number != 0) {
|
||||
timer_obj = PythonObjectCache::getInstance().lookup(timer->serial_number);
|
||||
}
|
||||
|
||||
// #180: If no wrapper exists (orphaned timer), create a new one
|
||||
if (!timer_obj || timer_obj == Py_None) {
|
||||
timer_obj = createTimerWrapper(timer);
|
||||
}
|
||||
|
||||
if (timer_obj && timer_obj != Py_None) {
|
||||
timer_objs.push_back(timer_obj);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -934,7 +970,8 @@ PyObject* McRFPy_API::api_get_timers()
|
|||
if (!tuple) return NULL;
|
||||
|
||||
for (Py_ssize_t i = 0; i < static_cast<Py_ssize_t>(timer_objs.size()); i++) {
|
||||
Py_INCREF(timer_objs[i]);
|
||||
// timer_objs already has a reference from lookup() or createTimerWrapper()
|
||||
// PyTuple_SET_ITEM steals that reference, so no additional INCREF needed
|
||||
PyTuple_SET_ITEM(tuple, i, timer_objs[i]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,8 +80,8 @@ int PyTimer::init(PyTimerObject* self, PyObject* args, PyObject* kwds) {
|
|||
current_time = Resources::game->runtime.getElapsedTime().asMilliseconds();
|
||||
}
|
||||
|
||||
// Create the timer with start parameter
|
||||
self->data = std::make_shared<Timer>(callback, interval, current_time, (bool)once, (bool)start);
|
||||
// Create the timer with start parameter and name (#180)
|
||||
self->data = std::make_shared<Timer>(callback, interval, current_time, (bool)once, (bool)start, self->name);
|
||||
|
||||
// Register in Python object cache
|
||||
if (self->data->serial_number == 0) {
|
||||
|
|
@ -112,18 +112,14 @@ void PyTimer::dealloc(PyTimerObject* self) {
|
|||
PyObject_ClearWeakRefs((PyObject*)self);
|
||||
}
|
||||
|
||||
// Remove from game engine if still registered
|
||||
if (Resources::game && !self->name.empty()) {
|
||||
auto it = Resources::game->timers.find(self->name);
|
||||
if (it != Resources::game->timers.end() && it->second == self->data) {
|
||||
Resources::game->timers.erase(it);
|
||||
}
|
||||
}
|
||||
// #180: DON'T remove from game->timers - timer keeps running even without Python wrapper.
|
||||
// The engine's shared_ptr keeps the Timer alive. When timer.stop() is called or
|
||||
// a one-shot timer fires, testTimers() removes it from the map.
|
||||
|
||||
// Explicitly destroy std::string
|
||||
self->name.~basic_string();
|
||||
|
||||
// Clear shared_ptr - this is the only place that truly destroys the Timer
|
||||
// Clear our shared_ptr - if engine still has one, Timer lives on
|
||||
self->data.reset();
|
||||
|
||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@
|
|||
#include "McRFPy_API.h"
|
||||
#include "GameEngine.h"
|
||||
|
||||
Timer::Timer(PyObject* _target, int _interval, int now, bool _once, bool _start)
|
||||
Timer::Timer(PyObject* _target, int _interval, int now, bool _once, bool _start, const std::string& _name)
|
||||
: callback(std::make_shared<PyCallable>(_target)), interval(_interval), last_ran(now),
|
||||
paused(false), pause_start_time(0), total_paused_time(0), once(_once), stopped(!_start)
|
||||
paused(false), pause_start_time(0), total_paused_time(0), once(_once), stopped(!_start), name(_name)
|
||||
{}
|
||||
|
||||
Timer::Timer()
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "Common.h"
|
||||
#include "Python.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class PyCallable;
|
||||
class GameEngine; // forward declare
|
||||
|
|
@ -26,9 +27,10 @@ private:
|
|||
|
||||
public:
|
||||
uint64_t serial_number = 0; // For Python object cache
|
||||
std::string name; // Store name for creating Python wrappers (#180)
|
||||
|
||||
Timer(); // for map to build
|
||||
Timer(PyObject* target, int interval, int now, bool once = false, bool start = true);
|
||||
Timer(PyObject* target, int interval, int now, bool once = false, bool start = true, const std::string& name = "");
|
||||
~Timer();
|
||||
|
||||
// Core timer functionality
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue