Timer refactor: stopwatch-like semantics, mcrfpy.timers collection closes #173

Major Timer API improvements:
- Add `stopped` flag to Timer C++ class for proper state management
- Add `start()` method to restart stopped timers (preserves callback)
- Add `stop()` method that removes from engine but preserves callback
- Make `active` property read-write (True=start/resume, False=pause)
- Add `start=True` init parameter to create timers in stopped state
- Add `mcrfpy.timers` module-level collection (tuple of active timers)
- One-shot timers now set stopped=true instead of clearing callback
- Remove deprecated `setTimer()` and `delTimer()` module functions

Timer callbacks now receive (timer, runtime) instead of just (runtime).
Updated all tests to use new Timer API and callback signature.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2026-01-03 19:21:37 -05:00
commit 5d41292bf6
16 changed files with 440 additions and 262 deletions

View file

@ -4,14 +4,14 @@
#include "McRFPy_API.h"
#include "GameEngine.h"
Timer::Timer(PyObject* _target, int _interval, int now, bool _once)
Timer::Timer(PyObject* _target, int _interval, int now, bool _once, bool _start)
: callback(std::make_shared<PyCallable>(_target)), interval(_interval), last_ran(now),
paused(false), pause_start_time(0), total_paused_time(0), once(_once)
paused(false), pause_start_time(0), total_paused_time(0), once(_once), stopped(!_start)
{}
Timer::Timer()
: callback(std::make_shared<PyCallable>(Py_None)), interval(0), last_ran(0),
paused(false), pause_start_time(0), total_paused_time(0), once(false)
paused(false), pause_start_time(0), total_paused_time(0), once(false), stopped(true)
{}
Timer::~Timer() {
@ -22,24 +22,24 @@ Timer::~Timer() {
bool Timer::hasElapsed(int now) const
{
if (paused) return false;
if (paused || stopped) return false;
return now >= last_ran + interval;
}
bool Timer::test(int now)
{
if (!callback || callback->isNone()) return false;
if (!callback || callback->isNone() || stopped) return false;
if (hasElapsed(now))
{
last_ran = now;
// Get the PyTimer wrapper from cache to pass to callback
PyObject* timer_obj = nullptr;
if (serial_number != 0) {
timer_obj = PythonObjectCache::getInstance().lookup(serial_number);
}
// Build args: (timer, runtime) or just (runtime) if no wrapper found
PyObject* args;
if (timer_obj) {
@ -48,10 +48,10 @@ bool Timer::test(int now)
// Fallback to old behavior if no wrapper found
args = Py_BuildValue("(i)", now);
}
PyObject* retval = callback->call(args, NULL);
Py_DECREF(args);
if (!retval)
{
std::cerr << "Timer callback raised an exception:" << std::endl;
@ -63,16 +63,16 @@ bool Timer::test(int now)
McRFPy_API::signalPythonException();
}
} else if (retval != Py_None)
{
{
std::cout << "Timer returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
Py_DECREF(retval);
}
// Handle one-shot timers
// Handle one-shot timers: stop but preserve callback for potential restart
if (once) {
cancel();
stopped = true; // Will be removed from map by testTimers(), but callback preserved
}
return true;
}
return false;
@ -101,23 +101,41 @@ void Timer::restart(int current_time)
{
last_ran = current_time;
paused = false;
stopped = false; // Ensure timer is running
pause_start_time = 0;
total_paused_time = 0;
}
void Timer::cancel()
void Timer::start(int current_time)
{
// Cancel by setting callback to None
callback = std::make_shared<PyCallable>(Py_None);
// Start/resume the timer - clear stopped flag, reset progress
stopped = false;
paused = false;
last_ran = current_time;
pause_start_time = 0;
total_paused_time = 0;
}
void Timer::stop()
{
// Stop the timer - it will be removed from engine map, but callback is preserved
stopped = true;
paused = false;
pause_start_time = 0;
total_paused_time = 0;
}
bool Timer::isActive() const
{
return callback && !callback->isNone() && !paused;
return callback && !callback->isNone() && !paused && !stopped;
}
int Timer::getRemaining(int current_time) const
{
if (stopped) {
// When stopped, progress is reset - full interval remaining
return interval;
}
if (paused) {
// When paused, calculate time remaining from when it was paused
int elapsed_when_paused = pause_start_time - last_ran;
@ -129,6 +147,10 @@ int Timer::getRemaining(int current_time) const
int Timer::getElapsed(int current_time) const
{
if (stopped) {
// When stopped, progress is reset
return 0;
}
if (paused) {
return pause_start_time - last_ran;
}