diff --git a/src/GameEngine.cpp b/src/GameEngine.cpp index a5a195b..351214d 100644 --- a/src/GameEngine.cpp +++ b/src/GameEngine.cpp @@ -164,6 +164,15 @@ void GameEngine::run() } } +std::shared_ptr GameEngine::getTimer(const std::string& name) +{ + auto it = timers.find(name); + if (it != timers.end()) { + return it->second; + } + return nullptr; +} + void GameEngine::manageTimer(std::string name, PyObject* target, int interval) { auto it = timers.find(name); diff --git a/src/GameEngine.h b/src/GameEngine.h index 02e02ae..f491a09 100644 --- a/src/GameEngine.h +++ b/src/GameEngine.h @@ -29,12 +29,12 @@ class GameEngine bool headless = false; McRogueFaceConfig config; - sf::Clock runtime; - //std::map timers; - std::map> timers; void testTimers(); public: + sf::Clock runtime; + //std::map timers; + std::map> timers; std::string scene; GameEngine(); GameEngine(const McRogueFaceConfig& cfg); @@ -54,6 +54,7 @@ public: float getFrameTime() { return frameTime; } sf::View getView() { return visible; } void manageTimer(std::string, PyObject*, int); + std::shared_ptr getTimer(const std::string& name); void setWindowScale(float); bool isHeadless() const { return headless; } void processEvent(const sf::Event& event); diff --git a/src/McRFPy_API.cpp b/src/McRFPy_API.cpp index 2b0c11b..a88cce6 100644 --- a/src/McRFPy_API.cpp +++ b/src/McRFPy_API.cpp @@ -3,6 +3,7 @@ #include "platform.h" #include "PyAnimation.h" #include "PyDrawable.h" +#include "PyTimer.h" #include "GameEngine.h" #include "UI.h" #include "Resources.h" @@ -85,6 +86,9 @@ PyObject* PyInit_mcrfpy() /*animation*/ &PyAnimationType, + + /*timer*/ + &PyTimerType, nullptr}; int i = 0; auto t = pytypes[i]; diff --git a/src/PyCallable.cpp b/src/PyCallable.cpp index 6d44501..c68275c 100644 --- a/src/PyCallable.cpp +++ b/src/PyCallable.cpp @@ -16,21 +16,24 @@ PyObject* PyCallable::call(PyObject* args, PyObject* kwargs) return PyObject_Call(target, args, kwargs); } -bool PyCallable::isNone() +bool PyCallable::isNone() const { return (target == Py_None || target == NULL); } PyTimerCallable::PyTimerCallable(PyObject* _target, int _interval, int now) -: PyCallable(_target), interval(_interval), last_ran(now) +: PyCallable(_target), interval(_interval), last_ran(now), + paused(false), pause_start_time(0), total_paused_time(0) {} PyTimerCallable::PyTimerCallable() -: PyCallable(Py_None), interval(0), last_ran(0) +: PyCallable(Py_None), interval(0), last_ran(0), + paused(false), pause_start_time(0), total_paused_time(0) {} bool PyTimerCallable::hasElapsed(int now) { + if (paused) return false; return now >= last_ran + interval; } @@ -60,6 +63,62 @@ bool PyTimerCallable::test(int now) return false; } +void PyTimerCallable::pause(int current_time) +{ + if (!paused) { + paused = true; + pause_start_time = current_time; + } +} + +void PyTimerCallable::resume(int current_time) +{ + if (paused) { + paused = false; + int paused_duration = current_time - pause_start_time; + total_paused_time += paused_duration; + // Adjust last_ran to account for the pause + last_ran += paused_duration; + } +} + +void PyTimerCallable::restart(int current_time) +{ + last_ran = current_time; + paused = false; + pause_start_time = 0; + total_paused_time = 0; +} + +void PyTimerCallable::cancel() +{ + // Cancel by setting target to None + if (target && target != Py_None) { + Py_DECREF(target); + } + target = Py_None; + Py_INCREF(Py_None); +} + +int PyTimerCallable::getRemaining(int current_time) const +{ + if (paused) { + // When paused, calculate time remaining from when it was paused + int elapsed_when_paused = pause_start_time - last_ran; + return interval - elapsed_when_paused; + } + int elapsed = current_time - last_ran; + return interval - elapsed; +} + +void PyTimerCallable::setCallback(PyObject* new_callback) +{ + if (target && target != Py_None) { + Py_DECREF(target); + } + target = Py_XNewRef(new_callback); +} + PyClickCallable::PyClickCallable(PyObject* _target) : PyCallable(_target) {} diff --git a/src/PyCallable.h b/src/PyCallable.h index ae828c7..6a4c7f6 100644 --- a/src/PyCallable.h +++ b/src/PyCallable.h @@ -10,7 +10,7 @@ protected: ~PyCallable(); PyObject* call(PyObject*, PyObject*); public: - bool isNone(); + bool isNone() const; }; class PyTimerCallable: public PyCallable @@ -19,11 +19,32 @@ private: int interval; int last_ran; void call(int); + + // Pause/resume support + bool paused; + int pause_start_time; + int total_paused_time; + public: bool hasElapsed(int); bool test(int); PyTimerCallable(PyObject*, int, int); PyTimerCallable(); + + // Timer control methods + void pause(int current_time); + void resume(int current_time); + void restart(int current_time); + void cancel(); + + // Timer state queries + bool isPaused() const { return paused; } + bool isActive() const { return !isNone() && !paused; } + int getInterval() const { return interval; } + void setInterval(int new_interval) { interval = new_interval; } + int getRemaining(int current_time) const; + PyObject* getCallback() { return target; } + void setCallback(PyObject* new_callback); }; class PyClickCallable: public PyCallable diff --git a/src/PyTimer.cpp b/src/PyTimer.cpp new file mode 100644 index 0000000..7f780a3 --- /dev/null +++ b/src/PyTimer.cpp @@ -0,0 +1,271 @@ +#include "PyTimer.h" +#include "PyCallable.h" +#include "GameEngine.h" +#include "Resources.h" +#include + +PyObject* PyTimer::repr(PyObject* self) { + PyTimerObject* timer = (PyTimerObject*)self; + std::ostringstream oss; + oss << "data) { + oss << "interval=" << timer->data->getInterval() << "ms "; + oss << (timer->data->isPaused() ? "paused" : "active"); + } else { + oss << "uninitialized"; + } + oss << ">"; + + return PyUnicode_FromString(oss.str().c_str()); +} + +PyObject* PyTimer::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds) { + PyTimerObject* self = (PyTimerObject*)type->tp_alloc(type, 0); + if (self) { + new(&self->name) std::string(); // Placement new for std::string + self->data = nullptr; + } + return (PyObject*)self; +} + +int PyTimer::init(PyTimerObject* self, PyObject* args, PyObject* kwds) { + static char* kwlist[] = {"name", "callback", "interval", NULL}; + const char* name = nullptr; + PyObject* callback = nullptr; + int interval = 0; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOi", kwlist, + &name, &callback, &interval)) { + return -1; + } + + if (!PyCallable_Check(callback)) { + PyErr_SetString(PyExc_TypeError, "callback must be callable"); + return -1; + } + + if (interval <= 0) { + PyErr_SetString(PyExc_ValueError, "interval must be positive"); + return -1; + } + + self->name = name; + + // Get current time from game engine + int current_time = 0; + if (Resources::game) { + current_time = Resources::game->runtime.getElapsedTime().asMilliseconds(); + } + + // Create the timer callable + self->data = std::make_shared(callback, interval, current_time); + + // Register with game engine + if (Resources::game) { + Resources::game->timers[self->name] = self->data; + } + + return 0; +} + +void PyTimer::dealloc(PyTimerObject* 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); + } + } + + // Explicitly destroy std::string + self->name.~basic_string(); + + // Clear shared_ptr + self->data.reset(); + + Py_TYPE(self)->tp_free((PyObject*)self); +} + +// Timer control methods +PyObject* PyTimer::pause(PyTimerObject* self, PyObject* Py_UNUSED(ignored)) { + if (!self->data) { + PyErr_SetString(PyExc_RuntimeError, "Timer not initialized"); + return nullptr; + } + + int current_time = 0; + if (Resources::game) { + current_time = Resources::game->runtime.getElapsedTime().asMilliseconds(); + } + + self->data->pause(current_time); + Py_RETURN_NONE; +} + +PyObject* PyTimer::resume(PyTimerObject* self, PyObject* Py_UNUSED(ignored)) { + if (!self->data) { + PyErr_SetString(PyExc_RuntimeError, "Timer not initialized"); + return nullptr; + } + + int current_time = 0; + if (Resources::game) { + current_time = Resources::game->runtime.getElapsedTime().asMilliseconds(); + } + + self->data->resume(current_time); + Py_RETURN_NONE; +} + +PyObject* PyTimer::cancel(PyTimerObject* self, PyObject* Py_UNUSED(ignored)) { + if (!self->data) { + PyErr_SetString(PyExc_RuntimeError, "Timer not initialized"); + return nullptr; + } + + // Remove from game engine + 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); + } + } + + self->data->cancel(); + self->data.reset(); + Py_RETURN_NONE; +} + +PyObject* PyTimer::restart(PyTimerObject* self, PyObject* Py_UNUSED(ignored)) { + if (!self->data) { + PyErr_SetString(PyExc_RuntimeError, "Timer not initialized"); + return nullptr; + } + + int current_time = 0; + if (Resources::game) { + current_time = Resources::game->runtime.getElapsedTime().asMilliseconds(); + } + + self->data->restart(current_time); + Py_RETURN_NONE; +} + +// Property getters/setters +PyObject* PyTimer::get_interval(PyTimerObject* self, void* closure) { + if (!self->data) { + PyErr_SetString(PyExc_RuntimeError, "Timer not initialized"); + return nullptr; + } + + return PyLong_FromLong(self->data->getInterval()); +} + +int PyTimer::set_interval(PyTimerObject* self, PyObject* value, void* closure) { + if (!self->data) { + PyErr_SetString(PyExc_RuntimeError, "Timer not initialized"); + return -1; + } + + if (!PyLong_Check(value)) { + PyErr_SetString(PyExc_TypeError, "interval must be an integer"); + return -1; + } + + long interval = PyLong_AsLong(value); + if (interval <= 0) { + PyErr_SetString(PyExc_ValueError, "interval must be positive"); + return -1; + } + + self->data->setInterval(interval); + return 0; +} + +PyObject* PyTimer::get_remaining(PyTimerObject* self, void* closure) { + if (!self->data) { + PyErr_SetString(PyExc_RuntimeError, "Timer not initialized"); + return nullptr; + } + + int current_time = 0; + if (Resources::game) { + current_time = Resources::game->runtime.getElapsedTime().asMilliseconds(); + } + + return PyLong_FromLong(self->data->getRemaining(current_time)); +} + +PyObject* PyTimer::get_paused(PyTimerObject* self, void* closure) { + if (!self->data) { + PyErr_SetString(PyExc_RuntimeError, "Timer not initialized"); + return nullptr; + } + + return PyBool_FromLong(self->data->isPaused()); +} + +PyObject* PyTimer::get_active(PyTimerObject* self, void* closure) { + if (!self->data) { + return Py_False; + } + + return PyBool_FromLong(self->data->isActive()); +} + +PyObject* PyTimer::get_callback(PyTimerObject* self, void* closure) { + if (!self->data) { + PyErr_SetString(PyExc_RuntimeError, "Timer not initialized"); + return nullptr; + } + + PyObject* callback = self->data->getCallback(); + if (!callback) { + Py_RETURN_NONE; + } + + Py_INCREF(callback); + return callback; +} + +int PyTimer::set_callback(PyTimerObject* self, PyObject* value, void* closure) { + if (!self->data) { + PyErr_SetString(PyExc_RuntimeError, "Timer not initialized"); + return -1; + } + + if (!PyCallable_Check(value)) { + PyErr_SetString(PyExc_TypeError, "callback must be callable"); + return -1; + } + + self->data->setCallback(value); + return 0; +} + +PyGetSetDef PyTimer::getsetters[] = { + {"interval", (getter)PyTimer::get_interval, (setter)PyTimer::set_interval, + "Timer interval in milliseconds", NULL}, + {"remaining", (getter)PyTimer::get_remaining, NULL, + "Time remaining until next trigger in milliseconds", NULL}, + {"paused", (getter)PyTimer::get_paused, NULL, + "Whether the timer is paused", NULL}, + {"active", (getter)PyTimer::get_active, NULL, + "Whether the timer is active and not paused", NULL}, + {"callback", (getter)PyTimer::get_callback, (setter)PyTimer::set_callback, + "The callback function to be called", NULL}, + {NULL} +}; + +PyMethodDef PyTimer::methods[] = { + {"pause", (PyCFunction)PyTimer::pause, METH_NOARGS, + "Pause the timer"}, + {"resume", (PyCFunction)PyTimer::resume, METH_NOARGS, + "Resume a paused timer"}, + {"cancel", (PyCFunction)PyTimer::cancel, METH_NOARGS, + "Cancel the timer and remove it from the system"}, + {"restart", (PyCFunction)PyTimer::restart, METH_NOARGS, + "Restart the timer from the current time"}, + {NULL} +}; \ No newline at end of file diff --git a/src/PyTimer.h b/src/PyTimer.h new file mode 100644 index 0000000..16c4deb --- /dev/null +++ b/src/PyTimer.h @@ -0,0 +1,58 @@ +#pragma once +#include "Common.h" +#include "Python.h" +#include +#include + +class PyTimerCallable; + +typedef struct { + PyObject_HEAD + std::shared_ptr data; + std::string name; +} PyTimerObject; + +class PyTimer +{ +public: + // Python type methods + static PyObject* repr(PyObject* self); + static int init(PyTimerObject* self, PyObject* args, PyObject* kwds); + static PyObject* pynew(PyTypeObject* type, PyObject* args=NULL, PyObject* kwds=NULL); + static void dealloc(PyTimerObject* self); + + // Timer control methods + static PyObject* pause(PyTimerObject* self, PyObject* Py_UNUSED(ignored)); + static PyObject* resume(PyTimerObject* self, PyObject* Py_UNUSED(ignored)); + static PyObject* cancel(PyTimerObject* self, PyObject* Py_UNUSED(ignored)); + static PyObject* restart(PyTimerObject* self, PyObject* Py_UNUSED(ignored)); + + // Timer property getters + static PyObject* get_interval(PyTimerObject* self, void* closure); + static int set_interval(PyTimerObject* self, PyObject* value, void* closure); + static PyObject* get_remaining(PyTimerObject* self, void* closure); + static PyObject* get_paused(PyTimerObject* self, void* closure); + static PyObject* get_active(PyTimerObject* self, void* closure); + static PyObject* get_callback(PyTimerObject* self, void* closure); + static int set_callback(PyTimerObject* self, PyObject* value, void* closure); + + static PyGetSetDef getsetters[]; + static PyMethodDef methods[]; +}; + +namespace mcrfpydef { + static PyTypeObject PyTimerType = { + .ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0}, + .tp_name = "mcrfpy.Timer", + .tp_basicsize = sizeof(PyTimerObject), + .tp_itemsize = 0, + .tp_dealloc = (destructor)PyTimer::dealloc, + .tp_repr = PyTimer::repr, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = PyDoc_STR("Timer object for scheduled callbacks"), + .tp_methods = PyTimer::methods, + .tp_getset = PyTimer::getsetters, + .tp_init = (initproc)PyTimer::init, + .tp_new = PyTimer::pynew, + }; +} \ No newline at end of file