Compare commits
4 commits
fc95fc2844
...
d878c8684d
| Author | SHA1 | Date | |
|---|---|---|---|
| d878c8684d | |||
| 357c2ac7d7 | |||
| cec76b63dc | |||
| 5d41292bf6 |
104 changed files with 1572 additions and 817 deletions
|
|
@ -73,9 +73,15 @@ void Animation::start(std::shared_ptr<UIDrawable> target) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, int>) {
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
int value;
|
// Most UI properties use float, so try float first, then int
|
||||||
if (target->getProperty(targetProperty, value)) {
|
float fvalue;
|
||||||
startValue = value;
|
if (target->getProperty(targetProperty, fvalue)) {
|
||||||
|
startValue = static_cast<int>(fvalue);
|
||||||
|
} else {
|
||||||
|
int ivalue;
|
||||||
|
if (target->getProperty(targetProperty, ivalue)) {
|
||||||
|
startValue = ivalue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
||||||
|
|
@ -104,6 +110,16 @@ void Animation::start(std::shared_ptr<UIDrawable> target) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, targetValue);
|
}, targetValue);
|
||||||
|
|
||||||
|
// For zero-duration animations, apply final value immediately
|
||||||
|
if (duration <= 0.0f) {
|
||||||
|
AnimationValue finalValue = interpolate(1.0f);
|
||||||
|
applyValue(target.get(), finalValue);
|
||||||
|
if (pythonCallback && !callbackTriggered) {
|
||||||
|
triggerCallback();
|
||||||
|
}
|
||||||
|
callbackTriggered = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::startEntity(std::shared_ptr<UIEntity> target) {
|
void Animation::startEntity(std::shared_ptr<UIEntity> target) {
|
||||||
|
|
@ -131,6 +147,16 @@ void Animation::startEntity(std::shared_ptr<UIEntity> target) {
|
||||||
}
|
}
|
||||||
// Entities don't support other types yet
|
// Entities don't support other types yet
|
||||||
}, targetValue);
|
}, targetValue);
|
||||||
|
|
||||||
|
// For zero-duration animations, apply final value immediately
|
||||||
|
if (duration <= 0.0f) {
|
||||||
|
AnimationValue finalValue = interpolate(1.0f);
|
||||||
|
applyValue(target.get(), finalValue);
|
||||||
|
if (pythonCallback && !callbackTriggered) {
|
||||||
|
triggerCallback();
|
||||||
|
}
|
||||||
|
callbackTriggered = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Animation::hasValidTarget() const {
|
bool Animation::hasValidTarget() const {
|
||||||
|
|
@ -175,7 +201,23 @@ bool Animation::update(float deltaTime) {
|
||||||
return false; // Remove this animation
|
return false; // Remove this animation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle already-complete animations (e.g., duration=0)
|
||||||
|
// Apply final value once before returning
|
||||||
if (isComplete()) {
|
if (isComplete()) {
|
||||||
|
if (!callbackTriggered) {
|
||||||
|
// Apply final value for zero-duration animations
|
||||||
|
AnimationValue finalValue = interpolate(1.0f);
|
||||||
|
if (target) {
|
||||||
|
applyValue(target.get(), finalValue);
|
||||||
|
} else if (entity) {
|
||||||
|
applyValue(entity.get(), finalValue);
|
||||||
|
}
|
||||||
|
// Trigger callback
|
||||||
|
if (pythonCallback) {
|
||||||
|
triggerCallback();
|
||||||
|
}
|
||||||
|
callbackTriggered = true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,8 +360,12 @@ void Animation::applyValue(UIDrawable* target, const AnimationValue& value) {
|
||||||
target->setProperty(targetProperty, val);
|
target->setProperty(targetProperty, val);
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, int>) {
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
// Most UI properties use float setProperty, so try float first
|
||||||
|
if (!target->setProperty(targetProperty, static_cast<float>(val))) {
|
||||||
|
// Fall back to int if float didn't work
|
||||||
target->setProperty(targetProperty, val);
|
target->setProperty(targetProperty, val);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if constexpr (std::is_same_v<T, sf::Color>) {
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
||||||
target->setProperty(targetProperty, val);
|
target->setProperty(targetProperty, val);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -357,52 +357,36 @@ std::shared_ptr<Timer> GameEngine::getTimer(const std::string& name)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameEngine::manageTimer(std::string name, PyObject* target, int interval)
|
// Note: manageTimer() removed in #173 - use Timer objects directly
|
||||||
{
|
|
||||||
auto it = timers.find(name);
|
|
||||||
|
|
||||||
// #153 - In headless mode, use simulation_time instead of real-time clock
|
|
||||||
int now = headless ? simulation_time : runtime.getElapsedTime().asMilliseconds();
|
|
||||||
|
|
||||||
if (it != timers.end()) // overwrite existing
|
|
||||||
{
|
|
||||||
if (target == NULL || target == Py_None)
|
|
||||||
{
|
|
||||||
// Delete: Overwrite existing timer with one that calls None. This will be deleted in the next timer check
|
|
||||||
// see gitea issue #4: this allows for a timer to be deleted during its own call to itself
|
|
||||||
timers[name] = std::make_shared<Timer>(Py_None, 1000, now);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target == NULL || target == Py_None)
|
|
||||||
{
|
|
||||||
std::cout << "Refusing to initialize timer to None. It's not an error, it's just pointless." << std::endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
timers[name] = std::make_shared<Timer>(target, interval, now);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GameEngine::testTimers()
|
void GameEngine::testTimers()
|
||||||
{
|
{
|
||||||
int now = runtime.getElapsedTime().asMilliseconds();
|
int now = headless ? simulation_time : runtime.getElapsedTime().asMilliseconds();
|
||||||
auto it = timers.begin();
|
auto it = timers.begin();
|
||||||
while (it != timers.end())
|
while (it != timers.end())
|
||||||
{
|
{
|
||||||
// Keep a local copy of the timer to prevent use-after-free.
|
// Keep a local copy of the timer to prevent use-after-free.
|
||||||
// If the callback calls delTimer(), the map entry gets replaced,
|
// If the callback calls stop(), the timer may be marked for removal,
|
||||||
// but we need the Timer object to survive until test() returns.
|
// but we need the Timer object to survive until test() returns.
|
||||||
auto timer = it->second;
|
auto timer = it->second;
|
||||||
timer->test(now);
|
|
||||||
|
|
||||||
// Remove timers that have been cancelled or are one-shot and fired.
|
// Skip stopped timers (they'll be removed below)
|
||||||
|
if (!timer->isStopped()) {
|
||||||
|
timer->test(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove timers that have been stopped (including one-shot timers that fired).
|
||||||
|
// The stopped flag is the authoritative marker for "remove from map".
|
||||||
// Note: Check it->second (current map value) in case callback replaced it.
|
// Note: Check it->second (current map value) in case callback replaced it.
|
||||||
if (!it->second->getCallback() || it->second->getCallback() == Py_None)
|
if (it->second->isStopped())
|
||||||
{
|
{
|
||||||
it = timers.erase(it);
|
it = timers.erase(it);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
it++;
|
it++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameEngine::processEvent(const sf::Event& event)
|
void GameEngine::processEvent(const sf::Event& event)
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ public:
|
||||||
int getFrame() { return currentFrame; }
|
int getFrame() { return currentFrame; }
|
||||||
float getFrameTime() { return frameTime; }
|
float getFrameTime() { return frameTime; }
|
||||||
sf::View getView() { return visible; }
|
sf::View getView() { return visible; }
|
||||||
void manageTimer(std::string, PyObject*, int);
|
// Note: manageTimer() removed in #173 - use Timer objects directly
|
||||||
std::shared_ptr<Timer> getTimer(const std::string& name);
|
std::shared_ptr<Timer> getTimer(const std::string& name);
|
||||||
void setWindowScale(float);
|
void setWindowScale(float);
|
||||||
bool isHeadless() const { return headless; }
|
bool isHeadless() const { return headless; }
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include "PySceneObject.h"
|
#include "PySceneObject.h"
|
||||||
#include "PyFOV.h"
|
#include "PyFOV.h"
|
||||||
#include "PyTransition.h"
|
#include "PyTransition.h"
|
||||||
|
#include "PyEasing.h"
|
||||||
#include "PySound.h"
|
#include "PySound.h"
|
||||||
#include "PyMusic.h"
|
#include "PyMusic.h"
|
||||||
#include "PyKeyboard.h"
|
#include "PyKeyboard.h"
|
||||||
|
|
@ -24,6 +25,7 @@
|
||||||
#include "GridLayers.h"
|
#include "GridLayers.h"
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
#include "PyScene.h"
|
#include "PyScene.h"
|
||||||
|
#include "PythonObjectCache.h"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <libtcod.h>
|
#include <libtcod.h>
|
||||||
|
|
@ -52,6 +54,10 @@ static PyObject* mcrfpy_module_getattr(PyObject* self, PyObject* args)
|
||||||
return McRFPy_API::api_get_scenes();
|
return McRFPy_API::api_get_scenes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strcmp(name, "timers") == 0) {
|
||||||
|
return McRFPy_API::api_get_timers();
|
||||||
|
}
|
||||||
|
|
||||||
if (strcmp(name, "default_transition") == 0) {
|
if (strcmp(name, "default_transition") == 0) {
|
||||||
return PyTransition::to_python(PyTransition::default_transition);
|
return PyTransition::to_python(PyTransition::default_transition);
|
||||||
}
|
}
|
||||||
|
|
@ -80,6 +86,11 @@ static int mcrfpy_module_setattro(PyObject* self, PyObject* name, PyObject* valu
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (strcmp(name_str, "timers") == 0) {
|
||||||
|
PyErr_SetString(PyExc_AttributeError, "'timers' is read-only");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (strcmp(name_str, "default_transition") == 0) {
|
if (strcmp(name_str, "default_transition") == 0) {
|
||||||
TransitionType trans;
|
TransitionType trans;
|
||||||
if (!PyTransition::from_arg(value, &trans, nullptr)) {
|
if (!PyTransition::from_arg(value, &trans, nullptr)) {
|
||||||
|
|
@ -138,26 +149,7 @@ static PyTypeObject McRFPyModuleType = {
|
||||||
|
|
||||||
static PyMethodDef mcrfpyMethods[] = {
|
static PyMethodDef mcrfpyMethods[] = {
|
||||||
|
|
||||||
{"setTimer", McRFPy_API::_setTimer, METH_VARARGS,
|
// Note: setTimer and delTimer removed in #173 - use Timer objects instead
|
||||||
MCRF_FUNCTION(setTimer,
|
|
||||||
MCRF_SIG("(name: str, handler: callable, interval: int)", "None"),
|
|
||||||
MCRF_DESC("Create or update a recurring timer."),
|
|
||||||
MCRF_ARGS_START
|
|
||||||
MCRF_ARG("name", "Unique identifier for the timer")
|
|
||||||
MCRF_ARG("handler", "Function called with (runtime: float) parameter")
|
|
||||||
MCRF_ARG("interval", "Time between calls in milliseconds")
|
|
||||||
MCRF_RETURNS("None")
|
|
||||||
MCRF_NOTE("If a timer with this name exists, it will be replaced. The handler receives the total runtime in seconds as its argument.")
|
|
||||||
)},
|
|
||||||
{"delTimer", McRFPy_API::_delTimer, METH_VARARGS,
|
|
||||||
MCRF_FUNCTION(delTimer,
|
|
||||||
MCRF_SIG("(name: str)", "None"),
|
|
||||||
MCRF_DESC("Stop and remove a timer."),
|
|
||||||
MCRF_ARGS_START
|
|
||||||
MCRF_ARG("name", "Timer identifier to remove")
|
|
||||||
MCRF_RETURNS("None")
|
|
||||||
MCRF_NOTE("No error is raised if the timer doesn't exist.")
|
|
||||||
)},
|
|
||||||
{"step", McRFPy_API::_step, METH_VARARGS,
|
{"step", McRFPy_API::_step, METH_VARARGS,
|
||||||
MCRF_FUNCTION(step,
|
MCRF_FUNCTION(step,
|
||||||
MCRF_SIG("(dt: float = None)", "float"),
|
MCRF_SIG("(dt: float = None)", "float"),
|
||||||
|
|
@ -438,6 +430,13 @@ PyObject* PyInit_mcrfpy()
|
||||||
// Note: default_transition and default_transition_duration are handled via
|
// Note: default_transition and default_transition_duration are handled via
|
||||||
// mcrfpy_module_getattr/setattro using PyTransition::default_transition/default_duration
|
// mcrfpy_module_getattr/setattro using PyTransition::default_transition/default_duration
|
||||||
|
|
||||||
|
// Add Easing enum class (uses Python's IntEnum)
|
||||||
|
PyObject* easing_class = PyEasing::create_enum_class(m);
|
||||||
|
if (!easing_class) {
|
||||||
|
// If enum creation fails, continue without it (non-fatal)
|
||||||
|
PyErr_Clear();
|
||||||
|
}
|
||||||
|
|
||||||
// Add automation submodule
|
// Add automation submodule
|
||||||
PyObject* automation_module = McRFPy_Automation::init_automation_module();
|
PyObject* automation_module = McRFPy_Automation::init_automation_module();
|
||||||
if (automation_module != NULL) {
|
if (automation_module != NULL) {
|
||||||
|
|
@ -883,22 +882,34 @@ PyObject* McRFPy_API::_setScene(PyObject* self, PyObject* args) {
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* McRFPy_API::_setTimer(PyObject* self, PyObject* args) { // TODO - compare with UIDrawable mouse & Scene Keyboard methods - inconsistent responsibility for incref/decref around mcrogueface
|
// #173: Get all timers as a tuple of Python Timer objects
|
||||||
const char* name;
|
PyObject* McRFPy_API::api_get_timers()
|
||||||
PyObject* callable;
|
{
|
||||||
int interval;
|
if (!game) {
|
||||||
if (!PyArg_ParseTuple(args, "sOi", &name, &callable, &interval)) return NULL;
|
return PyTuple_New(0);
|
||||||
game->manageTimer(name, callable, interval);
|
}
|
||||||
Py_INCREF(Py_None);
|
|
||||||
return Py_None;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject* McRFPy_API::_delTimer(PyObject* self, PyObject* args) {
|
// Count timers that have Python wrappers
|
||||||
const char* name;
|
std::vector<PyObject*> timer_objs;
|
||||||
if (!PyArg_ParseTuple(args, "s", &name)) return NULL;
|
for (auto& pair : game->timers) {
|
||||||
game->manageTimer(name, NULL, 0);
|
auto& timer = pair.second;
|
||||||
Py_INCREF(Py_None);
|
if (timer && timer->serial_number != 0) {
|
||||||
return Py_None;
|
PyObject* timer_obj = PythonObjectCache::getInstance().lookup(timer->serial_number);
|
||||||
|
if (timer_obj && timer_obj != Py_None) {
|
||||||
|
timer_objs.push_back(timer_obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* tuple = PyTuple_New(timer_objs.size());
|
||||||
|
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]);
|
||||||
|
PyTuple_SET_ITEM(tuple, i, timer_objs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tuple;
|
||||||
}
|
}
|
||||||
|
|
||||||
// #153 - Headless simulation control
|
// #153 - Headless simulation control
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,7 @@ public:
|
||||||
// Internal - used by PySceneObject::activate()
|
// Internal - used by PySceneObject::activate()
|
||||||
static PyObject* _setScene(PyObject*, PyObject*);
|
static PyObject* _setScene(PyObject*, PyObject*);
|
||||||
|
|
||||||
// timer control
|
// Note: setTimer/delTimer removed in #173 - use Timer objects instead
|
||||||
static PyObject* _setTimer(PyObject*, PyObject*);
|
|
||||||
static PyObject* _delTimer(PyObject*, PyObject*);
|
|
||||||
|
|
||||||
// #153 - Headless simulation control
|
// #153 - Headless simulation control
|
||||||
static PyObject* _step(PyObject*, PyObject*);
|
static PyObject* _step(PyObject*, PyObject*);
|
||||||
|
|
@ -88,6 +86,9 @@ public:
|
||||||
static int api_set_current_scene(PyObject* value);
|
static int api_set_current_scene(PyObject* value);
|
||||||
static PyObject* api_get_scenes();
|
static PyObject* api_get_scenes();
|
||||||
|
|
||||||
|
// #173: Module-level timer collection accessor
|
||||||
|
static PyObject* api_get_timers();
|
||||||
|
|
||||||
// Exception handling - signal game loop to exit on unhandled Python exceptions
|
// Exception handling - signal game loop to exit on unhandled Python exceptions
|
||||||
static std::atomic<bool> exception_occurred;
|
static std::atomic<bool> exception_occurred;
|
||||||
static std::atomic<int> exit_code;
|
static std::atomic<int> exit_code;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "PyAnimation.h"
|
#include "PyAnimation.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "McRFPy_Doc.h"
|
#include "McRFPy_Doc.h"
|
||||||
|
#include "PyEasing.h"
|
||||||
#include "UIDrawable.h"
|
#include "UIDrawable.h"
|
||||||
#include "UIFrame.h"
|
#include "UIFrame.h"
|
||||||
#include "UICaption.h"
|
#include "UICaption.h"
|
||||||
|
|
@ -24,12 +25,12 @@ int PyAnimation::init(PyAnimationObject* self, PyObject* args, PyObject* kwds) {
|
||||||
const char* property_name;
|
const char* property_name;
|
||||||
PyObject* target_value;
|
PyObject* target_value;
|
||||||
float duration;
|
float duration;
|
||||||
const char* easing_name = "linear";
|
PyObject* easing_arg = Py_None;
|
||||||
int delta = 0;
|
int delta = 0;
|
||||||
PyObject* callback = nullptr;
|
PyObject* callback = nullptr;
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOf|spO", const_cast<char**>(keywords),
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOf|OpO", const_cast<char**>(keywords),
|
||||||
&property_name, &target_value, &duration, &easing_name, &delta, &callback)) {
|
&property_name, &target_value, &duration, &easing_arg, &delta, &callback)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,8 +100,11 @@ int PyAnimation::init(PyAnimationObject* self, PyObject* args, PyObject* kwds) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get easing function
|
// Get easing function from argument (enum, string, int, or None)
|
||||||
EasingFunction easingFunc = EasingFunctions::getByName(easing_name);
|
EasingFunction easingFunc;
|
||||||
|
if (!PyEasing::from_arg(easing_arg, &easingFunc, nullptr)) {
|
||||||
|
return -1; // Error already set by from_arg
|
||||||
|
}
|
||||||
|
|
||||||
// Create the Animation
|
// Create the Animation
|
||||||
self->data = std::make_shared<Animation>(property_name, animValue, duration, easingFunc, delta != 0, callback);
|
self->data = std::make_shared<Animation>(property_name, animValue, duration, easingFunc, delta != 0, callback);
|
||||||
|
|
@ -113,6 +117,48 @@ void PyAnimation::dealloc(PyAnimationObject* self) {
|
||||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::repr(PyAnimationObject* self) {
|
||||||
|
if (!self->data) {
|
||||||
|
return PyUnicode_FromString("<Animation (uninitialized)>");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string property = self->data->getTargetProperty();
|
||||||
|
float duration = self->data->getDuration();
|
||||||
|
float elapsed = self->data->getElapsed();
|
||||||
|
bool complete = self->data->isComplete();
|
||||||
|
bool delta = self->data->isDelta();
|
||||||
|
bool hasTarget = self->data->hasValidTarget();
|
||||||
|
|
||||||
|
// Format: <Animation 'property' duration=2.0s elapsed=0.5s running>
|
||||||
|
// or: <Animation 'property' duration=2.0s complete>
|
||||||
|
// or: <Animation 'property' duration=2.0s delta complete>
|
||||||
|
// or: <Animation 'property' duration=2.0s (no target)>
|
||||||
|
|
||||||
|
std::string status;
|
||||||
|
if (!hasTarget) {
|
||||||
|
status = "(no target)";
|
||||||
|
} else if (complete) {
|
||||||
|
status = "complete";
|
||||||
|
} else {
|
||||||
|
char buf[32];
|
||||||
|
snprintf(buf, sizeof(buf), "elapsed=%.2fs", elapsed);
|
||||||
|
status = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
char result[256];
|
||||||
|
if (delta) {
|
||||||
|
snprintf(result, sizeof(result),
|
||||||
|
"<Animation '%s' duration=%.2fs delta %s>",
|
||||||
|
property.c_str(), duration, status.c_str());
|
||||||
|
} else {
|
||||||
|
snprintf(result, sizeof(result),
|
||||||
|
"<Animation '%s' duration=%.2fs %s>",
|
||||||
|
property.c_str(), duration, status.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
return PyUnicode_FromString(result);
|
||||||
|
}
|
||||||
|
|
||||||
PyObject* PyAnimation::get_property(PyAnimationObject* self, void* closure) {
|
PyObject* PyAnimation::get_property(PyAnimationObject* self, void* closure) {
|
||||||
return PyUnicode_FromString(self->data->getTargetProperty().c_str());
|
return PyUnicode_FromString(self->data->getTargetProperty().c_str());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ public:
|
||||||
static PyObject* create(PyTypeObject* type, PyObject* args, PyObject* kwds);
|
static PyObject* create(PyTypeObject* type, PyObject* args, PyObject* kwds);
|
||||||
static int init(PyAnimationObject* self, PyObject* args, PyObject* kwds);
|
static int init(PyAnimationObject* self, PyObject* args, PyObject* kwds);
|
||||||
static void dealloc(PyAnimationObject* self);
|
static void dealloc(PyAnimationObject* self);
|
||||||
|
static PyObject* repr(PyAnimationObject* self);
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
static PyObject* get_property(PyAnimationObject* self, void* closure);
|
static PyObject* get_property(PyAnimationObject* self, void* closure);
|
||||||
|
|
@ -42,8 +43,59 @@ namespace mcrfpydef {
|
||||||
.tp_basicsize = sizeof(PyAnimationObject),
|
.tp_basicsize = sizeof(PyAnimationObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
.tp_dealloc = (destructor)PyAnimation::dealloc,
|
.tp_dealloc = (destructor)PyAnimation::dealloc,
|
||||||
|
.tp_repr = (reprfunc)PyAnimation::repr,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("Animation object for animating UI properties"),
|
.tp_doc = PyDoc_STR(
|
||||||
|
"Animation(property: str, target: Any, duration: float, easing: str = 'linear', delta: bool = False, callback: Callable = None)\n"
|
||||||
|
"\n"
|
||||||
|
"Create an animation that interpolates a property value over time.\n"
|
||||||
|
"\n"
|
||||||
|
"Args:\n"
|
||||||
|
" property: Property name to animate. Valid properties depend on target type:\n"
|
||||||
|
" - Position/Size: 'x', 'y', 'w', 'h', 'pos', 'size'\n"
|
||||||
|
" - Appearance: 'fill_color', 'outline_color', 'outline', 'opacity'\n"
|
||||||
|
" - Sprite: 'sprite_index', 'sprite_number', 'scale'\n"
|
||||||
|
" - Grid: 'center', 'zoom'\n"
|
||||||
|
" - Caption: 'text'\n"
|
||||||
|
" - Sub-properties: 'fill_color.r', 'fill_color.g', 'fill_color.b', 'fill_color.a'\n"
|
||||||
|
" target: Target value for the animation. Type depends on property:\n"
|
||||||
|
" - float: For numeric properties (x, y, w, h, scale, opacity, zoom)\n"
|
||||||
|
" - int: For integer properties (sprite_index)\n"
|
||||||
|
" - tuple (r, g, b[, a]): For color properties\n"
|
||||||
|
" - tuple (x, y): For vector properties (pos, size, center)\n"
|
||||||
|
" - list[int]: For sprite animation sequences\n"
|
||||||
|
" - str: For text animation\n"
|
||||||
|
" duration: Animation duration in seconds.\n"
|
||||||
|
" easing: Easing function name. Options:\n"
|
||||||
|
" - 'linear' (default)\n"
|
||||||
|
" - 'easeIn', 'easeOut', 'easeInOut'\n"
|
||||||
|
" - 'easeInQuad', 'easeOutQuad', 'easeInOutQuad'\n"
|
||||||
|
" - 'easeInCubic', 'easeOutCubic', 'easeInOutCubic'\n"
|
||||||
|
" - 'easeInQuart', 'easeOutQuart', 'easeInOutQuart'\n"
|
||||||
|
" - 'easeInSine', 'easeOutSine', 'easeInOutSine'\n"
|
||||||
|
" - 'easeInExpo', 'easeOutExpo', 'easeInOutExpo'\n"
|
||||||
|
" - 'easeInCirc', 'easeOutCirc', 'easeInOutCirc'\n"
|
||||||
|
" - 'easeInElastic', 'easeOutElastic', 'easeInOutElastic'\n"
|
||||||
|
" - 'easeInBack', 'easeOutBack', 'easeInOutBack'\n"
|
||||||
|
" - 'easeInBounce', 'easeOutBounce', 'easeInOutBounce'\n"
|
||||||
|
" delta: If True, target is relative to start value (additive). Default False.\n"
|
||||||
|
" callback: Function(animation, target) called when animation completes.\n"
|
||||||
|
"\n"
|
||||||
|
"Example:\n"
|
||||||
|
" # Move a frame from current position to x=500 over 2 seconds\n"
|
||||||
|
" anim = mcrfpy.Animation('x', 500.0, 2.0, 'easeInOut')\n"
|
||||||
|
" anim.start(my_frame)\n"
|
||||||
|
"\n"
|
||||||
|
" # Fade out with callback\n"
|
||||||
|
" def on_done(anim, target):\n"
|
||||||
|
" print('Animation complete!')\n"
|
||||||
|
" fade = mcrfpy.Animation('fill_color.a', 0, 1.0, callback=on_done)\n"
|
||||||
|
" fade.start(my_sprite)\n"
|
||||||
|
"\n"
|
||||||
|
" # Animate through sprite frames\n"
|
||||||
|
" walk_cycle = mcrfpy.Animation('sprite_index', [0,1,2,3,2,1], 0.5, 'linear')\n"
|
||||||
|
" walk_cycle.start(my_entity)\n"
|
||||||
|
),
|
||||||
.tp_methods = PyAnimation::methods,
|
.tp_methods = PyAnimation::methods,
|
||||||
.tp_getset = PyAnimation::getsetters,
|
.tp_getset = PyAnimation::getsetters,
|
||||||
.tp_init = (initproc)PyAnimation::init,
|
.tp_init = (initproc)PyAnimation::init,
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,34 @@ namespace mcrfpydef {
|
||||||
.tp_repr = PyColor::repr,
|
.tp_repr = PyColor::repr,
|
||||||
.tp_hash = PyColor::hash,
|
.tp_hash = PyColor::hash,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("SFML Color Object"),
|
.tp_doc = PyDoc_STR(
|
||||||
|
"Color(r: int = 0, g: int = 0, b: int = 0, a: int = 255)\n"
|
||||||
|
"\n"
|
||||||
|
"RGBA color representation.\n"
|
||||||
|
"\n"
|
||||||
|
"Args:\n"
|
||||||
|
" r: Red component (0-255)\n"
|
||||||
|
" g: Green component (0-255)\n"
|
||||||
|
" b: Blue component (0-255)\n"
|
||||||
|
" a: Alpha component (0-255, default 255 = opaque)\n"
|
||||||
|
"\n"
|
||||||
|
"Note:\n"
|
||||||
|
" When accessing colors from UI elements (e.g., frame.fill_color),\n"
|
||||||
|
" you receive a COPY of the color. Modifying it doesn't affect the\n"
|
||||||
|
" original. To change a component:\n"
|
||||||
|
"\n"
|
||||||
|
" # This does NOT work:\n"
|
||||||
|
" frame.fill_color.r = 255 # Modifies a temporary copy\n"
|
||||||
|
"\n"
|
||||||
|
" # Do this instead:\n"
|
||||||
|
" c = frame.fill_color\n"
|
||||||
|
" c.r = 255\n"
|
||||||
|
" frame.fill_color = c\n"
|
||||||
|
"\n"
|
||||||
|
" # Or use Animation for sub-properties:\n"
|
||||||
|
" anim = mcrfpy.Animation('fill_color.r', 255, 0.5, 'linear')\n"
|
||||||
|
" anim.start(frame)\n"
|
||||||
|
),
|
||||||
.tp_methods = PyColor::methods,
|
.tp_methods = PyColor::methods,
|
||||||
.tp_getset = PyColor::getsetters,
|
.tp_getset = PyColor::getsetters,
|
||||||
.tp_init = (initproc)PyColor::init,
|
.tp_init = (initproc)PyColor::init,
|
||||||
|
|
|
||||||
228
src/PyEasing.cpp
Normal file
228
src/PyEasing.cpp
Normal file
|
|
@ -0,0 +1,228 @@
|
||||||
|
#include "PyEasing.h"
|
||||||
|
#include "McRFPy_API.h"
|
||||||
|
|
||||||
|
// Static storage for cached enum class reference
|
||||||
|
PyObject* PyEasing::easing_enum_class = nullptr;
|
||||||
|
|
||||||
|
// Easing function table - maps enum value to function and name
|
||||||
|
struct EasingEntry {
|
||||||
|
const char* name;
|
||||||
|
int value;
|
||||||
|
EasingFunction func;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const EasingEntry easing_table[] = {
|
||||||
|
{"LINEAR", 0, EasingFunctions::linear},
|
||||||
|
{"EASE_IN", 1, EasingFunctions::easeIn},
|
||||||
|
{"EASE_OUT", 2, EasingFunctions::easeOut},
|
||||||
|
{"EASE_IN_OUT", 3, EasingFunctions::easeInOut},
|
||||||
|
{"EASE_IN_QUAD", 4, EasingFunctions::easeInQuad},
|
||||||
|
{"EASE_OUT_QUAD", 5, EasingFunctions::easeOutQuad},
|
||||||
|
{"EASE_IN_OUT_QUAD", 6, EasingFunctions::easeInOutQuad},
|
||||||
|
{"EASE_IN_CUBIC", 7, EasingFunctions::easeInCubic},
|
||||||
|
{"EASE_OUT_CUBIC", 8, EasingFunctions::easeOutCubic},
|
||||||
|
{"EASE_IN_OUT_CUBIC", 9, EasingFunctions::easeInOutCubic},
|
||||||
|
{"EASE_IN_QUART", 10, EasingFunctions::easeInQuart},
|
||||||
|
{"EASE_OUT_QUART", 11, EasingFunctions::easeOutQuart},
|
||||||
|
{"EASE_IN_OUT_QUART", 12, EasingFunctions::easeInOutQuart},
|
||||||
|
{"EASE_IN_SINE", 13, EasingFunctions::easeInSine},
|
||||||
|
{"EASE_OUT_SINE", 14, EasingFunctions::easeOutSine},
|
||||||
|
{"EASE_IN_OUT_SINE", 15, EasingFunctions::easeInOutSine},
|
||||||
|
{"EASE_IN_EXPO", 16, EasingFunctions::easeInExpo},
|
||||||
|
{"EASE_OUT_EXPO", 17, EasingFunctions::easeOutExpo},
|
||||||
|
{"EASE_IN_OUT_EXPO", 18, EasingFunctions::easeInOutExpo},
|
||||||
|
{"EASE_IN_CIRC", 19, EasingFunctions::easeInCirc},
|
||||||
|
{"EASE_OUT_CIRC", 20, EasingFunctions::easeOutCirc},
|
||||||
|
{"EASE_IN_OUT_CIRC", 21, EasingFunctions::easeInOutCirc},
|
||||||
|
{"EASE_IN_ELASTIC", 22, EasingFunctions::easeInElastic},
|
||||||
|
{"EASE_OUT_ELASTIC", 23, EasingFunctions::easeOutElastic},
|
||||||
|
{"EASE_IN_OUT_ELASTIC", 24, EasingFunctions::easeInOutElastic},
|
||||||
|
{"EASE_IN_BACK", 25, EasingFunctions::easeInBack},
|
||||||
|
{"EASE_OUT_BACK", 26, EasingFunctions::easeOutBack},
|
||||||
|
{"EASE_IN_OUT_BACK", 27, EasingFunctions::easeInOutBack},
|
||||||
|
{"EASE_IN_BOUNCE", 28, EasingFunctions::easeInBounce},
|
||||||
|
{"EASE_OUT_BOUNCE", 29, EasingFunctions::easeOutBounce},
|
||||||
|
{"EASE_IN_OUT_BOUNCE", 30, EasingFunctions::easeInOutBounce},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Old string names (for backwards compatibility)
|
||||||
|
static const char* legacy_names[] = {
|
||||||
|
"linear", "easeIn", "easeOut", "easeInOut",
|
||||||
|
"easeInQuad", "easeOutQuad", "easeInOutQuad",
|
||||||
|
"easeInCubic", "easeOutCubic", "easeInOutCubic",
|
||||||
|
"easeInQuart", "easeOutQuart", "easeInOutQuart",
|
||||||
|
"easeInSine", "easeOutSine", "easeInOutSine",
|
||||||
|
"easeInExpo", "easeOutExpo", "easeInOutExpo",
|
||||||
|
"easeInCirc", "easeOutCirc", "easeInOutCirc",
|
||||||
|
"easeInElastic", "easeOutElastic", "easeInOutElastic",
|
||||||
|
"easeInBack", "easeOutBack", "easeInOutBack",
|
||||||
|
"easeInBounce", "easeOutBounce", "easeInOutBounce"
|
||||||
|
};
|
||||||
|
|
||||||
|
static const int NUM_EASING_ENTRIES = sizeof(easing_table) / sizeof(easing_table[0]);
|
||||||
|
|
||||||
|
const char* PyEasing::easing_name(int value) {
|
||||||
|
if (value >= 0 && value < NUM_EASING_ENTRIES) {
|
||||||
|
return easing_table[value].name;
|
||||||
|
}
|
||||||
|
return "LINEAR";
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyEasing::create_enum_class(PyObject* module) {
|
||||||
|
// Import IntEnum from enum module
|
||||||
|
PyObject* enum_module = PyImport_ImportModule("enum");
|
||||||
|
if (!enum_module) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* int_enum = PyObject_GetAttrString(enum_module, "IntEnum");
|
||||||
|
Py_DECREF(enum_module);
|
||||||
|
if (!int_enum) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create dict of enum members
|
||||||
|
PyObject* members = PyDict_New();
|
||||||
|
if (!members) {
|
||||||
|
Py_DECREF(int_enum);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all easing function members
|
||||||
|
for (int i = 0; i < NUM_EASING_ENTRIES; i++) {
|
||||||
|
PyObject* value = PyLong_FromLong(easing_table[i].value);
|
||||||
|
if (!value) {
|
||||||
|
Py_DECREF(members);
|
||||||
|
Py_DECREF(int_enum);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (PyDict_SetItemString(members, easing_table[i].name, value) < 0) {
|
||||||
|
Py_DECREF(value);
|
||||||
|
Py_DECREF(members);
|
||||||
|
Py_DECREF(int_enum);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_DECREF(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call IntEnum("Easing", members) to create the enum class
|
||||||
|
PyObject* name = PyUnicode_FromString("Easing");
|
||||||
|
if (!name) {
|
||||||
|
Py_DECREF(members);
|
||||||
|
Py_DECREF(int_enum);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// IntEnum(name, members) using functional API
|
||||||
|
PyObject* args = PyTuple_Pack(2, name, members);
|
||||||
|
Py_DECREF(name);
|
||||||
|
Py_DECREF(members);
|
||||||
|
if (!args) {
|
||||||
|
Py_DECREF(int_enum);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* easing_class = PyObject_Call(int_enum, args, NULL);
|
||||||
|
Py_DECREF(args);
|
||||||
|
Py_DECREF(int_enum);
|
||||||
|
|
||||||
|
if (!easing_class) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the reference for fast type checking
|
||||||
|
easing_enum_class = easing_class;
|
||||||
|
Py_INCREF(easing_enum_class);
|
||||||
|
|
||||||
|
// Add to module
|
||||||
|
if (PyModule_AddObject(module, "Easing", easing_class) < 0) {
|
||||||
|
Py_DECREF(easing_class);
|
||||||
|
easing_enum_class = nullptr;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return easing_class;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PyEasing::from_arg(PyObject* arg, EasingFunction* out_func, bool* was_none) {
|
||||||
|
if (was_none) *was_none = false;
|
||||||
|
|
||||||
|
// Accept None -> default to linear
|
||||||
|
if (arg == Py_None) {
|
||||||
|
if (was_none) *was_none = true;
|
||||||
|
*out_func = EasingFunctions::linear;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept Easing enum member (check if it's an instance of our enum)
|
||||||
|
if (easing_enum_class && PyObject_IsInstance(arg, easing_enum_class)) {
|
||||||
|
// IntEnum members have a 'value' attribute
|
||||||
|
PyObject* value = PyObject_GetAttrString(arg, "value");
|
||||||
|
if (!value) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
long val = PyLong_AsLong(value);
|
||||||
|
Py_DECREF(value);
|
||||||
|
if (val == -1 && PyErr_Occurred()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (val >= 0 && val < NUM_EASING_ENTRIES) {
|
||||||
|
*out_func = easing_table[val].func;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"Invalid Easing value: %ld. Must be 0-%d.", val, NUM_EASING_ENTRIES - 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept int (for backwards compatibility and direct enum value access)
|
||||||
|
if (PyLong_Check(arg)) {
|
||||||
|
long val = PyLong_AsLong(arg);
|
||||||
|
if (val == -1 && PyErr_Occurred()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (val >= 0 && val < NUM_EASING_ENTRIES) {
|
||||||
|
*out_func = easing_table[val].func;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"Invalid easing value: %ld. Must be 0-%d or use mcrfpy.Easing enum.",
|
||||||
|
val, NUM_EASING_ENTRIES - 1);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept string (for backwards compatibility)
|
||||||
|
if (PyUnicode_Check(arg)) {
|
||||||
|
const char* name = PyUnicode_AsUTF8(arg);
|
||||||
|
if (!name) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check legacy string names first
|
||||||
|
for (int i = 0; i < NUM_EASING_ENTRIES; i++) {
|
||||||
|
if (strcmp(name, legacy_names[i]) == 0) {
|
||||||
|
*out_func = easing_table[i].func;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check enum-style names (EASE_IN_OUT, etc.)
|
||||||
|
for (int i = 0; i < NUM_EASING_ENTRIES; i++) {
|
||||||
|
if (strcmp(name, easing_table[i].name) == 0) {
|
||||||
|
*out_func = easing_table[i].func;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build error message with available options
|
||||||
|
PyErr_Format(PyExc_ValueError,
|
||||||
|
"Unknown easing function: '%s'. Use mcrfpy.Easing enum (e.g., Easing.EASE_IN_OUT) "
|
||||||
|
"or legacy string names: 'linear', 'easeIn', 'easeOut', 'easeInOut', 'easeInQuad', etc.",
|
||||||
|
name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"Easing must be mcrfpy.Easing enum member, string, int, or None");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
29
src/PyEasing.h
Normal file
29
src/PyEasing.h
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
#include "Common.h"
|
||||||
|
#include "Python.h"
|
||||||
|
#include "Animation.h"
|
||||||
|
|
||||||
|
// Module-level Easing enum class (created at runtime using Python's IntEnum)
|
||||||
|
// Stored as a module attribute: mcrfpy.Easing
|
||||||
|
|
||||||
|
class PyEasing {
|
||||||
|
public:
|
||||||
|
// Create the Easing enum class and add to module
|
||||||
|
// Returns the enum class (new reference), or NULL on error
|
||||||
|
static PyObject* create_enum_class(PyObject* module);
|
||||||
|
|
||||||
|
// Helper to extract easing function from Python arg
|
||||||
|
// Accepts Easing enum, string (for backwards compatibility), int, or None
|
||||||
|
// Returns 1 on success, 0 on error (with exception set)
|
||||||
|
// If arg is None, sets *out_func to linear and sets *was_none to true
|
||||||
|
static int from_arg(PyObject* arg, EasingFunction* out_func, bool* was_none = nullptr);
|
||||||
|
|
||||||
|
// Convert easing enum value to string name
|
||||||
|
static const char* easing_name(int value);
|
||||||
|
|
||||||
|
// Cached reference to the Easing enum class for fast type checking
|
||||||
|
static PyObject* easing_enum_class;
|
||||||
|
|
||||||
|
// Number of easing functions
|
||||||
|
static const int NUM_EASING_FUNCTIONS = 32;
|
||||||
|
};
|
||||||
188
src/PyTimer.cpp
188
src/PyTimer.cpp
|
|
@ -16,7 +16,9 @@ PyObject* PyTimer::repr(PyObject* self) {
|
||||||
if (timer->data->isOnce()) {
|
if (timer->data->isOnce()) {
|
||||||
oss << "once=True ";
|
oss << "once=True ";
|
||||||
}
|
}
|
||||||
if (timer->data->isPaused()) {
|
if (timer->data->isStopped()) {
|
||||||
|
oss << "stopped";
|
||||||
|
} else if (timer->data->isPaused()) {
|
||||||
oss << "paused";
|
oss << "paused";
|
||||||
// Get current time to show remaining
|
// Get current time to show remaining
|
||||||
int current_time = 0;
|
int current_time = 0;
|
||||||
|
|
@ -25,9 +27,9 @@ PyObject* PyTimer::repr(PyObject* self) {
|
||||||
}
|
}
|
||||||
oss << " (remaining=" << timer->data->getRemaining(current_time) << "ms)";
|
oss << " (remaining=" << timer->data->getRemaining(current_time) << "ms)";
|
||||||
} else if (timer->data->isActive()) {
|
} else if (timer->data->isActive()) {
|
||||||
oss << "active";
|
oss << "running";
|
||||||
} else {
|
} else {
|
||||||
oss << "cancelled";
|
oss << "inactive";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
oss << "uninitialized";
|
oss << "uninitialized";
|
||||||
|
|
@ -48,14 +50,15 @@ PyObject* PyTimer::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int PyTimer::init(PyTimerObject* self, PyObject* args, PyObject* kwds) {
|
int PyTimer::init(PyTimerObject* self, PyObject* args, PyObject* kwds) {
|
||||||
static const char* kwlist[] = {"name", "callback", "interval", "once", NULL};
|
static const char* kwlist[] = {"name", "callback", "interval", "once", "start", NULL};
|
||||||
const char* name = nullptr;
|
const char* name = nullptr;
|
||||||
PyObject* callback = nullptr;
|
PyObject* callback = nullptr;
|
||||||
int interval = 0;
|
int interval = 0;
|
||||||
int once = 0; // Use int for bool parameter
|
int once = 0; // Use int for bool parameter
|
||||||
|
int start = 1; // Default: start=True
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOi|p", const_cast<char**>(kwlist),
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOi|pp", const_cast<char**>(kwlist),
|
||||||
&name, &callback, &interval, &once)) {
|
&name, &callback, &interval, &once, &start)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,8 +80,8 @@ int PyTimer::init(PyTimerObject* self, PyObject* args, PyObject* kwds) {
|
||||||
current_time = Resources::game->runtime.getElapsedTime().asMilliseconds();
|
current_time = Resources::game->runtime.getElapsedTime().asMilliseconds();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the timer
|
// Create the timer with start parameter
|
||||||
self->data = std::make_shared<Timer>(callback, interval, current_time, (bool)once);
|
self->data = std::make_shared<Timer>(callback, interval, current_time, (bool)once, (bool)start);
|
||||||
|
|
||||||
// Register in Python object cache
|
// Register in Python object cache
|
||||||
if (self->data->serial_number == 0) {
|
if (self->data->serial_number == 0) {
|
||||||
|
|
@ -90,8 +93,13 @@ int PyTimer::init(PyTimerObject* self, PyObject* args, PyObject* kwds) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register with game engine
|
// Register with game engine only if starting
|
||||||
if (Resources::game) {
|
if (Resources::game && start) {
|
||||||
|
// If a timer with this name already exists, stop it first
|
||||||
|
auto it = Resources::game->timers.find(self->name);
|
||||||
|
if (it != Resources::game->timers.end() && it->second != self->data) {
|
||||||
|
it->second->stop();
|
||||||
|
}
|
||||||
Resources::game->timers[self->name] = self->data;
|
Resources::game->timers[self->name] = self->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,13 +123,56 @@ void PyTimer::dealloc(PyTimerObject* self) {
|
||||||
// Explicitly destroy std::string
|
// Explicitly destroy std::string
|
||||||
self->name.~basic_string();
|
self->name.~basic_string();
|
||||||
|
|
||||||
// Clear shared_ptr
|
// Clear shared_ptr - this is the only place that truly destroys the Timer
|
||||||
self->data.reset();
|
self->data.reset();
|
||||||
|
|
||||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timer control methods
|
// Timer control methods
|
||||||
|
PyObject* PyTimer::start(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();
|
||||||
|
|
||||||
|
// If another timer has this name, stop it first
|
||||||
|
auto it = Resources::game->timers.find(self->name);
|
||||||
|
if (it != Resources::game->timers.end() && it->second != self->data) {
|
||||||
|
it->second->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to engine map
|
||||||
|
Resources::game->timers[self->name] = self->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->data->start(current_time);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyTimer::stop(PyTimerObject* self, PyObject* Py_UNUSED(ignored)) {
|
||||||
|
if (!self->data) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Timer not initialized");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from game engine map (but preserve the Timer data!)
|
||||||
|
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->stop();
|
||||||
|
// NOTE: We do NOT reset self->data here - the timer can be restarted
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
PyObject* PyTimer::pause(PyTimerObject* self, PyObject* Py_UNUSED(ignored)) {
|
PyObject* PyTimer::pause(PyTimerObject* self, PyObject* Py_UNUSED(ignored)) {
|
||||||
if (!self->data) {
|
if (!self->data) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Timer not initialized");
|
PyErr_SetString(PyExc_RuntimeError, "Timer not initialized");
|
||||||
|
|
@ -152,25 +203,6 @@ PyObject* PyTimer::resume(PyTimerObject* self, PyObject* Py_UNUSED(ignored)) {
|
||||||
Py_RETURN_NONE;
|
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)) {
|
PyObject* PyTimer::restart(PyTimerObject* self, PyObject* Py_UNUSED(ignored)) {
|
||||||
if (!self->data) {
|
if (!self->data) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Timer not initialized");
|
PyErr_SetString(PyExc_RuntimeError, "Timer not initialized");
|
||||||
|
|
@ -180,6 +212,17 @@ PyObject* PyTimer::restart(PyTimerObject* self, PyObject* Py_UNUSED(ignored)) {
|
||||||
int current_time = 0;
|
int current_time = 0;
|
||||||
if (Resources::game) {
|
if (Resources::game) {
|
||||||
current_time = Resources::game->runtime.getElapsedTime().asMilliseconds();
|
current_time = Resources::game->runtime.getElapsedTime().asMilliseconds();
|
||||||
|
|
||||||
|
// Ensure timer is in engine map
|
||||||
|
auto it = Resources::game->timers.find(self->name);
|
||||||
|
if (it == Resources::game->timers.end()) {
|
||||||
|
// Timer was stopped, re-add it
|
||||||
|
Resources::game->timers[self->name] = self->data;
|
||||||
|
} else if (it->second != self->data) {
|
||||||
|
// Another timer has this name, stop it and replace
|
||||||
|
it->second->stop();
|
||||||
|
Resources::game->timers[self->name] = self->data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self->data->restart(current_time);
|
self->data->restart(current_time);
|
||||||
|
|
@ -240,6 +283,14 @@ PyObject* PyTimer::get_paused(PyTimerObject* self, void* closure) {
|
||||||
return PyBool_FromLong(self->data->isPaused());
|
return PyBool_FromLong(self->data->isPaused());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject* PyTimer::get_stopped(PyTimerObject* self, void* closure) {
|
||||||
|
if (!self->data) {
|
||||||
|
return Py_True; // Uninitialized is effectively stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
return PyBool_FromLong(self->data->isStopped());
|
||||||
|
}
|
||||||
|
|
||||||
PyObject* PyTimer::get_active(PyTimerObject* self, void* closure) {
|
PyObject* PyTimer::get_active(PyTimerObject* self, void* closure) {
|
||||||
if (!self->data) {
|
if (!self->data) {
|
||||||
return Py_False;
|
return Py_False;
|
||||||
|
|
@ -248,6 +299,46 @@ PyObject* PyTimer::get_active(PyTimerObject* self, void* closure) {
|
||||||
return PyBool_FromLong(self->data->isActive());
|
return PyBool_FromLong(self->data->isActive());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int PyTimer::set_active(PyTimerObject* self, PyObject* value, void* closure) {
|
||||||
|
if (!self->data) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Timer not initialized");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool want_active = PyObject_IsTrue(value);
|
||||||
|
|
||||||
|
int current_time = 0;
|
||||||
|
if (Resources::game) {
|
||||||
|
current_time = Resources::game->runtime.getElapsedTime().asMilliseconds();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (want_active) {
|
||||||
|
if (self->data->isStopped()) {
|
||||||
|
// Reactivate a stopped timer
|
||||||
|
if (Resources::game) {
|
||||||
|
// Handle name collision
|
||||||
|
auto it = Resources::game->timers.find(self->name);
|
||||||
|
if (it != Resources::game->timers.end() && it->second != self->data) {
|
||||||
|
it->second->stop();
|
||||||
|
}
|
||||||
|
Resources::game->timers[self->name] = self->data;
|
||||||
|
}
|
||||||
|
self->data->start(current_time);
|
||||||
|
} else if (self->data->isPaused()) {
|
||||||
|
// Resume from pause
|
||||||
|
self->data->resume(current_time);
|
||||||
|
}
|
||||||
|
// If already running, do nothing
|
||||||
|
} else {
|
||||||
|
// Setting active=False means pause
|
||||||
|
if (!self->data->isPaused() && !self->data->isStopped()) {
|
||||||
|
self->data->pause(current_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
PyObject* PyTimer::get_callback(PyTimerObject* self, void* closure) {
|
PyObject* PyTimer::get_callback(PyTimerObject* self, void* closure) {
|
||||||
if (!self->data) {
|
if (!self->data) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Timer not initialized");
|
PyErr_SetString(PyExc_RuntimeError, "Timer not initialized");
|
||||||
|
|
@ -312,19 +403,35 @@ PyGetSetDef PyTimer::getsetters[] = {
|
||||||
{"interval", (getter)PyTimer::get_interval, (setter)PyTimer::set_interval,
|
{"interval", (getter)PyTimer::get_interval, (setter)PyTimer::set_interval,
|
||||||
MCRF_PROPERTY(interval, "Timer interval in milliseconds (int). Must be positive. Can be changed while timer is running."), NULL},
|
MCRF_PROPERTY(interval, "Timer interval in milliseconds (int). Must be positive. Can be changed while timer is running."), NULL},
|
||||||
{"remaining", (getter)PyTimer::get_remaining, NULL,
|
{"remaining", (getter)PyTimer::get_remaining, NULL,
|
||||||
MCRF_PROPERTY(remaining, "Time remaining until next trigger in milliseconds (int, read-only). Preserved when timer is paused."), NULL},
|
MCRF_PROPERTY(remaining, "Time remaining until next trigger in milliseconds (int, read-only). Full interval when stopped."), NULL},
|
||||||
{"paused", (getter)PyTimer::get_paused, NULL,
|
{"paused", (getter)PyTimer::get_paused, NULL,
|
||||||
MCRF_PROPERTY(paused, "Whether the timer is paused (bool, read-only). Paused timers preserve their remaining time."), NULL},
|
MCRF_PROPERTY(paused, "Whether the timer is paused (bool, read-only). Paused timers preserve their remaining time."), NULL},
|
||||||
{"active", (getter)PyTimer::get_active, NULL,
|
{"stopped", (getter)PyTimer::get_stopped, NULL,
|
||||||
MCRF_PROPERTY(active, "Whether the timer is active and not paused (bool, read-only). False if cancelled or paused."), NULL},
|
MCRF_PROPERTY(stopped, "Whether the timer is stopped (bool, read-only). Stopped timers are not in the engine tick loop but preserve their callback."), NULL},
|
||||||
|
{"active", (getter)PyTimer::get_active, (setter)PyTimer::set_active,
|
||||||
|
MCRF_PROPERTY(active, "Running state (bool, read-write). True if running (not paused, not stopped). Set True to start/resume, False to pause."), NULL},
|
||||||
{"callback", (getter)PyTimer::get_callback, (setter)PyTimer::set_callback,
|
{"callback", (getter)PyTimer::get_callback, (setter)PyTimer::set_callback,
|
||||||
MCRF_PROPERTY(callback, "The callback function to be called when timer fires (callable). Can be changed while timer is running."), NULL},
|
MCRF_PROPERTY(callback, "The callback function (callable). Preserved when stopped, allowing timer restart."), NULL},
|
||||||
{"once", (getter)PyTimer::get_once, (setter)PyTimer::set_once,
|
{"once", (getter)PyTimer::get_once, (setter)PyTimer::set_once,
|
||||||
MCRF_PROPERTY(once, "Whether the timer stops after firing once (bool). If False, timer repeats indefinitely."), NULL},
|
MCRF_PROPERTY(once, "Whether the timer stops after firing once (bool). One-shot timers can be restarted."), NULL},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
PyMethodDef PyTimer::methods[] = {
|
PyMethodDef PyTimer::methods[] = {
|
||||||
|
{"start", (PyCFunction)PyTimer::start, METH_NOARGS,
|
||||||
|
MCRF_METHOD(Timer, start,
|
||||||
|
MCRF_SIG("()", "None"),
|
||||||
|
MCRF_DESC("Start the timer, adding it to the engine tick loop."),
|
||||||
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("Resets progress and begins counting toward the next fire. If another timer has this name, it will be stopped.")
|
||||||
|
)},
|
||||||
|
{"stop", (PyCFunction)PyTimer::stop, METH_NOARGS,
|
||||||
|
MCRF_METHOD(Timer, stop,
|
||||||
|
MCRF_SIG("()", "None"),
|
||||||
|
MCRF_DESC("Stop the timer and remove it from the engine tick loop."),
|
||||||
|
MCRF_RETURNS("None")
|
||||||
|
MCRF_NOTE("The callback is preserved, so the timer can be restarted with start() or restart().")
|
||||||
|
)},
|
||||||
{"pause", (PyCFunction)PyTimer::pause, METH_NOARGS,
|
{"pause", (PyCFunction)PyTimer::pause, METH_NOARGS,
|
||||||
MCRF_METHOD(Timer, pause,
|
MCRF_METHOD(Timer, pause,
|
||||||
MCRF_SIG("()", "None"),
|
MCRF_SIG("()", "None"),
|
||||||
|
|
@ -339,19 +446,12 @@ PyMethodDef PyTimer::methods[] = {
|
||||||
MCRF_RETURNS("None")
|
MCRF_RETURNS("None")
|
||||||
MCRF_NOTE("Has no effect if the timer is not paused. Timer will fire after the remaining time elapses.")
|
MCRF_NOTE("Has no effect if the timer is not paused. Timer will fire after the remaining time elapses.")
|
||||||
)},
|
)},
|
||||||
{"cancel", (PyCFunction)PyTimer::cancel, METH_NOARGS,
|
|
||||||
MCRF_METHOD(Timer, cancel,
|
|
||||||
MCRF_SIG("()", "None"),
|
|
||||||
MCRF_DESC("Cancel the timer and remove it from the timer system."),
|
|
||||||
MCRF_RETURNS("None")
|
|
||||||
MCRF_NOTE("The timer will no longer fire and cannot be restarted. The callback will not be called again.")
|
|
||||||
)},
|
|
||||||
{"restart", (PyCFunction)PyTimer::restart, METH_NOARGS,
|
{"restart", (PyCFunction)PyTimer::restart, METH_NOARGS,
|
||||||
MCRF_METHOD(Timer, restart,
|
MCRF_METHOD(Timer, restart,
|
||||||
MCRF_SIG("()", "None"),
|
MCRF_SIG("()", "None"),
|
||||||
MCRF_DESC("Restart the timer from the beginning."),
|
MCRF_DESC("Restart the timer from the beginning and ensure it's running."),
|
||||||
MCRF_RETURNS("None")
|
MCRF_RETURNS("None")
|
||||||
MCRF_NOTE("Resets the timer to fire after a full interval from now, regardless of remaining time.")
|
MCRF_NOTE("Resets progress and adds timer to engine if stopped. Equivalent to stop() followed by start().")
|
||||||
)},
|
)},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
@ -23,9 +23,10 @@ public:
|
||||||
static void dealloc(PyTimerObject* self);
|
static void dealloc(PyTimerObject* self);
|
||||||
|
|
||||||
// Timer control methods
|
// Timer control methods
|
||||||
|
static PyObject* start(PyTimerObject* self, PyObject* Py_UNUSED(ignored));
|
||||||
|
static PyObject* stop(PyTimerObject* self, PyObject* Py_UNUSED(ignored));
|
||||||
static PyObject* pause(PyTimerObject* self, PyObject* Py_UNUSED(ignored));
|
static PyObject* pause(PyTimerObject* self, PyObject* Py_UNUSED(ignored));
|
||||||
static PyObject* resume(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));
|
static PyObject* restart(PyTimerObject* self, PyObject* Py_UNUSED(ignored));
|
||||||
|
|
||||||
// Timer property getters
|
// Timer property getters
|
||||||
|
|
@ -34,7 +35,9 @@ public:
|
||||||
static int set_interval(PyTimerObject* self, PyObject* value, void* closure);
|
static int set_interval(PyTimerObject* self, PyObject* value, void* closure);
|
||||||
static PyObject* get_remaining(PyTimerObject* self, void* closure);
|
static PyObject* get_remaining(PyTimerObject* self, void* closure);
|
||||||
static PyObject* get_paused(PyTimerObject* self, void* closure);
|
static PyObject* get_paused(PyTimerObject* self, void* closure);
|
||||||
|
static PyObject* get_stopped(PyTimerObject* self, void* closure);
|
||||||
static PyObject* get_active(PyTimerObject* self, void* closure);
|
static PyObject* get_active(PyTimerObject* self, void* closure);
|
||||||
|
static int set_active(PyTimerObject* self, PyObject* value, void* closure);
|
||||||
static PyObject* get_callback(PyTimerObject* self, void* closure);
|
static PyObject* get_callback(PyTimerObject* self, void* closure);
|
||||||
static int set_callback(PyTimerObject* self, PyObject* value, void* closure);
|
static int set_callback(PyTimerObject* self, PyObject* value, void* closure);
|
||||||
static PyObject* get_once(PyTimerObject* self, void* closure);
|
static PyObject* get_once(PyTimerObject* self, void* closure);
|
||||||
|
|
@ -53,35 +56,39 @@ namespace mcrfpydef {
|
||||||
.tp_dealloc = (destructor)PyTimer::dealloc,
|
.tp_dealloc = (destructor)PyTimer::dealloc,
|
||||||
.tp_repr = PyTimer::repr,
|
.tp_repr = PyTimer::repr,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("Timer(name, callback, interval, once=False)\n\n"
|
.tp_doc = PyDoc_STR("Timer(name, callback, interval, once=False, start=True)\n\n"
|
||||||
"Create a timer that calls a function at regular intervals.\n\n"
|
"Create a timer that calls a function at regular intervals.\n\n"
|
||||||
"Args:\n"
|
"Args:\n"
|
||||||
" name (str): Unique identifier for the timer\n"
|
" name (str): Unique identifier for the timer\n"
|
||||||
" callback (callable): Function to call - receives (timer, runtime) args\n"
|
" callback (callable): Function to call - receives (timer, runtime) args\n"
|
||||||
" interval (int): Time between calls in milliseconds\n"
|
" interval (int): Time between calls in milliseconds\n"
|
||||||
" once (bool): If True, timer stops after first call. Default: False\n\n"
|
" once (bool): If True, timer stops after first call. Default: False\n"
|
||||||
|
" start (bool): If True, timer starts immediately. Default: True\n\n"
|
||||||
"Attributes:\n"
|
"Attributes:\n"
|
||||||
" interval (int): Time between calls in milliseconds\n"
|
" interval (int): Time between calls in milliseconds\n"
|
||||||
" remaining (int): Time until next call in milliseconds (read-only)\n"
|
" remaining (int): Time until next call in milliseconds (read-only)\n"
|
||||||
" paused (bool): Whether timer is paused (read-only)\n"
|
" paused (bool): Whether timer is paused (read-only)\n"
|
||||||
" active (bool): Whether timer is active and not paused (read-only)\n"
|
" stopped (bool): Whether timer is stopped (read-only)\n"
|
||||||
" callback (callable): The callback function\n"
|
" active (bool): Running state (read-write). Set True to start, False to pause\n"
|
||||||
|
" callback (callable): The callback function (preserved when stopped)\n"
|
||||||
" once (bool): Whether timer stops after firing once\n\n"
|
" once (bool): Whether timer stops after firing once\n\n"
|
||||||
"Methods:\n"
|
"Methods:\n"
|
||||||
|
" start(): Start the timer, adding to engine tick loop\n"
|
||||||
|
" stop(): Stop the timer (removes from engine, preserves callback)\n"
|
||||||
" pause(): Pause the timer, preserving time remaining\n"
|
" pause(): Pause the timer, preserving time remaining\n"
|
||||||
" resume(): Resume a paused timer\n"
|
" resume(): Resume a paused timer\n"
|
||||||
" cancel(): Stop and remove the timer\n"
|
" restart(): Reset timer and ensure it's running\n\n"
|
||||||
" restart(): Reset timer to start from beginning\n\n"
|
|
||||||
"Example:\n"
|
"Example:\n"
|
||||||
" def on_timer(timer, runtime):\n"
|
" def on_timer(timer, runtime):\n"
|
||||||
" print(f'Timer {timer} fired at {runtime}ms')\n"
|
" print(f'Timer {timer} fired at {runtime}ms')\n"
|
||||||
" if runtime > 5000:\n"
|
" if runtime > 5000:\n"
|
||||||
" timer.cancel()\n"
|
" timer.stop() # Stop but can restart later\n"
|
||||||
" \n"
|
" \n"
|
||||||
" timer = mcrfpy.Timer('my_timer', on_timer, 1000)\n"
|
" timer = mcrfpy.Timer('my_timer', on_timer, 1000)\n"
|
||||||
" timer.pause() # Pause timer\n"
|
" timer.pause() # Pause timer\n"
|
||||||
" timer.resume() # Resume timer\n"
|
" timer.resume() # Resume timer\n"
|
||||||
" timer.once = True # Make it one-shot"),
|
" timer.stop() # Stop completely\n"
|
||||||
|
" timer.start() # Restart from beginning"),
|
||||||
.tp_methods = PyTimer::methods,
|
.tp_methods = PyTimer::methods,
|
||||||
.tp_getset = PyTimer::getsetters,
|
.tp_getset = PyTimer::getsetters,
|
||||||
.tp_init = (initproc)PyTimer::init,
|
.tp_init = (initproc)PyTimer::init,
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "GameEngine.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),
|
: 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()
|
Timer::Timer()
|
||||||
: callback(std::make_shared<PyCallable>(Py_None)), interval(0), last_ran(0),
|
: 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() {
|
Timer::~Timer() {
|
||||||
|
|
@ -22,13 +22,13 @@ Timer::~Timer() {
|
||||||
|
|
||||||
bool Timer::hasElapsed(int now) const
|
bool Timer::hasElapsed(int now) const
|
||||||
{
|
{
|
||||||
if (paused) return false;
|
if (paused || stopped) return false;
|
||||||
return now >= last_ran + interval;
|
return now >= last_ran + interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Timer::test(int now)
|
bool Timer::test(int now)
|
||||||
{
|
{
|
||||||
if (!callback || callback->isNone()) return false;
|
if (!callback || callback->isNone() || stopped) return false;
|
||||||
|
|
||||||
if (hasElapsed(now))
|
if (hasElapsed(now))
|
||||||
{
|
{
|
||||||
|
|
@ -68,9 +68,9 @@ bool Timer::test(int now)
|
||||||
Py_DECREF(retval);
|
Py_DECREF(retval);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle one-shot timers
|
// Handle one-shot timers: stop but preserve callback for potential restart
|
||||||
if (once) {
|
if (once) {
|
||||||
cancel();
|
stopped = true; // Will be removed from map by testTimers(), but callback preserved
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -101,23 +101,41 @@ void Timer::restart(int current_time)
|
||||||
{
|
{
|
||||||
last_ran = current_time;
|
last_ran = current_time;
|
||||||
paused = false;
|
paused = false;
|
||||||
|
stopped = false; // Ensure timer is running
|
||||||
pause_start_time = 0;
|
pause_start_time = 0;
|
||||||
total_paused_time = 0;
|
total_paused_time = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Timer::cancel()
|
void Timer::start(int current_time)
|
||||||
{
|
{
|
||||||
// Cancel by setting callback to None
|
// Start/resume the timer - clear stopped flag, reset progress
|
||||||
callback = std::make_shared<PyCallable>(Py_None);
|
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
|
bool Timer::isActive() const
|
||||||
{
|
{
|
||||||
return callback && !callback->isNone() && !paused;
|
return callback && !callback->isNone() && !paused && !stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Timer::getRemaining(int current_time) const
|
int Timer::getRemaining(int current_time) const
|
||||||
{
|
{
|
||||||
|
if (stopped) {
|
||||||
|
// When stopped, progress is reset - full interval remaining
|
||||||
|
return interval;
|
||||||
|
}
|
||||||
if (paused) {
|
if (paused) {
|
||||||
// When paused, calculate time remaining from when it was paused
|
// When paused, calculate time remaining from when it was paused
|
||||||
int elapsed_when_paused = pause_start_time - last_ran;
|
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
|
int Timer::getElapsed(int current_time) const
|
||||||
{
|
{
|
||||||
|
if (stopped) {
|
||||||
|
// When stopped, progress is reset
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
if (paused) {
|
if (paused) {
|
||||||
return pause_start_time - last_ran;
|
return pause_start_time - last_ran;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
src/Timer.h
11
src/Timer.h
|
|
@ -21,11 +21,14 @@ private:
|
||||||
// One-shot timer support
|
// One-shot timer support
|
||||||
bool once;
|
bool once;
|
||||||
|
|
||||||
|
// Stopped state: timer is not in engine map, but callback is preserved
|
||||||
|
bool stopped;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
uint64_t serial_number = 0; // For Python object cache
|
uint64_t serial_number = 0; // For Python object cache
|
||||||
|
|
||||||
Timer(); // for map to build
|
Timer(); // for map to build
|
||||||
Timer(PyObject* target, int interval, int now, bool once = false);
|
Timer(PyObject* target, int interval, int now, bool once = false, bool start = true);
|
||||||
~Timer();
|
~Timer();
|
||||||
|
|
||||||
// Core timer functionality
|
// Core timer functionality
|
||||||
|
|
@ -36,11 +39,13 @@ public:
|
||||||
void pause(int current_time);
|
void pause(int current_time);
|
||||||
void resume(int current_time);
|
void resume(int current_time);
|
||||||
void restart(int current_time);
|
void restart(int current_time);
|
||||||
void cancel();
|
void start(int current_time); // Clear stopped flag, reset progress
|
||||||
|
void stop(); // Set stopped flag, preserve callback
|
||||||
|
|
||||||
// Timer state queries
|
// Timer state queries
|
||||||
bool isPaused() const { return paused; }
|
bool isPaused() const { return paused; }
|
||||||
bool isActive() const;
|
bool isStopped() const { return stopped; }
|
||||||
|
bool isActive() const; // Running: not paused AND not stopped AND has callback
|
||||||
int getInterval() const { return interval; }
|
int getInterval() const { return interval; }
|
||||||
void setInterval(int new_interval) { interval = new_interval; }
|
void setInterval(int new_interval) { interval = new_interval; }
|
||||||
int getRemaining(int current_time) const;
|
int getRemaining(int current_time) const;
|
||||||
|
|
|
||||||
|
|
@ -268,8 +268,12 @@ PyGetSetDef UICaption::getsetters[] = {
|
||||||
//{"w", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "width of the rectangle", (void*)2},
|
//{"w", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "width of the rectangle", (void*)2},
|
||||||
//{"h", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "height of the rectangle", (void*)3},
|
//{"h", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "height of the rectangle", (void*)3},
|
||||||
{"outline", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Thickness of the border", (void*)4},
|
{"outline", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Thickness of the border", (void*)4},
|
||||||
{"fill_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member, "Fill color of the text", (void*)0},
|
{"fill_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member,
|
||||||
{"outline_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member, "Outline color of the text", (void*)1},
|
"Fill color of the text. Returns a copy; modifying components requires reassignment. "
|
||||||
|
"For animation, use 'fill_color.r', 'fill_color.g', etc.", (void*)0},
|
||||||
|
{"outline_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member,
|
||||||
|
"Outline color of the text. Returns a copy; modifying components requires reassignment. "
|
||||||
|
"For animation, use 'outline_color.r', 'outline_color.g', etc.", (void*)1},
|
||||||
//{"children", (getter)PyUIFrame_get_children, NULL, "UICollection of objects on top of this one", NULL},
|
//{"children", (getter)PyUIFrame_get_children, NULL, "UICollection of objects on top of this one", NULL},
|
||||||
{"text", (getter)UICaption::get_text, (setter)UICaption::set_text, "The text displayed", NULL},
|
{"text", (getter)UICaption::get_text, (setter)UICaption::set_text, "The text displayed", NULL},
|
||||||
{"font_size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Font size (integer) in points", (void*)5},
|
{"font_size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Font size (integer) in points", (void*)5},
|
||||||
|
|
|
||||||
|
|
@ -434,8 +434,12 @@ PyGetSetDef UIFrame::getsetters[] = {
|
||||||
{"w", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "width of the rectangle", (void*)((intptr_t)PyObjectsEnum::UIFRAME << 8 | 2)},
|
{"w", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "width of the rectangle", (void*)((intptr_t)PyObjectsEnum::UIFRAME << 8 | 2)},
|
||||||
{"h", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "height of the rectangle", (void*)((intptr_t)PyObjectsEnum::UIFRAME << 8 | 3)},
|
{"h", (getter)UIDrawable::get_float_member, (setter)UIDrawable::set_float_member, "height of the rectangle", (void*)((intptr_t)PyObjectsEnum::UIFRAME << 8 | 3)},
|
||||||
{"outline", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "Thickness of the border", (void*)4},
|
{"outline", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "Thickness of the border", (void*)4},
|
||||||
{"fill_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Fill color of the rectangle", (void*)0},
|
{"fill_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member,
|
||||||
{"outline_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Outline color of the rectangle", (void*)1},
|
"Fill color of the rectangle. Returns a copy; modifying components requires reassignment. "
|
||||||
|
"For animation, use 'fill_color.r', 'fill_color.g', etc.", (void*)0},
|
||||||
|
{"outline_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member,
|
||||||
|
"Outline color of the rectangle. Returns a copy; modifying components requires reassignment. "
|
||||||
|
"For animation, use 'outline_color.r', 'outline_color.g', etc.", (void*)1},
|
||||||
{"children", (getter)UIFrame::get_children, NULL, "UICollection of objects on top of this one", NULL},
|
{"children", (getter)UIFrame::get_children, NULL, "UICollection of objects on top of this one", NULL},
|
||||||
{"on_click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
{"on_click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click,
|
||||||
MCRF_PROPERTY(on_click,
|
MCRF_PROPERTY(on_click,
|
||||||
|
|
|
||||||
|
|
@ -2059,7 +2059,9 @@ PyGetSetDef UIGrid::getsetters[] = {
|
||||||
), (void*)PyObjectsEnum::UIGRID},
|
), (void*)PyObjectsEnum::UIGRID},
|
||||||
|
|
||||||
{"texture", (getter)UIGrid::get_texture, NULL, "Texture of the grid", NULL}, //TODO 7DRL-day2-item5
|
{"texture", (getter)UIGrid::get_texture, NULL, "Texture of the grid", NULL}, //TODO 7DRL-day2-item5
|
||||||
{"fill_color", (getter)UIGrid::get_fill_color, (setter)UIGrid::set_fill_color, "Background fill color of the grid", NULL},
|
{"fill_color", (getter)UIGrid::get_fill_color, (setter)UIGrid::set_fill_color,
|
||||||
|
"Background fill color of the grid. Returns a copy; modifying components requires reassignment. "
|
||||||
|
"For animation, use 'fill_color.r', 'fill_color.g', etc.", NULL},
|
||||||
{"perspective", (getter)UIGrid::get_perspective, (setter)UIGrid::set_perspective,
|
{"perspective", (getter)UIGrid::get_perspective, (setter)UIGrid::set_perspective,
|
||||||
"Entity whose perspective to use for FOV rendering (None for omniscient view). "
|
"Entity whose perspective to use for FOV rendering (None for omniscient view). "
|
||||||
"Setting an entity automatically enables perspective mode.", NULL},
|
"Setting an entity automatically enables perspective mode.", NULL},
|
||||||
|
|
|
||||||
133
stubs/mcrfpy.pyi
133
stubs/mcrfpy.pyi
|
|
@ -12,7 +12,25 @@ Transition = Union[str, None]
|
||||||
# Classes
|
# Classes
|
||||||
|
|
||||||
class Color:
|
class Color:
|
||||||
"""SFML Color Object for RGBA colors."""
|
"""RGBA color representation.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
When accessing colors from UI elements (e.g., frame.fill_color),
|
||||||
|
you receive a COPY of the color. Modifying it doesn't affect the
|
||||||
|
original. To change a component:
|
||||||
|
|
||||||
|
# This does NOT work:
|
||||||
|
frame.fill_color.r = 255 # Modifies a temporary copy
|
||||||
|
|
||||||
|
# Do this instead:
|
||||||
|
c = frame.fill_color
|
||||||
|
c.r = 255
|
||||||
|
frame.fill_color = c
|
||||||
|
|
||||||
|
# Or use Animation for sub-properties:
|
||||||
|
anim = mcrfpy.Animation('fill_color.r', 255, 0.5, 'linear')
|
||||||
|
anim.start(frame)
|
||||||
|
"""
|
||||||
|
|
||||||
r: int
|
r: int
|
||||||
g: int
|
g: int
|
||||||
|
|
@ -534,31 +552,118 @@ class Window:
|
||||||
...
|
...
|
||||||
|
|
||||||
class Animation:
|
class Animation:
|
||||||
"""Animation object for animating UI properties."""
|
"""Animation for interpolating UI properties over time.
|
||||||
|
|
||||||
target: Any
|
Create an animation targeting a specific property, then call start() on a
|
||||||
property: str
|
UI element to begin the animation. The AnimationManager handles updates
|
||||||
duration: float
|
automatically.
|
||||||
easing: str
|
|
||||||
loop: bool
|
|
||||||
on_complete: Optional[Callable]
|
|
||||||
|
|
||||||
def __init__(self, target: Any, property: str, start_value: Any, end_value: Any,
|
Example:
|
||||||
duration: float, easing: str = 'linear', loop: bool = False,
|
# Move a frame to x=500 over 2 seconds with easing
|
||||||
on_complete: Optional[Callable] = None) -> None: ...
|
anim = mcrfpy.Animation('x', 500.0, 2.0, 'easeInOut')
|
||||||
|
anim.start(my_frame)
|
||||||
|
|
||||||
def start(self) -> None:
|
# Animate color with completion callback
|
||||||
"""Start the animation."""
|
def on_done(anim, target):
|
||||||
|
print('Fade complete!')
|
||||||
|
fade = mcrfpy.Animation('fill_color.a', 0, 1.0, callback=on_done)
|
||||||
|
fade.start(my_sprite)
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def property(self) -> str:
|
||||||
|
"""Target property name being animated (read-only)."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def duration(self) -> float:
|
||||||
|
"""Animation duration in seconds (read-only)."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def elapsed(self) -> float:
|
||||||
|
"""Time elapsed since animation started in seconds (read-only)."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_complete(self) -> bool:
|
||||||
|
"""Whether the animation has finished (read-only)."""
|
||||||
|
...
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_delta(self) -> bool:
|
||||||
|
"""Whether animation uses delta/additive mode (read-only)."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
property: str,
|
||||||
|
target: Union[float, int, Tuple[float, float], Tuple[int, int, int], Tuple[int, int, int, int], List[int], str],
|
||||||
|
duration: float,
|
||||||
|
easing: str = 'linear',
|
||||||
|
delta: bool = False,
|
||||||
|
callback: Optional[Callable[['Animation', Any], None]] = None) -> None:
|
||||||
|
"""Create an animation for a UI property.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
property: Property name to animate. Common properties:
|
||||||
|
- Position/Size: 'x', 'y', 'w', 'h', 'pos', 'size'
|
||||||
|
- Appearance: 'fill_color', 'outline_color', 'opacity'
|
||||||
|
- Sprite: 'sprite_index', 'scale'
|
||||||
|
- Grid: 'center', 'zoom'
|
||||||
|
- Sub-properties: 'fill_color.r', 'fill_color.g', etc.
|
||||||
|
target: Target value. Type depends on property:
|
||||||
|
- float: For x, y, w, h, scale, opacity, zoom
|
||||||
|
- int: For sprite_index
|
||||||
|
- (r, g, b) or (r, g, b, a): For colors
|
||||||
|
- (x, y): For pos, size, center
|
||||||
|
- [int, ...]: For sprite animation sequences
|
||||||
|
- str: For text animation
|
||||||
|
duration: Animation duration in seconds.
|
||||||
|
easing: Easing function. Options: 'linear', 'easeIn', 'easeOut',
|
||||||
|
'easeInOut', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad',
|
||||||
|
'easeInCubic', 'easeOutCubic', 'easeInOutCubic',
|
||||||
|
'easeInElastic', 'easeOutElastic', 'easeInOutElastic',
|
||||||
|
'easeInBounce', 'easeOutBounce', 'easeInOutBounce', and more.
|
||||||
|
delta: If True, target value is added to start value.
|
||||||
|
callback: Function(animation, target) called on completion.
|
||||||
|
"""
|
||||||
|
...
|
||||||
|
|
||||||
|
def start(self, target: UIElement, conflict_mode: str = 'replace') -> None:
|
||||||
|
"""Start the animation on a UI element.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
target: The UI element to animate (Frame, Caption, Sprite, Grid, or Entity)
|
||||||
|
conflict_mode: How to handle if property is already animating:
|
||||||
|
- 'replace': Stop existing animation, start new one (default)
|
||||||
|
- 'queue': Wait for existing animation to complete
|
||||||
|
- 'error': Raise RuntimeError if property is busy
|
||||||
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
def update(self, dt: float) -> bool:
|
def update(self, dt: float) -> bool:
|
||||||
"""Update animation, returns True if still running."""
|
"""Update animation by time delta. Returns True if still running.
|
||||||
|
|
||||||
|
Note: Normally called automatically by AnimationManager.
|
||||||
|
"""
|
||||||
...
|
...
|
||||||
|
|
||||||
def get_current_value(self) -> Any:
|
def get_current_value(self) -> Any:
|
||||||
"""Get the current interpolated value."""
|
"""Get the current interpolated value."""
|
||||||
...
|
...
|
||||||
|
|
||||||
|
def complete(self) -> None:
|
||||||
|
"""Complete the animation immediately, jumping to final value."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def hasValidTarget(self) -> bool:
|
||||||
|
"""Check if the animation target still exists."""
|
||||||
|
...
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
"""Return string representation showing property, duration, and status."""
|
||||||
|
...
|
||||||
|
|
||||||
# Module-level attributes
|
# Module-level attributes
|
||||||
|
|
||||||
__version__: str
|
__version__: str
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ def handle_key(key, state):
|
||||||
benchmark.on_key = handle_key
|
benchmark.on_key = handle_key
|
||||||
|
|
||||||
# Update entity positions
|
# Update entity positions
|
||||||
def update_entities(ms):
|
def update_entities(timer, ms):
|
||||||
dt = ms / 1000.0 # Convert to seconds
|
dt = ms / 1000.0 # Convert to seconds
|
||||||
|
|
||||||
for entity in entities:
|
for entity in entities:
|
||||||
|
|
@ -119,13 +119,13 @@ def update_entities(ms):
|
||||||
entity.y = new_y
|
entity.y = new_y
|
||||||
|
|
||||||
# Run movement update every frame (16ms)
|
# Run movement update every frame (16ms)
|
||||||
mcrfpy.setTimer("movement", update_entities, 16)
|
movement_timer = mcrfpy.Timer("movement", update_entities, 16)
|
||||||
|
|
||||||
# Benchmark statistics
|
# Benchmark statistics
|
||||||
frame_count = 0
|
frame_count = 0
|
||||||
start_time = None
|
start_time = None
|
||||||
|
|
||||||
def benchmark_timer(ms):
|
def benchmark_callback(timer, ms):
|
||||||
global frame_count, start_time
|
global frame_count, start_time
|
||||||
|
|
||||||
if start_time is None:
|
if start_time is None:
|
||||||
|
|
@ -152,4 +152,4 @@ def benchmark_timer(ms):
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
# Don't exit - let user review
|
# Don't exit - let user review
|
||||||
|
|
||||||
mcrfpy.setTimer("benchmark", benchmark_timer, 100)
|
benchmark_timer = mcrfpy.Timer("benchmark", benchmark_callback, 100)
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ frame_count = 0
|
||||||
metrics_samples = []
|
metrics_samples = []
|
||||||
|
|
||||||
|
|
||||||
def collect_metrics(runtime):
|
def collect_metrics(timer, runtime):
|
||||||
"""Timer callback to collect metrics each frame."""
|
"""Timer callback to collect metrics each frame."""
|
||||||
global frame_count, metrics_samples
|
global frame_count, metrics_samples
|
||||||
|
|
||||||
|
|
@ -65,9 +65,9 @@ def collect_metrics(runtime):
|
||||||
|
|
||||||
def finish_scenario():
|
def finish_scenario():
|
||||||
"""Calculate statistics and store results for current scenario."""
|
"""Calculate statistics and store results for current scenario."""
|
||||||
global results, current_scenario, metrics_samples
|
global results, current_scenario, metrics_samples, benchmark_timer
|
||||||
|
|
||||||
mcrfpy.delTimer("benchmark_collect")
|
benchmark_timer.stop()
|
||||||
|
|
||||||
if not metrics_samples:
|
if not metrics_samples:
|
||||||
print(f" WARNING: No samples collected for {current_scenario}")
|
print(f" WARNING: No samples collected for {current_scenario}")
|
||||||
|
|
@ -149,7 +149,8 @@ def run_next_scenario():
|
||||||
scenarios[next_idx][1]()
|
scenarios[next_idx][1]()
|
||||||
|
|
||||||
# Start collection timer (runs every frame)
|
# Start collection timer (runs every frame)
|
||||||
mcrfpy.setTimer("benchmark_collect", collect_metrics, 1)
|
global benchmark_timer
|
||||||
|
benchmark_timer = mcrfpy.Timer("benchmark_collect", collect_metrics, 1)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -427,7 +427,7 @@ def print_analysis():
|
||||||
print(f" Note: This overhead is acceptable given query speedups")
|
print(f" Note: This overhead is acceptable given query speedups")
|
||||||
|
|
||||||
|
|
||||||
def run_benchmarks(runtime=None):
|
def run_benchmarks(timer=None, runtime=None):
|
||||||
"""Main benchmark runner."""
|
"""Main benchmark runner."""
|
||||||
global results
|
global results
|
||||||
|
|
||||||
|
|
@ -458,4 +458,4 @@ if __name__ == "__main__":
|
||||||
if "--headless" in sys.argv or True: # Always run immediately for benchmarks
|
if "--headless" in sys.argv or True: # Always run immediately for benchmarks
|
||||||
run_benchmarks()
|
run_benchmarks()
|
||||||
else:
|
else:
|
||||||
mcrfpy.setTimer("run_bench", run_benchmarks, 100)
|
bench_timer = mcrfpy.Timer("run_bench", run_benchmarks, 100, once=True)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ frame_count = 0
|
||||||
test_results = {} # Store filenames for each test
|
test_results = {} # Store filenames for each test
|
||||||
|
|
||||||
|
|
||||||
def run_test_phase(runtime):
|
def run_test_phase(timer, runtime):
|
||||||
"""Run through warmup and measurement phases."""
|
"""Run through warmup and measurement phases."""
|
||||||
global frame_count
|
global frame_count
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ def run_test_phase(runtime):
|
||||||
test_results[current_test] = filename
|
test_results[current_test] = filename
|
||||||
print(f" {current_test}: saved to {filename}")
|
print(f" {current_test}: saved to {filename}")
|
||||||
|
|
||||||
mcrfpy.delTimer("test_phase")
|
timer.stop()
|
||||||
run_next_test()
|
run_next_test()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -90,7 +90,8 @@ def run_next_test():
|
||||||
print(f"\n[{next_idx + 1}/{len(tests)}] Running: {current_test}")
|
print(f"\n[{next_idx + 1}/{len(tests)}] Running: {current_test}")
|
||||||
tests[next_idx][1]()
|
tests[next_idx][1]()
|
||||||
|
|
||||||
mcrfpy.setTimer("test_phase", run_test_phase, 1)
|
global test_phase_timer
|
||||||
|
test_phase_timer = mcrfpy.Timer("test_phase", run_test_phase, 1)
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
@ -130,14 +131,15 @@ def setup_base_layer_modified():
|
||||||
|
|
||||||
# Timer to modify one cell per frame (triggers dirty flag each frame)
|
# Timer to modify one cell per frame (triggers dirty flag each frame)
|
||||||
mod_counter = [0]
|
mod_counter = [0]
|
||||||
def modify_cell(runtime):
|
def modify_cell(timer, runtime):
|
||||||
x = mod_counter[0] % GRID_SIZE
|
x = mod_counter[0] % GRID_SIZE
|
||||||
y = (mod_counter[0] // GRID_SIZE) % GRID_SIZE
|
y = (mod_counter[0] // GRID_SIZE) % GRID_SIZE
|
||||||
layer.set(x, y, mcrfpy.Color(255, 0, 0, 255))
|
layer.set(x, y, mcrfpy.Color(255, 0, 0, 255))
|
||||||
mod_counter[0] += 1
|
mod_counter[0] += 1
|
||||||
|
|
||||||
test_base_mod.activate()
|
test_base_mod.activate()
|
||||||
mcrfpy.setTimer("modify", modify_cell, 1)
|
global modify_timer
|
||||||
|
modify_timer = mcrfpy.Timer("modify", modify_cell, 1)
|
||||||
|
|
||||||
|
|
||||||
def setup_color_layer_static():
|
def setup_color_layer_static():
|
||||||
|
|
@ -170,14 +172,15 @@ def setup_color_layer_modified():
|
||||||
|
|
||||||
# Timer to modify one cell per frame - triggers re-render
|
# Timer to modify one cell per frame - triggers re-render
|
||||||
mod_counter = [0]
|
mod_counter = [0]
|
||||||
def modify_cell(runtime):
|
def modify_cell(timer, runtime):
|
||||||
x = mod_counter[0] % GRID_SIZE
|
x = mod_counter[0] % GRID_SIZE
|
||||||
y = (mod_counter[0] // GRID_SIZE) % GRID_SIZE
|
y = (mod_counter[0] // GRID_SIZE) % GRID_SIZE
|
||||||
layer.set(x, y, mcrfpy.Color(255, 0, 0, 255))
|
layer.set(x, y, mcrfpy.Color(255, 0, 0, 255))
|
||||||
mod_counter[0] += 1
|
mod_counter[0] += 1
|
||||||
|
|
||||||
test_color_mod.activate()
|
test_color_mod.activate()
|
||||||
mcrfpy.setTimer("modify", modify_cell, 1)
|
global modify_timer
|
||||||
|
modify_timer = mcrfpy.Timer("modify", modify_cell, 1)
|
||||||
|
|
||||||
|
|
||||||
def setup_tile_layer_static():
|
def setup_tile_layer_static():
|
||||||
|
|
@ -222,7 +225,7 @@ def setup_tile_layer_modified():
|
||||||
|
|
||||||
# Timer to modify one cell per frame
|
# Timer to modify one cell per frame
|
||||||
mod_counter = [0]
|
mod_counter = [0]
|
||||||
def modify_cell(runtime):
|
def modify_cell(timer, runtime):
|
||||||
if layer:
|
if layer:
|
||||||
x = mod_counter[0] % GRID_SIZE
|
x = mod_counter[0] % GRID_SIZE
|
||||||
y = (mod_counter[0] // GRID_SIZE) % GRID_SIZE
|
y = (mod_counter[0] // GRID_SIZE) % GRID_SIZE
|
||||||
|
|
@ -230,7 +233,8 @@ def setup_tile_layer_modified():
|
||||||
mod_counter[0] += 1
|
mod_counter[0] += 1
|
||||||
|
|
||||||
test_tile_mod.activate()
|
test_tile_mod.activate()
|
||||||
mcrfpy.setTimer("modify", modify_cell, 1)
|
global modify_timer
|
||||||
|
modify_timer = mcrfpy.Timer("modify", modify_cell, 1)
|
||||||
|
|
||||||
|
|
||||||
def setup_multi_layer_static():
|
def setup_multi_layer_static():
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class StressTestRunner:
|
||||||
def add_test(self, name, setup_fn, description=""):
|
def add_test(self, name, setup_fn, description=""):
|
||||||
self.tests.append({'name': name, 'setup': setup_fn, 'description': description})
|
self.tests.append({'name': name, 'setup': setup_fn, 'description': description})
|
||||||
|
|
||||||
def tick(self, runtime):
|
def tick(self, timer, runtime):
|
||||||
"""Single timer callback that manages all test flow"""
|
"""Single timer callback that manages all test flow"""
|
||||||
self.frames_counted += 1
|
self.frames_counted += 1
|
||||||
|
|
||||||
|
|
@ -103,7 +103,7 @@ class StressTestRunner:
|
||||||
self.results[test['name']] = {'error': str(e)}
|
self.results[test['name']] = {'error': str(e)}
|
||||||
|
|
||||||
def finish_suite(self):
|
def finish_suite(self):
|
||||||
mcrfpy.delTimer("tick")
|
self.tick_timer.stop()
|
||||||
|
|
||||||
print("\n" + "="*50)
|
print("\n" + "="*50)
|
||||||
print("STRESS TEST COMPLETE")
|
print("STRESS TEST COMPLETE")
|
||||||
|
|
@ -137,7 +137,7 @@ class StressTestRunner:
|
||||||
ui = init.children
|
ui = init.children
|
||||||
ui.append(mcrfpy.Frame(pos=(0,0), size=(10,10))) # Required for timer to fire
|
ui.append(mcrfpy.Frame(pos=(0,0), size=(10,10))) # Required for timer to fire
|
||||||
init.activate()
|
init.activate()
|
||||||
mcrfpy.setTimer("tick", self.tick, TIMER_INTERVAL_MS)
|
self.tick_timer = mcrfpy.Timer("tick", self.tick, TIMER_INTERVAL_MS)
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
print("FOV Isolation Test - Is TCOD slow, or is it the Python wrapper?")
|
print("FOV Isolation Test - Is TCOD slow, or is it the Python wrapper?")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|
@ -96,4 +96,4 @@ def run_test(runtime):
|
||||||
|
|
||||||
init = mcrfpy.Scene("init")
|
init = mcrfpy.Scene("init")
|
||||||
init.activate()
|
init.activate()
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ init = mcrfpy.Scene("init")
|
||||||
init.activate()
|
init.activate()
|
||||||
|
|
||||||
# Use a timer to let the engine initialize
|
# Use a timer to let the engine initialize
|
||||||
def run_benchmark(runtime):
|
def run_benchmark(timer, runtime):
|
||||||
main()
|
main()
|
||||||
|
|
||||||
mcrfpy.setTimer("bench", run_benchmark, 100)
|
bench_timer = mcrfpy.Timer("bench", run_benchmark, 100, once=True)
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ class DemoRunner:
|
||||||
self.current_index = 0
|
self.current_index = 0
|
||||||
self.render_wait = 0
|
self.render_wait = 0
|
||||||
|
|
||||||
def screenshot_cycle(runtime):
|
def screenshot_cycle(timer, runtime):
|
||||||
if self.render_wait == 0:
|
if self.render_wait == 0:
|
||||||
# Set scene and wait for render
|
# Set scene and wait for render
|
||||||
if self.current_index >= len(self.screens):
|
if self.current_index >= len(self.screens):
|
||||||
|
|
@ -139,7 +139,7 @@ class DemoRunner:
|
||||||
print("Done!")
|
print("Done!")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
mcrfpy.setTimer("screenshot", screenshot_cycle, 50)
|
self.screenshot_timer = mcrfpy.Timer("screenshot", screenshot_cycle, 50)
|
||||||
|
|
||||||
def run_interactive(self):
|
def run_interactive(self):
|
||||||
"""Run in interactive mode with menu."""
|
"""Run in interactive mode with menu."""
|
||||||
|
|
|
||||||
|
|
@ -126,9 +126,10 @@ def setup_scene():
|
||||||
patrol_demo.on_key = on_keypress
|
patrol_demo.on_key = on_keypress
|
||||||
|
|
||||||
# Start patrol timer
|
# Start patrol timer
|
||||||
mcrfpy.setTimer("patrol", patrol_step, move_timer_ms)
|
global patrol_timer
|
||||||
|
patrol_timer = mcrfpy.Timer("patrol", patrol_step, move_timer_ms)
|
||||||
|
|
||||||
def patrol_step(runtime):
|
def patrol_step(timer, runtime):
|
||||||
"""Move entity one step toward current waypoint"""
|
"""Move entity one step toward current waypoint"""
|
||||||
global current_waypoint, patrol_paused
|
global current_waypoint, patrol_paused
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -784,12 +784,12 @@ def run_demo():
|
||||||
demo_state = create_demo_scene()
|
demo_state = create_demo_scene()
|
||||||
|
|
||||||
# Set up exit timer for headless testing
|
# Set up exit timer for headless testing
|
||||||
def check_exit(dt):
|
def check_exit(timer, dt):
|
||||||
# In headless mode, exit after a short delay
|
# In headless mode, exit after a short delay
|
||||||
# In interactive mode, this won't trigger
|
# In interactive mode, this won't trigger
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# mcrfpy.setTimer("demo_check", check_exit, 100)
|
# check_exit_timer = mcrfpy.Timer("demo_check", check_exit, 100)
|
||||||
|
|
||||||
|
|
||||||
# Run if executed directly
|
# Run if executed directly
|
||||||
|
|
@ -801,8 +801,8 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
# If --screenshot flag, take a screenshot and exit
|
# If --screenshot flag, take a screenshot and exit
|
||||||
if "--screenshot" in sys.argv or len(sys.argv) > 1:
|
if "--screenshot" in sys.argv or len(sys.argv) > 1:
|
||||||
def take_screenshot(dt):
|
def take_screenshot(timer, dt):
|
||||||
automation.screenshot("focus_demo_screenshot.png")
|
automation.screenshot("focus_demo_screenshot.png")
|
||||||
print("Screenshot saved: focus_demo_screenshot.png")
|
print("Screenshot saved: focus_demo_screenshot.png")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
mcrfpy.setTimer("screenshot", take_screenshot, 200)
|
screenshot_timer = mcrfpy.Timer("screenshot", take_screenshot, 200, once=True)
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ class GeometryDemoRunner:
|
||||||
self.current_index = 0
|
self.current_index = 0
|
||||||
self.render_wait = 0
|
self.render_wait = 0
|
||||||
|
|
||||||
def screenshot_cycle(runtime):
|
def screenshot_cycle(timer, runtime):
|
||||||
if self.render_wait == 0:
|
if self.render_wait == 0:
|
||||||
if self.current_index >= len(self.screens):
|
if self.current_index >= len(self.screens):
|
||||||
print("Done!")
|
print("Done!")
|
||||||
|
|
@ -162,7 +162,7 @@ class GeometryDemoRunner:
|
||||||
print("Done!")
|
print("Done!")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
mcrfpy.setTimer("screenshot", screenshot_cycle, 100)
|
self.screenshot_timer = mcrfpy.Timer("screenshot", screenshot_cycle, 100)
|
||||||
|
|
||||||
def run_interactive(self):
|
def run_interactive(self):
|
||||||
"""Run in interactive mode with menu."""
|
"""Run in interactive mode with menu."""
|
||||||
|
|
|
||||||
|
|
@ -46,17 +46,19 @@ class GeometryDemoScreen:
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
"""Clean up timers when leaving screen."""
|
"""Clean up timers when leaving screen."""
|
||||||
for timer_name in self.timers:
|
for timer in self.timers:
|
||||||
try:
|
try:
|
||||||
mcrfpy.delTimer(timer_name)
|
timer.stop()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def restart_timers(self):
|
def restart_timers(self):
|
||||||
"""Re-register timers after cleanup."""
|
"""Re-register timers after cleanup."""
|
||||||
|
self.timers = [] # Clear old timer references
|
||||||
for name, callback, interval in self._timer_configs:
|
for name, callback, interval in self._timer_configs:
|
||||||
try:
|
try:
|
||||||
mcrfpy.setTimer(name, callback, interval)
|
timer = mcrfpy.Timer(name, callback, interval)
|
||||||
|
self.timers.append(timer)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Timer restart failed: {e}")
|
print(f"Timer restart failed: {e}")
|
||||||
|
|
||||||
|
|
@ -111,6 +113,6 @@ class GeometryDemoScreen:
|
||||||
if callback is None:
|
if callback is None:
|
||||||
print(f"Warning: Timer '{name}' callback is None, skipping")
|
print(f"Warning: Timer '{name}' callback is None, skipping")
|
||||||
return
|
return
|
||||||
mcrfpy.setTimer(name, callback, interval)
|
timer = mcrfpy.Timer(name, callback, interval)
|
||||||
self.timers.append(name)
|
self.timers.append(timer)
|
||||||
self._timer_configs.append((name, callback, interval))
|
self._timer_configs.append((name, callback, interval))
|
||||||
|
|
|
||||||
|
|
@ -269,7 +269,7 @@ class PathfindingAnimatedDemo(GeometryDemoScreen):
|
||||||
self.dist_label.fill_color = mcrfpy.Color(150, 150, 150)
|
self.dist_label.fill_color = mcrfpy.Color(150, 150, 150)
|
||||||
self.ui.append(self.dist_label)
|
self.ui.append(self.dist_label)
|
||||||
|
|
||||||
def _tick(self, runtime):
|
def _tick(self, timer, runtime):
|
||||||
"""Advance one turn."""
|
"""Advance one turn."""
|
||||||
self.current_time += 1
|
self.current_time += 1
|
||||||
self.time_label.text = f"Turn: {self.current_time}"
|
self.time_label.text = f"Turn: {self.current_time}"
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,7 @@ class SolarSystemDemo(GeometryDemoScreen):
|
||||||
self.ui.append(moon_path)
|
self.ui.append(moon_path)
|
||||||
self.orbit_rings[moon.name + "_path"] = moon_path
|
self.orbit_rings[moon.name + "_path"] = moon_path
|
||||||
|
|
||||||
def _tick(self, runtime):
|
def _tick(self, timer, runtime):
|
||||||
"""Advance time by one turn and update planet positions."""
|
"""Advance time by one turn and update planet positions."""
|
||||||
self.current_time += 1
|
self.current_time += 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ def handle_keypress(scene_name, keycode):
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# Timer callback for animation
|
# Timer callback for animation
|
||||||
def update_animation(dt):
|
def update_animation(timer, dt):
|
||||||
"""Update animation state"""
|
"""Update animation state"""
|
||||||
animate_movement(dt / 1000.0) # Convert ms to seconds
|
animate_movement(dt / 1000.0) # Convert ms to seconds
|
||||||
|
|
||||||
|
|
@ -335,7 +335,7 @@ for i, entity in enumerate(entities):
|
||||||
dijkstra_enhanced.on_key = handle_keypress
|
dijkstra_enhanced.on_key = handle_keypress
|
||||||
|
|
||||||
# Set up animation timer (60 FPS)
|
# Set up animation timer (60 FPS)
|
||||||
mcrfpy.setTimer("animation", update_animation, 16)
|
animation_timer = mcrfpy.Timer("animation", update_animation, 16)
|
||||||
|
|
||||||
# Show the scene
|
# Show the scene
|
||||||
dijkstra_enhanced.activate()
|
dijkstra_enhanced.activate()
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ def test_dijkstra(grid, entities):
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
"""Timer callback to run tests and take screenshot"""
|
"""Timer callback to run tests and take screenshot"""
|
||||||
# Run pathfinding tests
|
# Run pathfinding tests
|
||||||
results = test_dijkstra(grid, entities)
|
results = test_dijkstra(grid, entities)
|
||||||
|
|
@ -101,8 +101,8 @@ def run_test(runtime):
|
||||||
ui.append(caption)
|
ui.append(caption)
|
||||||
y_pos += 20
|
y_pos += 20
|
||||||
|
|
||||||
# Take screenshot
|
# Take screenshot (one-shot timer)
|
||||||
mcrfpy.setTimer("screenshot", lambda rt: take_screenshot(), 500)
|
screenshot_timer = mcrfpy.Timer("screenshot", lambda t, rt: take_screenshot(), 500, once=True)
|
||||||
|
|
||||||
def take_screenshot():
|
def take_screenshot():
|
||||||
"""Take screenshot and exit"""
|
"""Take screenshot and exit"""
|
||||||
|
|
@ -140,7 +140,7 @@ ui.append(legend)
|
||||||
# Set scene
|
# Set scene
|
||||||
dijkstra_test.activate()
|
dijkstra_test.activate()
|
||||||
|
|
||||||
# Run test after scene loads
|
# Run test after scene loads (one-shot timer)
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
||||||
print("Running Dijkstra tests...")
|
print("Running Dijkstra tests...")
|
||||||
|
|
@ -9,7 +9,7 @@ This test verifies that:
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def timer_that_raises(runtime):
|
def timer_that_raises(timer, runtime):
|
||||||
"""A timer callback that raises an exception"""
|
"""A timer callback that raises an exception"""
|
||||||
raise ValueError("Intentional test exception")
|
raise ValueError("Intentional test exception")
|
||||||
|
|
||||||
|
|
@ -17,8 +17,8 @@ def timer_that_raises(runtime):
|
||||||
test = mcrfpy.Scene("test")
|
test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
# Schedule the timer - it will fire after 50ms
|
# Schedule the timer - it will fire after 50ms (one-shot timer)
|
||||||
mcrfpy.setTimer("raise_exception", timer_that_raises, 50)
|
exception_timer = mcrfpy.Timer("raise_exception", timer_that_raises, 50, once=True)
|
||||||
|
|
||||||
# This test expects:
|
# This test expects:
|
||||||
# - Default behavior: exit with code 1 after first exception
|
# - Default behavior: exit with code 1 after first exception
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ def test_edge_cases():
|
||||||
print(" Edge cases: PASS")
|
print(" Edge cases: PASS")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
"""Timer callback to run tests after scene is active"""
|
"""Timer callback to run tests after scene is active"""
|
||||||
results = []
|
results = []
|
||||||
|
|
||||||
|
|
@ -185,4 +185,4 @@ if __name__ == "__main__":
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
# Run tests after scene is active
|
# Run tests after scene is active
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
print("Issue #146 Regression Test: compute_fov() returns None")
|
print("Issue #146 Regression Test: compute_fov() returns None")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|
@ -111,4 +111,4 @@ def run_test(runtime):
|
||||||
# Initialize and run
|
# Initialize and run
|
||||||
init = mcrfpy.Scene("init")
|
init = mcrfpy.Scene("init")
|
||||||
init.activate()
|
init.activate()
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ Tests:
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
print("Issue #147 Regression Test: Dynamic Layer System for Grid")
|
print("Issue #147 Regression Test: Dynamic Layer System for Grid")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|
@ -190,4 +190,4 @@ def run_test(runtime):
|
||||||
# Initialize and run
|
# Initialize and run
|
||||||
init = mcrfpy.Scene("init")
|
init = mcrfpy.Scene("init")
|
||||||
init.activate()
|
init.activate()
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
print("Issue #148 Regression Test: Layer Dirty Flags and Caching")
|
print("Issue #148 Regression Test: Layer Dirty Flags and Caching")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
|
|
@ -154,4 +154,4 @@ def run_test(runtime):
|
||||||
# Initialize and run
|
# Initialize and run
|
||||||
init = mcrfpy.Scene("init")
|
init = mcrfpy.Scene("init")
|
||||||
init.activate()
|
init.activate()
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class CustomEntity(mcrfpy.Entity):
|
||||||
def custom_method(self):
|
def custom_method(self):
|
||||||
return "Custom method called"
|
return "Custom method called"
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
"""Test that derived entity classes maintain their type in collections"""
|
"""Test that derived entity classes maintain their type in collections"""
|
||||||
try:
|
try:
|
||||||
# Create a grid
|
# Create a grid
|
||||||
|
|
@ -85,4 +85,4 @@ test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
# Schedule test to run after game loop starts
|
# Schedule test to run after game loop starts
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
@ -149,7 +149,7 @@ def test_color_properties():
|
||||||
|
|
||||||
return tests_passed == tests_total
|
return tests_passed == tests_total
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
"""Timer callback to run the test"""
|
"""Timer callback to run the test"""
|
||||||
try:
|
try:
|
||||||
success = test_color_properties()
|
success = test_color_properties()
|
||||||
|
|
@ -167,4 +167,4 @@ test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
# Schedule test to run after game loop starts
|
# Schedule test to run after game loop starts
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
@ -183,7 +183,7 @@ def test_property_introspection():
|
||||||
|
|
||||||
return tests_passed, tests_total
|
return tests_passed, tests_total
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
"""Timer callback to run the test"""
|
"""Timer callback to run the test"""
|
||||||
try:
|
try:
|
||||||
print("=== Testing Texture and Font Properties (Issue #99) ===\n")
|
print("=== Testing Texture and Font Properties (Issue #99) ===\n")
|
||||||
|
|
@ -221,4 +221,4 @@ test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
# Schedule test to run after game loop starts
|
# Schedule test to run after game loop starts
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
@ -7,7 +7,7 @@ import mcrfpy
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
"""Test RenderTexture resizing"""
|
"""Test RenderTexture resizing"""
|
||||||
print("Testing Issue #9: RenderTexture resize (minimal)")
|
print("Testing Issue #9: RenderTexture resize (minimal)")
|
||||||
|
|
||||||
|
|
@ -64,4 +64,4 @@ test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
# Schedule test
|
# Schedule test
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
@ -209,7 +209,7 @@ def test_rendertexture_resize():
|
||||||
|
|
||||||
print(f"\nScreenshots saved to /tmp/issue_9_*.png")
|
print(f"\nScreenshots saved to /tmp/issue_9_*.png")
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
"""Timer callback to run the test"""
|
"""Timer callback to run the test"""
|
||||||
try:
|
try:
|
||||||
test_rendertexture_resize()
|
test_rendertexture_resize()
|
||||||
|
|
@ -226,4 +226,4 @@ test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
# Schedule test to run after game loop starts
|
# Schedule test to run after game loop starts
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
@ -9,7 +9,7 @@ import mcrfpy
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
"""Test that UIGrid properly handles resizing"""
|
"""Test that UIGrid properly handles resizing"""
|
||||||
try:
|
try:
|
||||||
# Create a grid with initial size
|
# Create a grid with initial size
|
||||||
|
|
@ -86,4 +86,4 @@ test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
# Schedule test to run after game loop starts
|
# Schedule test to run after game loop starts
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
@ -64,7 +64,7 @@ def demonstrate_solution():
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
"""Timer callback"""
|
"""Timer callback"""
|
||||||
try:
|
try:
|
||||||
demonstrate_solution()
|
demonstrate_solution()
|
||||||
|
|
@ -80,4 +80,4 @@ def run_test(runtime):
|
||||||
# Set up scene and run
|
# Set up scene and run
|
||||||
test = mcrfpy.Scene("test")
|
test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
@ -4,7 +4,7 @@ import mcrfpy
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
def run_automation_tests():
|
def run_automation_tests(timer, runtime):
|
||||||
"""This runs AFTER the game loop has started and rendered frames"""
|
"""This runs AFTER the game loop has started and rendered frames"""
|
||||||
print("\n=== Automation Test Running (1 second after start) ===")
|
print("\n=== Automation Test Running (1 second after start) ===")
|
||||||
|
|
||||||
|
|
@ -34,13 +34,14 @@ def run_automation_tests():
|
||||||
print("3. The RenderTexture now contains actual rendered content")
|
print("3. The RenderTexture now contains actual rendered content")
|
||||||
|
|
||||||
# Cancel this timer so it doesn't repeat
|
# Cancel this timer so it doesn't repeat
|
||||||
mcrfpy.delTimer("automation_test")
|
timer.stop()
|
||||||
|
|
||||||
# Optional: exit after a moment
|
# Optional: exit after a moment
|
||||||
def exit_game():
|
def exit_game(t, r):
|
||||||
print("Exiting...")
|
print("Exiting...")
|
||||||
mcrfpy.exit()
|
mcrfpy.exit()
|
||||||
mcrfpy.setTimer("exit", exit_game, 500) # Exit 500ms later
|
global exit_timer
|
||||||
|
exit_timer = mcrfpy.Timer("exit", exit_game, 500, once=True)
|
||||||
|
|
||||||
# This code runs during --exec script execution
|
# This code runs during --exec script execution
|
||||||
print("=== Setting Up Test Scene ===")
|
print("=== Setting Up Test Scene ===")
|
||||||
|
|
@ -73,7 +74,7 @@ frame.on_click = frame_clicked
|
||||||
print("Scene setup complete. Setting timer for automation tests...")
|
print("Scene setup complete. Setting timer for automation tests...")
|
||||||
|
|
||||||
# THIS IS THE KEY: Set timer to run AFTER the game loop starts
|
# THIS IS THE KEY: Set timer to run AFTER the game loop starts
|
||||||
mcrfpy.setTimer("automation_test", run_automation_tests, 1000)
|
automation_test_timer = mcrfpy.Timer("automation_test", run_automation_tests, 1000, once=True)
|
||||||
|
|
||||||
print("Timer set. Game loop will start after this script completes.")
|
print("Timer set. Game loop will start after this script completes.")
|
||||||
print("Automation tests will run 1 second later when content is visible.")
|
print("Automation tests will run 1 second later when content is visible.")
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,122 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Test for mcrfpy.setTimer() and delTimer() methods"""
|
"""Test for mcrfpy.Timer class - replaces old setTimer/delTimer tests (#173)"""
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def test_timers():
|
def test_timers():
|
||||||
"""Test timer API methods"""
|
"""Test Timer class API"""
|
||||||
print("Testing mcrfpy timer methods...")
|
print("Testing mcrfpy.Timer class...")
|
||||||
|
|
||||||
# Test 1: Create a simple timer
|
# Test 1: Create a simple timer
|
||||||
try:
|
try:
|
||||||
call_count = [0]
|
call_count = [0]
|
||||||
def simple_callback(runtime):
|
def simple_callback(timer, runtime):
|
||||||
call_count[0] += 1
|
call_count[0] += 1
|
||||||
print(f"Timer callback called, count={call_count[0]}, runtime={runtime}")
|
print(f"Timer callback called, count={call_count[0]}, runtime={runtime}")
|
||||||
|
|
||||||
mcrfpy.setTimer("test_timer", simple_callback, 100)
|
timer = mcrfpy.Timer("test_timer", simple_callback, 100)
|
||||||
print("✓ setTimer() called successfully")
|
print("✓ Timer() created successfully")
|
||||||
|
print(f" Timer repr: {timer}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"✗ setTimer() failed: {e}")
|
print(f"✗ Timer() failed: {e}")
|
||||||
print("FAIL")
|
print("FAIL")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Test 2: Delete the timer
|
# Test 2: Stop the timer
|
||||||
try:
|
try:
|
||||||
mcrfpy.delTimer("test_timer")
|
timer.stop()
|
||||||
print("✓ delTimer() called successfully")
|
print("✓ timer.stop() called successfully")
|
||||||
|
assert timer.stopped == True, "Timer should be stopped"
|
||||||
|
print(f" Timer after stop: {timer}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"✗ delTimer() failed: {e}")
|
print(f"✗ timer.stop() failed: {e}")
|
||||||
print("FAIL")
|
print("FAIL")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Test 3: Delete non-existent timer (should not crash)
|
# Test 3: Restart the timer
|
||||||
try:
|
try:
|
||||||
mcrfpy.delTimer("nonexistent_timer")
|
timer.start()
|
||||||
print("✓ delTimer() accepts non-existent timer names")
|
print("✓ timer.start() called successfully")
|
||||||
|
assert timer.stopped == False, "Timer should not be stopped"
|
||||||
|
assert timer.active == True, "Timer should be active"
|
||||||
|
timer.stop() # Clean up
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"✗ delTimer() failed on non-existent timer: {e}")
|
print(f"✗ timer.start() failed: {e}")
|
||||||
print("FAIL")
|
print("FAIL")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Test 4: Create multiple timers
|
# Test 4: Create timer with start=False
|
||||||
try:
|
try:
|
||||||
def callback1(rt): pass
|
def callback2(timer, runtime): pass
|
||||||
def callback2(rt): pass
|
timer2 = mcrfpy.Timer("timer2", callback2, 500, start=False)
|
||||||
def callback3(rt): pass
|
assert timer2.stopped == True, "Timer with start=False should be stopped"
|
||||||
|
print("✓ Timer with start=False created in stopped state")
|
||||||
|
timer2.start()
|
||||||
|
assert timer2.active == True, "Timer should be active after start()"
|
||||||
|
timer2.stop()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ Timer with start=False failed: {e}")
|
||||||
|
print("FAIL")
|
||||||
|
return
|
||||||
|
|
||||||
mcrfpy.setTimer("timer1", callback1, 500)
|
# Test 5: Create multiple timers
|
||||||
mcrfpy.setTimer("timer2", callback2, 750)
|
try:
|
||||||
mcrfpy.setTimer("timer3", callback3, 250)
|
def callback3(t, rt): pass
|
||||||
|
|
||||||
|
t1 = mcrfpy.Timer("multi1", callback3, 500)
|
||||||
|
t2 = mcrfpy.Timer("multi2", callback3, 750)
|
||||||
|
t3 = mcrfpy.Timer("multi3", callback3, 250)
|
||||||
print("✓ Multiple timers created successfully")
|
print("✓ Multiple timers created successfully")
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
mcrfpy.delTimer("timer1")
|
t1.stop()
|
||||||
mcrfpy.delTimer("timer2")
|
t2.stop()
|
||||||
mcrfpy.delTimer("timer3")
|
t3.stop()
|
||||||
print("✓ Multiple timers deleted successfully")
|
print("✓ Multiple timers stopped successfully")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"✗ Multiple timer test failed: {e}")
|
print(f"✗ Multiple timer test failed: {e}")
|
||||||
print("FAIL")
|
print("FAIL")
|
||||||
return
|
return
|
||||||
|
|
||||||
print("\nAll timer API tests passed")
|
# Test 6: mcrfpy.timers collection
|
||||||
|
try:
|
||||||
|
# Create a timer that's running
|
||||||
|
running_timer = mcrfpy.Timer("running_test", callback3, 1000)
|
||||||
|
|
||||||
|
timers = mcrfpy.timers
|
||||||
|
assert isinstance(timers, tuple), "mcrfpy.timers should be a tuple"
|
||||||
|
print(f"✓ mcrfpy.timers returns tuple with {len(timers)} timer(s)")
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
running_timer.stop()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ mcrfpy.timers test failed: {e}")
|
||||||
|
print("FAIL")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Test 7: active property is read-write
|
||||||
|
try:
|
||||||
|
active_timer = mcrfpy.Timer("active_test", callback3, 1000)
|
||||||
|
assert active_timer.active == True, "New timer should be active"
|
||||||
|
|
||||||
|
active_timer.active = False # Should pause
|
||||||
|
assert active_timer.paused == True, "Timer should be paused after active=False"
|
||||||
|
|
||||||
|
active_timer.active = True # Should resume
|
||||||
|
assert active_timer.active == True, "Timer should be active after active=True"
|
||||||
|
|
||||||
|
active_timer.stop()
|
||||||
|
active_timer.active = True # Should restart from stopped
|
||||||
|
assert active_timer.active == True, "Timer should restart from stopped via active=True"
|
||||||
|
|
||||||
|
active_timer.stop()
|
||||||
|
print("✓ active property is read-write")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"✗ active property test failed: {e}")
|
||||||
|
print("FAIL")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\nAll Timer API tests passed")
|
||||||
print("PASS")
|
print("PASS")
|
||||||
|
|
||||||
# Run the test
|
# Run the test
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import sys
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
"""Timer callback to test benchmark logging"""
|
"""Timer callback to test benchmark logging"""
|
||||||
# Stop the benchmark and get filename
|
# Stop the benchmark and get filename
|
||||||
try:
|
try:
|
||||||
|
|
@ -132,4 +132,4 @@ test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
# Schedule test completion after ~100ms (to capture some frames)
|
# Schedule test completion after ~100ms (to capture some frames)
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
|
||||||
|
|
@ -68,13 +68,13 @@ print("\nTest 7: Path after potential sync")
|
||||||
path4 = grid.compute_astar_path(0, 0, 5, 5)
|
path4 = grid.compute_astar_path(0, 0, 5, 5)
|
||||||
print(f" A* path: {path4}")
|
print(f" A* path: {path4}")
|
||||||
|
|
||||||
def timer_cb(dt):
|
def timer_cb(timer, runtime):
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# Quick UI setup
|
# Quick UI setup
|
||||||
ui = debug.children
|
ui = debug.children
|
||||||
ui.append(grid)
|
ui.append(grid)
|
||||||
debug.activate()
|
debug.activate()
|
||||||
mcrfpy.setTimer("exit", timer_cb, 100)
|
exit_timer = mcrfpy.Timer("exit", timer_cb, 100, once=True)
|
||||||
|
|
||||||
print("\nStarting timer...")
|
print("\nStarting timer...")
|
||||||
|
|
@ -404,7 +404,7 @@ screenshots = [
|
||||||
("combined_example", "ui_combined_example.png")
|
("combined_example", "ui_combined_example.png")
|
||||||
]
|
]
|
||||||
|
|
||||||
def take_screenshots(runtime):
|
def take_screenshots(timer, runtime):
|
||||||
"""Timer callback to take screenshots sequentially"""
|
"""Timer callback to take screenshots sequentially"""
|
||||||
global current_screenshot
|
global current_screenshot
|
||||||
|
|
||||||
|
|
@ -420,7 +420,7 @@ def take_screenshots(runtime):
|
||||||
mcrfpy.current_scene = scene_name
|
mcrfpy.current_scene = scene_name
|
||||||
|
|
||||||
# Take screenshot after a short delay to ensure rendering
|
# Take screenshot after a short delay to ensure rendering
|
||||||
def capture():
|
def capture(t, r):
|
||||||
global current_screenshot
|
global current_screenshot
|
||||||
full_path = f"{output_dir}/{filename}"
|
full_path = f"{output_dir}/{filename}"
|
||||||
result = automation.screenshot(full_path)
|
result = automation.screenshot(full_path)
|
||||||
|
|
@ -429,23 +429,25 @@ def take_screenshots(runtime):
|
||||||
current_screenshot += 1
|
current_screenshot += 1
|
||||||
|
|
||||||
# Schedule next screenshot
|
# Schedule next screenshot
|
||||||
mcrfpy.setTimer("next_screenshot", take_screenshots, 200)
|
global next_screenshot_timer
|
||||||
|
next_screenshot_timer = mcrfpy.Timer("next_screenshot", take_screenshots, 200, once=True)
|
||||||
|
|
||||||
# Give scene time to render
|
# Give scene time to render
|
||||||
mcrfpy.setTimer("capture", lambda r: capture(), 100)
|
global capture_timer
|
||||||
|
capture_timer = mcrfpy.Timer("capture", capture, 100, once=True)
|
||||||
|
|
||||||
# Start with the first scene
|
# Start with the first scene
|
||||||
caption_example.activate()
|
caption_example.activate()
|
||||||
|
|
||||||
# Start the screenshot process
|
# Start the screenshot process
|
||||||
print(f"\nStarting screenshot capture of {len(screenshots)} scenes...")
|
print(f"\nStarting screenshot capture of {len(screenshots)} scenes...")
|
||||||
mcrfpy.setTimer("start", take_screenshots, 500)
|
start_timer = mcrfpy.Timer("start", take_screenshots, 500, once=True)
|
||||||
|
|
||||||
# Safety timeout
|
# Safety timeout
|
||||||
def safety_exit(runtime):
|
def safety_exit(timer, runtime):
|
||||||
print("\nERROR: Safety timeout reached! Exiting...")
|
print("\nERROR: Safety timeout reached! Exiting...")
|
||||||
mcrfpy.exit()
|
mcrfpy.exit()
|
||||||
|
|
||||||
mcrfpy.setTimer("safety", safety_exit, 30000)
|
safety_timer = mcrfpy.Timer("safety", safety_exit, 30000, once=True)
|
||||||
|
|
||||||
print("Setup complete. Game loop starting...")
|
print("Setup complete. Game loop starting...")
|
||||||
|
|
@ -5,7 +5,7 @@ import mcrfpy
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def capture_grid(runtime):
|
def capture_grid(timer, runtime):
|
||||||
"""Capture grid example after render loop starts"""
|
"""Capture grid example after render loop starts"""
|
||||||
|
|
||||||
# Take screenshot
|
# Take screenshot
|
||||||
|
|
@ -112,4 +112,4 @@ ui.append(info)
|
||||||
grid.activate()
|
grid.activate()
|
||||||
|
|
||||||
# Set timer to capture after rendering starts
|
# Set timer to capture after rendering starts
|
||||||
mcrfpy.setTimer("capture", capture_grid, 100)
|
capture_timer = mcrfpy.Timer("capture", capture_grid, 100, once=True)
|
||||||
|
|
@ -5,7 +5,7 @@ import mcrfpy
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def capture_sprites(runtime):
|
def capture_sprites(timer, runtime):
|
||||||
"""Capture sprite examples after render loop starts"""
|
"""Capture sprite examples after render loop starts"""
|
||||||
|
|
||||||
# Take screenshot
|
# Take screenshot
|
||||||
|
|
@ -157,4 +157,4 @@ ui.append(scale_label)
|
||||||
sprites.activate()
|
sprites.activate()
|
||||||
|
|
||||||
# Set timer to capture after rendering starts
|
# Set timer to capture after rendering starts
|
||||||
mcrfpy.setTimer("capture", capture_sprites, 100)
|
capture_timer = mcrfpy.Timer("capture", capture_sprites, 100, once=True)
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
Test for keypressScene() validation - should reject non-callable arguments
|
Test for keypressScene() validation - should reject non-callable arguments
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def test_keypress_validation(timer_name):
|
def test_keypress_validation(timer, runtime):
|
||||||
"""Test that keypressScene validates its argument is callable"""
|
"""Test that keypressScene validates its argument is callable"""
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -90,4 +90,4 @@ def test_keypress_validation(timer_name):
|
||||||
|
|
||||||
# Execute the test after a short delay
|
# Execute the test after a short delay
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
mcrfpy.setTimer("test", test_keypress_validation, 100)
|
test_timer = mcrfpy.Timer("test", test_keypress_validation, 100, once=True)
|
||||||
|
|
@ -6,7 +6,7 @@ from mcrfpy import automation
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
def take_screenshot(runtime):
|
def take_screenshot(timer, runtime):
|
||||||
"""Take screenshot after render starts"""
|
"""Take screenshot after render starts"""
|
||||||
print(f"Timer callback fired at runtime: {runtime}")
|
print(f"Timer callback fired at runtime: {runtime}")
|
||||||
|
|
||||||
|
|
@ -41,5 +41,5 @@ test.activate()
|
||||||
|
|
||||||
# Use timer to ensure rendering has started
|
# Use timer to ensure rendering has started
|
||||||
print("Setting timer...")
|
print("Setting timer...")
|
||||||
mcrfpy.setTimer("screenshot", take_screenshot, 500) # Wait 0.5 seconds
|
mcrfpy.Timer("screenshot", take_screenshot, 500, once=True) # Wait 0.5 seconds
|
||||||
print("Timer set, entering game loop...")
|
print("Timer set, entering game loop...")
|
||||||
|
|
@ -6,7 +6,7 @@ from mcrfpy import automation
|
||||||
# Counter to track timer calls
|
# Counter to track timer calls
|
||||||
call_count = 0
|
call_count = 0
|
||||||
|
|
||||||
def take_screenshot_and_exit():
|
def take_screenshot_and_exit(timer, runtime):
|
||||||
"""Timer callback that takes screenshot then exits"""
|
"""Timer callback that takes screenshot then exits"""
|
||||||
global call_count
|
global call_count
|
||||||
call_count += 1
|
call_count += 1
|
||||||
|
|
@ -35,6 +35,6 @@ frame = mcrfpy.Frame(pos=(100, 100), size=(200, 200),
|
||||||
ui.append(frame)
|
ui.append(frame)
|
||||||
|
|
||||||
print("Setting timer to fire in 100ms...")
|
print("Setting timer to fire in 100ms...")
|
||||||
mcrfpy.setTimer("screenshot_timer", take_screenshot_and_exit, 100)
|
mcrfpy.Timer("screenshot_timer", take_screenshot_and_exit, 100, once=True)
|
||||||
|
|
||||||
print("Setup complete. Game loop starting...")
|
print("Setup complete. Game loop starting...")
|
||||||
|
|
@ -6,6 +6,7 @@ import sys
|
||||||
|
|
||||||
# Global state to track callback
|
# Global state to track callback
|
||||||
callback_count = 0
|
callback_count = 0
|
||||||
|
callback_demo = None # Will be set in setup_and_run
|
||||||
|
|
||||||
def my_callback(anim, target):
|
def my_callback(anim, target):
|
||||||
"""Simple callback that prints when animation completes"""
|
"""Simple callback that prints when animation completes"""
|
||||||
|
|
@ -16,6 +17,7 @@ def my_callback(anim, target):
|
||||||
|
|
||||||
def setup_and_run():
|
def setup_and_run():
|
||||||
"""Set up scene and run animation with callback"""
|
"""Set up scene and run animation with callback"""
|
||||||
|
global callback_demo
|
||||||
# Create scene
|
# Create scene
|
||||||
callback_demo = mcrfpy.Scene("callback_demo")
|
callback_demo = mcrfpy.Scene("callback_demo")
|
||||||
callback_demo.activate()
|
callback_demo.activate()
|
||||||
|
|
@ -31,11 +33,11 @@ def setup_and_run():
|
||||||
anim.start(frame)
|
anim.start(frame)
|
||||||
|
|
||||||
# Schedule check after animation should complete
|
# Schedule check after animation should complete
|
||||||
mcrfpy.setTimer("check", check_result, 1500)
|
mcrfpy.Timer("check", check_result, 1500, once=True)
|
||||||
|
|
||||||
def check_result(runtime):
|
def check_result(timer, runtime):
|
||||||
"""Check if callback fired correctly"""
|
"""Check if callback fired correctly"""
|
||||||
global callback_count
|
global callback_count, callback_demo
|
||||||
|
|
||||||
if callback_count == 1:
|
if callback_count == 1:
|
||||||
print("SUCCESS: Callback fired exactly once!")
|
print("SUCCESS: Callback fired exactly once!")
|
||||||
|
|
@ -48,12 +50,12 @@ def check_result(runtime):
|
||||||
anim2 = mcrfpy.Animation("y", 300.0, 0.5, "linear")
|
anim2 = mcrfpy.Animation("y", 300.0, 0.5, "linear")
|
||||||
anim2.start(frame)
|
anim2.start(frame)
|
||||||
|
|
||||||
mcrfpy.setTimer("final", final_check, 700)
|
mcrfpy.Timer("final", final_check, 700, once=True)
|
||||||
else:
|
else:
|
||||||
print(f"FAIL: Expected 1 callback, got {callback_count}")
|
print(f"FAIL: Expected 1 callback, got {callback_count}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def final_check(runtime):
|
def final_check(timer, runtime):
|
||||||
"""Final check - callback count should still be 1"""
|
"""Final check - callback count should still be 1"""
|
||||||
global callback_count
|
global callback_count
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,8 @@ class PathAnimator:
|
||||||
if self.current_index >= len(self.path):
|
if self.current_index >= len(self.path):
|
||||||
# Path complete
|
# Path complete
|
||||||
self.animating = False
|
self.animating = False
|
||||||
mcrfpy.delTimer(self.check_timer_name)
|
if hasattr(self, '_check_timer'):
|
||||||
|
self._check_timer.stop()
|
||||||
if self.on_complete:
|
if self.on_complete:
|
||||||
self.on_complete()
|
self.on_complete()
|
||||||
return
|
return
|
||||||
|
|
@ -56,14 +57,14 @@ class PathAnimator:
|
||||||
self.entity.update_visibility()
|
self.entity.update_visibility()
|
||||||
|
|
||||||
# Set timer to check completion
|
# Set timer to check completion
|
||||||
mcrfpy.setTimer(self.check_timer_name, self._check_completion, 50)
|
self._check_timer = mcrfpy.Timer(self.check_timer_name, self._check_completion, 50)
|
||||||
|
|
||||||
def _check_completion(self, dt):
|
def _check_completion(self, timer, runtime):
|
||||||
"""Check if current animation is complete"""
|
"""Check if current animation is complete"""
|
||||||
if hasattr(self.anim_x, 'is_complete') and self.anim_x.is_complete:
|
if hasattr(self.anim_x, 'is_complete') and self.anim_x.is_complete:
|
||||||
# Move to next step
|
# Move to next step
|
||||||
self.current_index += 1
|
self.current_index += 1
|
||||||
mcrfpy.delTimer(self.check_timer_name)
|
timer.stop()
|
||||||
self._animate_next_step()
|
self._animate_next_step()
|
||||||
|
|
||||||
# Create test scene
|
# Create test scene
|
||||||
|
|
@ -165,7 +166,7 @@ def animate_both():
|
||||||
# Camera follow test
|
# Camera follow test
|
||||||
camera_follow = False
|
camera_follow = False
|
||||||
|
|
||||||
def update_camera(dt):
|
def update_camera(timer, runtime):
|
||||||
"""Update camera to follow player if enabled"""
|
"""Update camera to follow player if enabled"""
|
||||||
if camera_follow and player_animator and player_animator.animating:
|
if camera_follow and player_animator and player_animator.animating:
|
||||||
# Smooth camera follow
|
# Smooth camera follow
|
||||||
|
|
@ -205,7 +206,7 @@ chain_test.activate()
|
||||||
chain_test.on_key = handle_input
|
chain_test.on_key = handle_input
|
||||||
|
|
||||||
# Camera update timer
|
# Camera update timer
|
||||||
mcrfpy.setTimer("cam_update", update_camera, 100)
|
cam_update_timer = mcrfpy.Timer("cam_update", update_camera, 100)
|
||||||
|
|
||||||
print("Animation Chaining Test")
|
print("Animation Chaining Test")
|
||||||
print("=======================")
|
print("=======================")
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,9 @@ class AnimationTracker:
|
||||||
|
|
||||||
# Set timer to check completion
|
# Set timer to check completion
|
||||||
check_interval = 100 # ms
|
check_interval = 100 # ms
|
||||||
mcrfpy.setTimer(f"check_{self.name}", self._check_complete, check_interval)
|
self._check_timer = mcrfpy.Timer(f"check_{self.name}", self._check_complete, check_interval)
|
||||||
|
|
||||||
def _check_complete(self, dt):
|
def _check_complete(self, timer, runtime):
|
||||||
"""Check if animation is complete"""
|
"""Check if animation is complete"""
|
||||||
if self.animation and hasattr(self.animation, 'is_complete') and self.animation.is_complete:
|
if self.animation and hasattr(self.animation, 'is_complete') and self.animation.is_complete:
|
||||||
# Log completion
|
# Log completion
|
||||||
|
|
@ -56,7 +56,7 @@ class AnimationTracker:
|
||||||
del active_animations[self.name]
|
del active_animations[self.name]
|
||||||
|
|
||||||
# Stop checking
|
# Stop checking
|
||||||
mcrfpy.delTimer(f"check_{self.name}")
|
timer.stop()
|
||||||
|
|
||||||
# Create test scene
|
# Create test scene
|
||||||
anim_debug = mcrfpy.Scene("anim_debug")
|
anim_debug = mcrfpy.Scene("anim_debug")
|
||||||
|
|
@ -119,12 +119,13 @@ def test_rapid_fire():
|
||||||
anim1.start()
|
anim1.start()
|
||||||
|
|
||||||
# Start another after 500ms (before first completes)
|
# Start another after 500ms (before first completes)
|
||||||
def start_second(dt):
|
def start_second(timer, runtime):
|
||||||
anim2 = AnimationTracker("rapid_2", entity, "x", 12.0, 1.0)
|
anim2 = AnimationTracker("rapid_2", entity, "x", 12.0, 1.0)
|
||||||
anim2.start()
|
anim2.start()
|
||||||
mcrfpy.delTimer("rapid_timer")
|
timer.stop()
|
||||||
|
|
||||||
mcrfpy.setTimer("rapid_timer", start_second, 500)
|
global rapid_timer
|
||||||
|
rapid_timer = mcrfpy.Timer("rapid_timer", start_second, 500, once=True)
|
||||||
|
|
||||||
def test_sequential():
|
def test_sequential():
|
||||||
"""Test proper sequential animations"""
|
"""Test proper sequential animations"""
|
||||||
|
|
@ -149,7 +150,7 @@ def test_sequential():
|
||||||
|
|
||||||
# Schedule next
|
# Schedule next
|
||||||
delay = int(duration * 1000) + 100 # Add buffer
|
delay = int(duration * 1000) + 100 # Add buffer
|
||||||
mcrfpy.setTimer(f"seq_timer_{index}", lambda dt: run_sequence(index + 1), delay)
|
mcrfpy.Timer(f"seq_timer_{index}", lambda t, r: run_sequence(index + 1), delay, once=True)
|
||||||
|
|
||||||
run_sequence()
|
run_sequence()
|
||||||
|
|
||||||
|
|
@ -163,16 +164,17 @@ def test_conflicting():
|
||||||
anim1.start()
|
anim1.start()
|
||||||
|
|
||||||
# After 1 second, start conflicting animation to x=2
|
# After 1 second, start conflicting animation to x=2
|
||||||
def start_conflict(dt):
|
def start_conflict(timer, runtime):
|
||||||
print("Starting conflicting animation!")
|
print("Starting conflicting animation!")
|
||||||
anim2 = AnimationTracker("conflict_2", entity, "x", 2.0, 1.0)
|
anim2 = AnimationTracker("conflict_2", entity, "x", 2.0, 1.0)
|
||||||
anim2.start()
|
anim2.start()
|
||||||
mcrfpy.delTimer("conflict_timer")
|
timer.stop()
|
||||||
|
|
||||||
mcrfpy.setTimer("conflict_timer", start_conflict, 1000)
|
global conflict_timer
|
||||||
|
conflict_timer = mcrfpy.Timer("conflict_timer", start_conflict, 1000, once=True)
|
||||||
|
|
||||||
# Update display
|
# Update display
|
||||||
def update_display(dt):
|
def update_display(timer, runtime):
|
||||||
pos_display.text = f"Entity position: ({entity.x:.2f}, {entity.y:.2f})"
|
pos_display.text = f"Entity position: ({entity.x:.2f}, {entity.y:.2f})"
|
||||||
active_display.text = f"Active animations: {len(active_animations)}"
|
active_display.text = f"Active animations: {len(active_animations)}"
|
||||||
|
|
||||||
|
|
@ -217,7 +219,7 @@ def handle_input(key, state):
|
||||||
# Setup
|
# Setup
|
||||||
anim_debug.activate()
|
anim_debug.activate()
|
||||||
anim_debug.on_key = handle_input
|
anim_debug.on_key = handle_input
|
||||||
mcrfpy.setTimer("update", update_display, 100)
|
update_display_timer = mcrfpy.Timer("update", update_display, 100)
|
||||||
|
|
||||||
print("Animation Debug Tool")
|
print("Animation Debug Tool")
|
||||||
print("====================")
|
print("====================")
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ def test_8_replace_completes_old():
|
||||||
test_result("Replace completes old animation", False, str(e))
|
test_result("Replace completes old animation", False, str(e))
|
||||||
|
|
||||||
|
|
||||||
def run_all_tests(runtime):
|
def run_all_tests(timer, runtime):
|
||||||
"""Run all property locking tests"""
|
"""Run all property locking tests"""
|
||||||
print("\nRunning Animation Property Locking Tests...")
|
print("\nRunning Animation Property Locking Tests...")
|
||||||
print("-" * 50)
|
print("-" * 50)
|
||||||
|
|
@ -246,4 +246,4 @@ test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
# Start tests after a brief delay to allow scene to initialize
|
# Start tests after a brief delay to allow scene to initialize
|
||||||
mcrfpy.setTimer("start", run_all_tests, 100)
|
mcrfpy.Timer("start", run_all_tests, 100, once=True)
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ def test_4_multiple_animations_timer():
|
||||||
"""Test creating multiple animations in timer callback"""
|
"""Test creating multiple animations in timer callback"""
|
||||||
success = False
|
success = False
|
||||||
|
|
||||||
def create_animations(runtime):
|
def create_animations(timer, runtime):
|
||||||
nonlocal success
|
nonlocal success
|
||||||
try:
|
try:
|
||||||
ui = test.children
|
ui = test.children
|
||||||
|
|
@ -110,15 +110,15 @@ def test_4_multiple_animations_timer():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Timer animation error: {e}")
|
print(f"Timer animation error: {e}")
|
||||||
finally:
|
finally:
|
||||||
mcrfpy.setTimer("exit", lambda t: None, 100)
|
mcrfpy.Timer("exit", lambda t, r: None, 100, once=True)
|
||||||
|
|
||||||
# Clear scene
|
# Clear scene
|
||||||
ui = test.children
|
ui = test.children
|
||||||
while len(ui) > 0:
|
while len(ui) > 0:
|
||||||
ui.remove(len(ui) - 1)
|
ui.remove(len(ui) - 1)
|
||||||
|
|
||||||
mcrfpy.setTimer("test", create_animations, 50)
|
mcrfpy.Timer("test", create_animations, 50, once=True)
|
||||||
mcrfpy.setTimer("check", lambda t: test_result("Multiple animations in timer", success), 200)
|
mcrfpy.Timer("check", lambda t, r: test_result("Multiple animations in timer", success), 200, once=True)
|
||||||
|
|
||||||
def test_5_scene_cleanup():
|
def test_5_scene_cleanup():
|
||||||
"""Test that changing scenes cleans up animations"""
|
"""Test that changing scenes cleans up animations"""
|
||||||
|
|
@ -168,7 +168,7 @@ def test_6_animation_after_clear():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
test_result("Animation after UI clear", False, str(e))
|
test_result("Animation after UI clear", False, str(e))
|
||||||
|
|
||||||
def run_all_tests(runtime):
|
def run_all_tests(timer, runtime):
|
||||||
"""Run all RAII tests"""
|
"""Run all RAII tests"""
|
||||||
print("\nRunning RAII Animation Tests...")
|
print("\nRunning RAII Animation Tests...")
|
||||||
print("-" * 40)
|
print("-" * 40)
|
||||||
|
|
@ -181,25 +181,25 @@ def run_all_tests(runtime):
|
||||||
test_6_animation_after_clear()
|
test_6_animation_after_clear()
|
||||||
|
|
||||||
# Schedule result summary
|
# Schedule result summary
|
||||||
mcrfpy.setTimer("results", print_results, 500)
|
mcrfpy.Timer("results", print_results, 500, once=True)
|
||||||
|
|
||||||
def print_results(runtime):
|
def print_results(timer, runtime):
|
||||||
"""Print test results"""
|
"""Print test results"""
|
||||||
print("\n" + "=" * 40)
|
print("\n" + "=" * 40)
|
||||||
print(f"Tests passed: {tests_passed}")
|
print(f"Tests passed: {tests_passed}")
|
||||||
print(f"Tests failed: {tests_failed}")
|
print(f"Tests failed: {tests_failed}")
|
||||||
|
|
||||||
if tests_failed == 0:
|
if tests_failed == 0:
|
||||||
print("\n✓ All tests passed! RAII implementation is working correctly.")
|
print("\n+ All tests passed! RAII implementation is working correctly.")
|
||||||
else:
|
else:
|
||||||
print(f"\n✗ {tests_failed} tests failed.")
|
print(f"\nx {tests_failed} tests failed.")
|
||||||
print("\nFailed tests:")
|
print("\nFailed tests:")
|
||||||
for name, passed, details in test_results:
|
for name, passed, details in test_results:
|
||||||
if not passed:
|
if not passed:
|
||||||
print(f" - {name}: {details}")
|
print(f" - {name}: {details}")
|
||||||
|
|
||||||
# Exit
|
# Exit
|
||||||
mcrfpy.setTimer("exit", lambda t: sys.exit(0 if tests_failed == 0 else 1), 500)
|
mcrfpy.Timer("exit", lambda t, r: sys.exit(0 if tests_failed == 0 else 1), 500, once=True)
|
||||||
|
|
||||||
# Setup and run
|
# Setup and run
|
||||||
test = mcrfpy.Scene("test")
|
test = mcrfpy.Scene("test")
|
||||||
|
|
@ -212,4 +212,4 @@ bg.fill_color = mcrfpy.Color(20, 20, 30)
|
||||||
ui.append(bg)
|
ui.append(bg)
|
||||||
|
|
||||||
# Start tests
|
# Start tests
|
||||||
mcrfpy.setTimer("start", run_all_tests, 100)
|
start_timer = mcrfpy.Timer("start", run_all_tests, 100, once=True)
|
||||||
|
|
@ -6,7 +6,7 @@ Test if the crash is related to removing animated objects
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def clear_and_recreate(runtime):
|
def clear_and_recreate(timer, runtime):
|
||||||
"""Clear UI and recreate - mimics demo switching"""
|
"""Clear UI and recreate - mimics demo switching"""
|
||||||
print(f"\nTimer called at {runtime}")
|
print(f"\nTimer called at {runtime}")
|
||||||
|
|
||||||
|
|
@ -33,7 +33,8 @@ def clear_and_recreate(runtime):
|
||||||
print("New objects created and animated")
|
print("New objects created and animated")
|
||||||
|
|
||||||
# Schedule exit
|
# Schedule exit
|
||||||
mcrfpy.setTimer("exit", lambda t: sys.exit(0), 2000)
|
global exit_timer
|
||||||
|
exit_timer = mcrfpy.Timer("exit", lambda t, r: sys.exit(0), 2000, once=True)
|
||||||
|
|
||||||
# Create initial scene
|
# Create initial scene
|
||||||
print("Creating scene...")
|
print("Creating scene...")
|
||||||
|
|
@ -60,6 +61,6 @@ for i in range(10):
|
||||||
print(f"Initial scene has {len(ui)} elements")
|
print(f"Initial scene has {len(ui)} elements")
|
||||||
|
|
||||||
# Schedule the clear and recreate
|
# Schedule the clear and recreate
|
||||||
mcrfpy.setTimer("switch", clear_and_recreate, 1000)
|
switch_timer = mcrfpy.Timer("switch", clear_and_recreate, 1000, once=True)
|
||||||
|
|
||||||
print("\nEntering game loop...")
|
print("\nEntering game loop...")
|
||||||
|
|
@ -114,7 +114,7 @@ print(" - Empty paths returned for blocked destinations")
|
||||||
print(" - Diagonal movement supported")
|
print(" - Diagonal movement supported")
|
||||||
|
|
||||||
# Quick visual test
|
# Quick visual test
|
||||||
def visual_test(runtime):
|
def visual_test(timer, runtime):
|
||||||
print("\nVisual test timer fired")
|
print("\nVisual test timer fired")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
@ -125,6 +125,6 @@ grid.position = (50, 50)
|
||||||
grid.size = (400, 400)
|
grid.size = (400, 400)
|
||||||
|
|
||||||
astar_test.activate()
|
astar_test.activate()
|
||||||
mcrfpy.setTimer("visual", visual_test, 100)
|
visual_test_timer = mcrfpy.Timer("visual", visual_test, 100, once=True)
|
||||||
|
|
||||||
print("\nStarting visual test...")
|
print("\nStarting visual test...")
|
||||||
|
|
@ -6,7 +6,7 @@ Test #94: Color helper methods - from_hex, to_hex, lerp
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def test_color_helpers(runtime):
|
def test_color_helpers(timer, runtime):
|
||||||
"""Test Color helper methods"""
|
"""Test Color helper methods"""
|
||||||
|
|
||||||
all_pass = True
|
all_pass = True
|
||||||
|
|
@ -179,4 +179,4 @@ def test_color_helpers(runtime):
|
||||||
|
|
||||||
# Run test
|
# Run test
|
||||||
test = mcrfpy.Scene("test")
|
test = mcrfpy.Scene("test")
|
||||||
mcrfpy.setTimer("test", test_color_helpers, 100)
|
test_timer = mcrfpy.Timer("test", test_color_helpers, 100, once=True)
|
||||||
|
|
@ -183,7 +183,7 @@ def test_multi_target_scenario():
|
||||||
cell.tilesprite = 83 # S for safe
|
cell.tilesprite = 83 # S for safe
|
||||||
grid._color_layer.set(best_pos[0], best_pos[1], mcrfpy.Color(0, 255, 0))
|
grid._color_layer.set(best_pos[0], best_pos[1], mcrfpy.Color(0, 255, 0))
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
"""Timer callback to run tests after scene loads"""
|
"""Timer callback to run tests after scene loads"""
|
||||||
test_basic_dijkstra()
|
test_basic_dijkstra()
|
||||||
test_libtcod_interface()
|
test_libtcod_interface()
|
||||||
|
|
@ -221,7 +221,7 @@ title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||||
ui.append(title)
|
ui.append(title)
|
||||||
|
|
||||||
# Set timer to run tests
|
# Set timer to run tests
|
||||||
mcrfpy.setTimer("test", run_test, 100)
|
test_timer = mcrfpy.Timer("test", run_test, 100, once=True)
|
||||||
|
|
||||||
# Show scene
|
# Show scene
|
||||||
dijkstra_test.activate()
|
dijkstra_test.activate()
|
||||||
|
|
@ -20,7 +20,7 @@ def test_method_docs():
|
||||||
'createSoundBuffer', 'loadMusic', 'setMusicVolume', 'setSoundVolume',
|
'createSoundBuffer', 'loadMusic', 'setMusicVolume', 'setSoundVolume',
|
||||||
'playSound', 'getMusicVolume', 'getSoundVolume', 'sceneUI',
|
'playSound', 'getMusicVolume', 'getSoundVolume', 'sceneUI',
|
||||||
'currentScene', 'setScene', 'createScene', 'keypressScene',
|
'currentScene', 'setScene', 'createScene', 'keypressScene',
|
||||||
'setTimer', 'delTimer', 'exit', 'setScale', 'find', 'findAll',
|
'exit', 'setScale', 'find', 'findAll',
|
||||||
'getMetrics'
|
'getMetrics'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ def test_class_docs():
|
||||||
"""Test class documentation."""
|
"""Test class documentation."""
|
||||||
print("=== Class Documentation ===")
|
print("=== Class Documentation ===")
|
||||||
|
|
||||||
classes = ['Frame', 'Caption', 'Sprite', 'Grid', 'Entity', 'Color', 'Vector', 'Texture', 'Font']
|
classes = ['Frame', 'Caption', 'Sprite', 'Grid', 'Entity', 'Color', 'Vector', 'Texture', 'Font', 'Timer']
|
||||||
|
|
||||||
for class_name in classes:
|
for class_name in classes:
|
||||||
if hasattr(mcrfpy, class_name):
|
if hasattr(mcrfpy, class_name):
|
||||||
|
|
@ -80,12 +80,12 @@ def test_method_signatures():
|
||||||
else:
|
else:
|
||||||
print("✗ setScene signature incorrect or missing")
|
print("✗ setScene signature incorrect or missing")
|
||||||
|
|
||||||
if hasattr(mcrfpy, 'setTimer'):
|
if hasattr(mcrfpy, 'Timer'):
|
||||||
doc = mcrfpy.setTimer.__doc__
|
doc = mcrfpy.Timer.__doc__
|
||||||
if doc and 'setTimer(name: str, handler: callable, interval: int)' in doc:
|
if doc and 'Timer' in doc:
|
||||||
print("✓ setTimer signature correct")
|
print("+ Timer class documentation present")
|
||||||
else:
|
else:
|
||||||
print("✗ setTimer signature incorrect or missing")
|
print("x Timer class documentation missing")
|
||||||
|
|
||||||
if hasattr(mcrfpy, 'find'):
|
if hasattr(mcrfpy, 'find'):
|
||||||
doc = mcrfpy.find.__doc__
|
doc = mcrfpy.find.__doc__
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ test.activate()
|
||||||
print("Scene created, no animations added")
|
print("Scene created, no animations added")
|
||||||
print("Starting game loop in 100ms...")
|
print("Starting game loop in 100ms...")
|
||||||
|
|
||||||
def check_alive(runtime):
|
def check_alive(timer, runtime):
|
||||||
print(f"Timer fired at {runtime}ms - AnimationManager survived!")
|
print(f"Timer fired at {runtime}ms - AnimationManager survived!")
|
||||||
mcrfpy.setTimer("exit", lambda t: mcrfpy.exit(), 100)
|
mcrfpy.Timer("exit", lambda t, r: mcrfpy.exit(), 100, once=True)
|
||||||
|
|
||||||
mcrfpy.setTimer("check", check_alive, 1000)
|
mcrfpy.Timer("check", check_alive, 1000, once=True)
|
||||||
print("If this crashes immediately, AnimationManager has an issue with empty state")
|
print("If this crashes immediately, AnimationManager has an issue with empty state")
|
||||||
|
|
@ -77,7 +77,7 @@ current_waypoint = 0
|
||||||
animating = False
|
animating = False
|
||||||
waypoints = [(5,5), (10,5), (10,10), (5,10), (5,5)]
|
waypoints = [(5,5), (10,5), (10,10), (5,10), (5,5)]
|
||||||
|
|
||||||
def update_position_display(dt):
|
def update_position_display(timer, runtime):
|
||||||
"""Update position display every 200ms"""
|
"""Update position display every 200ms"""
|
||||||
pos_display.text = f"Entity Position: ({entity.x:.2f}, {entity.y:.2f})"
|
pos_display.text = f"Entity Position: ({entity.x:.2f}, {entity.y:.2f})"
|
||||||
|
|
||||||
|
|
@ -126,7 +126,8 @@ def animate_to_next_waypoint():
|
||||||
current_waypoint += 1
|
current_waypoint += 1
|
||||||
|
|
||||||
# Schedule next waypoint
|
# Schedule next waypoint
|
||||||
mcrfpy.setTimer("next_waypoint", lambda dt: animate_to_next_waypoint(), int(duration * 1000 + 100))
|
global next_waypoint_timer
|
||||||
|
next_waypoint_timer = mcrfpy.Timer("next_waypoint", lambda t, r: animate_to_next_waypoint(), int(duration * 1000 + 100), once=True)
|
||||||
|
|
||||||
def start_animation():
|
def start_animation():
|
||||||
"""Start or restart the animation sequence"""
|
"""Start or restart the animation sequence"""
|
||||||
|
|
@ -186,7 +187,7 @@ test_anim.activate()
|
||||||
test_anim.on_key = handle_input
|
test_anim.on_key = handle_input
|
||||||
|
|
||||||
# Start position update timer
|
# Start position update timer
|
||||||
mcrfpy.setTimer("update_pos", update_position_display, 200)
|
update_pos_timer = mcrfpy.Timer("update_pos", update_position_display, 200)
|
||||||
|
|
||||||
# No perspective (omniscient view)
|
# No perspective (omniscient view)
|
||||||
grid.perspective = -1
|
grid.perspective = -1
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ status.fill_color = mcrfpy.Color(200, 200, 200)
|
||||||
ui.append(status)
|
ui.append(status)
|
||||||
|
|
||||||
# Update display
|
# Update display
|
||||||
def update_display(dt):
|
def update_display(timer, runtime):
|
||||||
pos_info.text = f"Entity Grid Position: ({entity.x:.2f}, {entity.y:.2f})"
|
pos_info.text = f"Entity Grid Position: ({entity.x:.2f}, {entity.y:.2f})"
|
||||||
# We can't access sprite position from Python, but in C++ it would show
|
# We can't access sprite position from Python, but in C++ it would show
|
||||||
# the issue: sprite position would be (2, 2) instead of pixel coords
|
# the issue: sprite position would be (2, 2) instead of pixel coords
|
||||||
|
|
@ -113,7 +113,7 @@ def handle_input(key, state):
|
||||||
# Setup
|
# Setup
|
||||||
fix_demo.activate()
|
fix_demo.activate()
|
||||||
fix_demo.on_key = handle_input
|
fix_demo.on_key = handle_input
|
||||||
mcrfpy.setTimer("update", update_display, 100)
|
update_timer = mcrfpy.Timer("update", update_display, 100)
|
||||||
|
|
||||||
print("Ready to demonstrate the issue.")
|
print("Ready to demonstrate the issue.")
|
||||||
print()
|
print()
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ import sys
|
||||||
# Module-level state to avoid closures
|
# Module-level state to avoid closures
|
||||||
_test_state = {}
|
_test_state = {}
|
||||||
|
|
||||||
def take_second_screenshot(runtime):
|
def take_second_screenshot(timer, runtime):
|
||||||
"""Take final screenshot and exit"""
|
"""Take final screenshot and exit"""
|
||||||
mcrfpy.delTimer("screenshot2")
|
timer.stop()
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
automation.screenshot("frame_clipping_animated.png")
|
automation.screenshot("frame_clipping_animated.png")
|
||||||
print("\nTest completed successfully!")
|
print("\nTest completed successfully!")
|
||||||
|
|
@ -19,20 +19,21 @@ def take_second_screenshot(runtime):
|
||||||
print(" - frame_clipping_animated.png (with animation)")
|
print(" - frame_clipping_animated.png (with animation)")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def animate_frames(runtime):
|
def animate_frames(timer, runtime):
|
||||||
"""Animate frames to demonstrate clipping"""
|
"""Animate frames to demonstrate clipping"""
|
||||||
mcrfpy.delTimer("animate")
|
timer.stop()
|
||||||
scene = test.children
|
scene = test.children
|
||||||
# Move child frames
|
# Move child frames
|
||||||
parent1 = scene[0]
|
parent1 = scene[0]
|
||||||
parent2 = scene[1]
|
parent2 = scene[1]
|
||||||
parent1.children[1].x = 50
|
parent1.children[1].x = 50
|
||||||
parent2.children[1].x = 50
|
parent2.children[1].x = 50
|
||||||
mcrfpy.setTimer("screenshot2", take_second_screenshot, 500)
|
global screenshot2_timer
|
||||||
|
screenshot2_timer = mcrfpy.Timer("screenshot2", take_second_screenshot, 500, once=True)
|
||||||
|
|
||||||
def test_clipping(runtime):
|
def test_clipping(timer, runtime):
|
||||||
"""Test that clip_children property works correctly"""
|
"""Test that clip_children property works correctly"""
|
||||||
mcrfpy.delTimer("test_clipping")
|
timer.stop()
|
||||||
|
|
||||||
print("Testing UIFrame clipping functionality...")
|
print("Testing UIFrame clipping functionality...")
|
||||||
|
|
||||||
|
|
@ -115,7 +116,8 @@ def test_clipping(runtime):
|
||||||
print(f"PASS: clip_children correctly rejected non-boolean: {e}")
|
print(f"PASS: clip_children correctly rejected non-boolean: {e}")
|
||||||
|
|
||||||
# Start animation after a short delay
|
# Start animation after a short delay
|
||||||
mcrfpy.setTimer("animate", animate_frames, 100)
|
global animate_timer
|
||||||
|
animate_timer = mcrfpy.Timer("animate", animate_frames, 100, once=True)
|
||||||
|
|
||||||
def handle_keypress(key, modifiers):
|
def handle_keypress(key, modifiers):
|
||||||
if key == "c":
|
if key == "c":
|
||||||
|
|
@ -129,5 +131,5 @@ print("Creating test scene...")
|
||||||
test = mcrfpy.Scene("test")
|
test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
test.on_key = handle_keypress
|
test.on_key = handle_keypress
|
||||||
mcrfpy.setTimer("test_clipping", test_clipping, 100)
|
test_clipping_timer = mcrfpy.Timer("test_clipping", test_clipping, 100, once=True)
|
||||||
print("Test scheduled, running...")
|
print("Test scheduled, running...")
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ import mcrfpy
|
||||||
from mcrfpy import Color, Frame, Caption, Vector
|
from mcrfpy import Color, Frame, Caption, Vector
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def test_nested_clipping(runtime):
|
def test_nested_clipping(timer, runtime):
|
||||||
"""Test nested frames with clipping"""
|
"""Test nested frames with clipping"""
|
||||||
mcrfpy.delTimer("test_nested_clipping")
|
timer.stop()
|
||||||
|
|
||||||
print("Testing advanced UIFrame clipping with nested frames...")
|
print("Testing advanced UIFrame clipping with nested frames...")
|
||||||
|
|
||||||
|
|
@ -62,8 +62,8 @@ def test_nested_clipping(runtime):
|
||||||
print(f"Inner frame size: {inner.w}x{inner.h}")
|
print(f"Inner frame size: {inner.w}x{inner.h}")
|
||||||
|
|
||||||
# Dynamically resize frames to test RenderTexture recreation
|
# Dynamically resize frames to test RenderTexture recreation
|
||||||
def resize_test(runtime):
|
def resize_test(timer, runtime):
|
||||||
mcrfpy.delTimer("resize_test")
|
timer.stop()
|
||||||
print("Resizing frames to test RenderTexture recreation...")
|
print("Resizing frames to test RenderTexture recreation...")
|
||||||
outer.w = 450
|
outer.w = 450
|
||||||
outer.h = 350
|
outer.h = 350
|
||||||
|
|
@ -73,10 +73,11 @@ def test_nested_clipping(runtime):
|
||||||
print(f"New inner frame size: {inner.w}x{inner.h}")
|
print(f"New inner frame size: {inner.w}x{inner.h}")
|
||||||
|
|
||||||
# Take screenshot after resize
|
# Take screenshot after resize
|
||||||
mcrfpy.setTimer("screenshot_resize", take_resize_screenshot, 500)
|
global screenshot_resize_timer
|
||||||
|
screenshot_resize_timer = mcrfpy.Timer("screenshot_resize", take_resize_screenshot, 500, once=True)
|
||||||
|
|
||||||
def take_resize_screenshot(runtime):
|
def take_resize_screenshot(timer, runtime):
|
||||||
mcrfpy.delTimer("screenshot_resize")
|
timer.stop()
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
automation.screenshot("frame_clipping_resized.png")
|
automation.screenshot("frame_clipping_resized.png")
|
||||||
print("\nAdvanced test completed!")
|
print("\nAdvanced test completed!")
|
||||||
|
|
@ -90,7 +91,8 @@ def test_nested_clipping(runtime):
|
||||||
print("Initial screenshot saved: frame_clipping_nested.png")
|
print("Initial screenshot saved: frame_clipping_nested.png")
|
||||||
|
|
||||||
# Schedule resize test
|
# Schedule resize test
|
||||||
mcrfpy.setTimer("resize_test", resize_test, 1000)
|
global resize_test_timer
|
||||||
|
resize_test_timer = mcrfpy.Timer("resize_test", resize_test, 1000, once=True)
|
||||||
|
|
||||||
# Main execution
|
# Main execution
|
||||||
print("Creating advanced test scene...")
|
print("Creating advanced test scene...")
|
||||||
|
|
@ -98,6 +100,6 @@ test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
# Schedule the test
|
# Schedule the test
|
||||||
mcrfpy.setTimer("test_nested_clipping", test_nested_clipping, 100)
|
test_nested_clipping_timer = mcrfpy.Timer("test_nested_clipping", test_nested_clipping, 100, once=True)
|
||||||
|
|
||||||
print("Advanced test scheduled, running...")
|
print("Advanced test scheduled, running...")
|
||||||
|
|
@ -42,25 +42,25 @@ def test_grid_background():
|
||||||
# Activate the scene
|
# Activate the scene
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
def run_tests(dt):
|
def run_tests(timer, runtime):
|
||||||
"""Run background color tests"""
|
"""Run background color tests"""
|
||||||
mcrfpy.delTimer("run_tests")
|
timer.stop()
|
||||||
|
|
||||||
print("\nTest 1: Default background color")
|
print("\nTest 1: Default background color")
|
||||||
default_color = grid.background_color
|
default_color = grid.background_color
|
||||||
print(f"Default: R={default_color.r}, G={default_color.g}, B={default_color.b}, A={default_color.a}")
|
print(f"Default: R={default_color.r}, G={default_color.g}, B={default_color.b}, A={default_color.a}")
|
||||||
color_display.text = f"R:{default_color.r} G:{default_color.g} B:{default_color.b}"
|
color_display.text = f"R:{default_color.r} G:{default_color.g} B:{default_color.b}"
|
||||||
|
|
||||||
def test_set_color(dt):
|
def test_set_color(timer, runtime):
|
||||||
mcrfpy.delTimer("test_set")
|
timer.stop()
|
||||||
print("\nTest 2: Set background to blue")
|
print("\nTest 2: Set background to blue")
|
||||||
grid.background_color = mcrfpy.Color(20, 40, 100)
|
grid.background_color = mcrfpy.Color(20, 40, 100)
|
||||||
new_color = grid.background_color
|
new_color = grid.background_color
|
||||||
print(f"✓ Set to: R={new_color.r}, G={new_color.g}, B={new_color.b}")
|
print(f"+ Set to: R={new_color.r}, G={new_color.g}, B={new_color.b}")
|
||||||
color_display.text = f"R:{new_color.r} G:{new_color.g} B:{new_color.b}"
|
color_display.text = f"R:{new_color.r} G:{new_color.g} B:{new_color.b}"
|
||||||
|
|
||||||
def test_animation(dt):
|
def test_animation(timer, runtime):
|
||||||
mcrfpy.delTimer("test_anim")
|
timer.stop()
|
||||||
print("\nTest 3: Manual color cycling")
|
print("\nTest 3: Manual color cycling")
|
||||||
# Manually change color to test property is working
|
# Manually change color to test property is working
|
||||||
colors = [
|
colors = [
|
||||||
|
|
@ -71,39 +71,39 @@ def test_grid_background():
|
||||||
|
|
||||||
color_index = [0] # Use list to allow modification in nested function
|
color_index = [0] # Use list to allow modification in nested function
|
||||||
|
|
||||||
def cycle_red(dt):
|
def cycle_red(t, r):
|
||||||
mcrfpy.delTimer("cycle_0")
|
t.stop()
|
||||||
grid.background_color = colors[0]
|
grid.background_color = colors[0]
|
||||||
c = grid.background_color
|
c = grid.background_color
|
||||||
color_display.text = f"R:{c.r} G:{c.g} B:{c.b}"
|
color_display.text = f"R:{c.r} G:{c.g} B:{c.b}"
|
||||||
print(f"✓ Set to Red: R={c.r}, G={c.g}, B={c.b}")
|
print(f"+ Set to Red: R={c.r}, G={c.g}, B={c.b}")
|
||||||
|
|
||||||
def cycle_green(dt):
|
def cycle_green(t, r):
|
||||||
mcrfpy.delTimer("cycle_1")
|
t.stop()
|
||||||
grid.background_color = colors[1]
|
grid.background_color = colors[1]
|
||||||
c = grid.background_color
|
c = grid.background_color
|
||||||
color_display.text = f"R:{c.r} G:{c.g} B:{c.b}"
|
color_display.text = f"R:{c.r} G:{c.g} B:{c.b}"
|
||||||
print(f"✓ Set to Green: R={c.r}, G={c.g}, B={c.b}")
|
print(f"+ Set to Green: R={c.r}, G={c.g}, B={c.b}")
|
||||||
|
|
||||||
def cycle_blue(dt):
|
def cycle_blue(t, r):
|
||||||
mcrfpy.delTimer("cycle_2")
|
t.stop()
|
||||||
grid.background_color = colors[2]
|
grid.background_color = colors[2]
|
||||||
c = grid.background_color
|
c = grid.background_color
|
||||||
color_display.text = f"R:{c.r} G:{c.g} B:{c.b}"
|
color_display.text = f"R:{c.r} G:{c.g} B:{c.b}"
|
||||||
print(f"✓ Set to Blue: R={c.r}, G={c.g}, B={c.b}")
|
print(f"+ Set to Blue: R={c.r}, G={c.g}, B={c.b}")
|
||||||
|
|
||||||
# Cycle through colors
|
# Cycle through colors
|
||||||
mcrfpy.setTimer("cycle_0", cycle_red, 100)
|
mcrfpy.Timer("cycle_0", cycle_red, 100, once=True)
|
||||||
mcrfpy.setTimer("cycle_1", cycle_green, 400)
|
mcrfpy.Timer("cycle_1", cycle_green, 400, once=True)
|
||||||
mcrfpy.setTimer("cycle_2", cycle_blue, 700)
|
mcrfpy.Timer("cycle_2", cycle_blue, 700, once=True)
|
||||||
|
|
||||||
def test_complete(dt):
|
def test_complete(timer, runtime):
|
||||||
mcrfpy.delTimer("complete")
|
timer.stop()
|
||||||
print("\nTest 4: Final color check")
|
print("\nTest 4: Final color check")
|
||||||
final_color = grid.background_color
|
final_color = grid.background_color
|
||||||
print(f"Final: R={final_color.r}, G={final_color.g}, B={final_color.b}")
|
print(f"Final: R={final_color.r}, G={final_color.g}, B={final_color.b}")
|
||||||
|
|
||||||
print("\n✓ Grid background color tests completed!")
|
print("\n+ Grid background color tests completed!")
|
||||||
print("- Default background color works")
|
print("- Default background color works")
|
||||||
print("- Setting background color works")
|
print("- Setting background color works")
|
||||||
print("- Color cycling works")
|
print("- Color cycling works")
|
||||||
|
|
@ -111,12 +111,12 @@ def test_grid_background():
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# Schedule tests
|
# Schedule tests
|
||||||
mcrfpy.setTimer("test_set", test_set_color, 1000)
|
mcrfpy.Timer("test_set", test_set_color, 1000, once=True)
|
||||||
mcrfpy.setTimer("test_anim", test_animation, 2000)
|
mcrfpy.Timer("test_anim", test_animation, 2000, once=True)
|
||||||
mcrfpy.setTimer("complete", test_complete, 4500)
|
mcrfpy.Timer("complete", test_complete, 4500, once=True)
|
||||||
|
|
||||||
# Start tests
|
# Start tests
|
||||||
mcrfpy.setTimer("run_tests", run_tests, 100)
|
mcrfpy.Timer("run_tests", run_tests, 100, once=True)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
test_grid_background()
|
test_grid_background()
|
||||||
|
|
@ -68,9 +68,7 @@ def test_cell_hover():
|
||||||
automation.moveTo(150, 150)
|
automation.moveTo(150, 150)
|
||||||
automation.moveTo(200, 200)
|
automation.moveTo(200, 200)
|
||||||
|
|
||||||
def check_hover(runtime):
|
def check_hover(timer, runtime):
|
||||||
mcrfpy.delTimer("check_hover")
|
|
||||||
|
|
||||||
print(f" Enter events: {len(enter_events)}, Exit events: {len(exit_events)}")
|
print(f" Enter events: {len(enter_events)}, Exit events: {len(exit_events)}")
|
||||||
print(f" Hovered cell: {grid.hovered_cell}")
|
print(f" Hovered cell: {grid.hovered_cell}")
|
||||||
|
|
||||||
|
|
@ -82,7 +80,7 @@ def test_cell_hover():
|
||||||
# Continue to click test
|
# Continue to click test
|
||||||
test_cell_click()
|
test_cell_click()
|
||||||
|
|
||||||
mcrfpy.setTimer("check_hover", check_hover, 200)
|
mcrfpy.Timer("check_hover", check_hover, 200, once=True)
|
||||||
|
|
||||||
|
|
||||||
def test_cell_click():
|
def test_cell_click():
|
||||||
|
|
@ -105,9 +103,7 @@ def test_cell_click():
|
||||||
|
|
||||||
automation.click(200, 200)
|
automation.click(200, 200)
|
||||||
|
|
||||||
def check_click(runtime):
|
def check_click(timer, runtime):
|
||||||
mcrfpy.delTimer("check_click")
|
|
||||||
|
|
||||||
print(f" Click events: {len(click_events)}")
|
print(f" Click events: {len(click_events)}")
|
||||||
|
|
||||||
if len(click_events) >= 1:
|
if len(click_events) >= 1:
|
||||||
|
|
@ -118,7 +114,7 @@ def test_cell_click():
|
||||||
print("\n=== All grid cell event tests passed! ===")
|
print("\n=== All grid cell event tests passed! ===")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
mcrfpy.setTimer("check_click", check_click, 200)
|
mcrfpy.Timer("check_click", check_click, 200, once=True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,18 @@ import mcrfpy
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def take_screenshot(runtime):
|
def take_screenshot(timer, runtime):
|
||||||
"""Take screenshot after render completes"""
|
"""Take screenshot after render completes"""
|
||||||
mcrfpy.delTimer("screenshot")
|
timer.stop()
|
||||||
automation.screenshot("test_grid_children_result.png")
|
automation.screenshot("test_grid_children_result.png")
|
||||||
|
|
||||||
print("Screenshot saved to test_grid_children_result.png")
|
print("Screenshot saved to test_grid_children_result.png")
|
||||||
print("PASS - Grid.children test completed")
|
print("PASS - Grid.children test completed")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
"""Main test - runs after scene is set up"""
|
"""Main test - runs after scene is set up"""
|
||||||
mcrfpy.delTimer("test")
|
timer.stop()
|
||||||
|
|
||||||
# Get the scene UI
|
# Get the scene UI
|
||||||
ui = test.children
|
ui = test.children
|
||||||
|
|
@ -119,11 +119,11 @@ def run_test(runtime):
|
||||||
print(f"\nFinal children count: {len(grid.children)}")
|
print(f"\nFinal children count: {len(grid.children)}")
|
||||||
|
|
||||||
# Schedule screenshot for next frame
|
# Schedule screenshot for next frame
|
||||||
mcrfpy.setTimer("screenshot", take_screenshot, 100)
|
mcrfpy.Timer("screenshot", take_screenshot, 100, once=True)
|
||||||
|
|
||||||
# Create a test scene
|
# Create a test scene
|
||||||
test = mcrfpy.Scene("test")
|
test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
# Schedule test to run after game loop starts
|
# Schedule test to run after game loop starts
|
||||||
mcrfpy.setTimer("test", run_test, 50)
|
mcrfpy.Timer("test", run_test, 50, once=True)
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,7 @@ def test_headless_click():
|
||||||
automation.click(150, 150)
|
automation.click(150, 150)
|
||||||
|
|
||||||
# Give time for events to process
|
# Give time for events to process
|
||||||
def check_results(runtime):
|
def check_results(timer, runtime):
|
||||||
mcrfpy.delTimer("check_click") # Clean up timer
|
|
||||||
|
|
||||||
if len(start_clicks) >= 1:
|
if len(start_clicks) >= 1:
|
||||||
print(f" - Click received: {len(start_clicks)} click(s)")
|
print(f" - Click received: {len(start_clicks)} click(s)")
|
||||||
# Verify position
|
# Verify position
|
||||||
|
|
@ -53,7 +51,7 @@ def test_headless_click():
|
||||||
print(f" - No clicks received: FAIL")
|
print(f" - No clicks received: FAIL")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
mcrfpy.setTimer("check_click", check_results, 200)
|
mcrfpy.Timer("check_click", check_results, 200, once=True)
|
||||||
|
|
||||||
|
|
||||||
def test_click_miss():
|
def test_click_miss():
|
||||||
|
|
@ -84,9 +82,7 @@ def test_click_miss():
|
||||||
print(" Clicking outside frame at (50, 50)...")
|
print(" Clicking outside frame at (50, 50)...")
|
||||||
automation.click(50, 50)
|
automation.click(50, 50)
|
||||||
|
|
||||||
def check_miss_results(runtime):
|
def check_miss_results(timer, runtime):
|
||||||
mcrfpy.delTimer("check_miss") # Clean up timer
|
|
||||||
|
|
||||||
if miss_count[0] == 0:
|
if miss_count[0] == 0:
|
||||||
print(" - No click on miss: PASS")
|
print(" - No click on miss: PASS")
|
||||||
# Now run the main click test
|
# Now run the main click test
|
||||||
|
|
@ -95,7 +91,7 @@ def test_click_miss():
|
||||||
print(f" - Unexpected {miss_count[0]} click(s): FAIL")
|
print(f" - Unexpected {miss_count[0]} click(s): FAIL")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
mcrfpy.setTimer("check_miss", check_miss_results, 200)
|
mcrfpy.Timer("check_miss", check_miss_results, 200, once=True)
|
||||||
|
|
||||||
|
|
||||||
def test_position_tracking():
|
def test_position_tracking():
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ frame = mcrfpy.Frame(pos=(100, 100), size=(200, 200))
|
||||||
frame.fill_color = mcrfpy.Color(255, 100, 100, 255)
|
frame.fill_color = mcrfpy.Color(255, 100, 100, 255)
|
||||||
ui.append(frame)
|
ui.append(frame)
|
||||||
|
|
||||||
def test_mode(runtime):
|
def test_mode(timer, runtime):
|
||||||
try:
|
try:
|
||||||
# Try to take a screenshot - this should work in both modes
|
# Try to take a screenshot - this should work in both modes
|
||||||
automation.screenshot("test_screenshot.png")
|
automation.screenshot("test_screenshot.png")
|
||||||
|
|
@ -36,4 +36,4 @@ def test_mode(runtime):
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# Run test after render loop starts
|
# Run test after render loop starts
|
||||||
mcrfpy.setTimer("test", test_mode, 100)
|
test_timer = mcrfpy.Timer("test", test_mode, 100, once=True)
|
||||||
|
|
@ -22,8 +22,8 @@ ui.append(caption)
|
||||||
print("Script started. Window should appear unless --headless was specified.")
|
print("Script started. Window should appear unless --headless was specified.")
|
||||||
|
|
||||||
# Exit after 2 seconds
|
# Exit after 2 seconds
|
||||||
def exit_test(runtime):
|
def exit_test(timer, runtime):
|
||||||
print("Test complete. Exiting.")
|
print("Test complete. Exiting.")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
mcrfpy.setTimer("exit", exit_test, 2000)
|
exit_timer = mcrfpy.Timer("exit", exit_test, 2000, once=True)
|
||||||
|
|
@ -5,8 +5,12 @@ import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
def test_metrics(runtime):
|
# Track success across callbacks
|
||||||
|
success = True
|
||||||
|
|
||||||
|
def test_metrics(timer, runtime):
|
||||||
"""Test the metrics after timer starts"""
|
"""Test the metrics after timer starts"""
|
||||||
|
global success
|
||||||
print("\nRunning metrics test...")
|
print("\nRunning metrics test...")
|
||||||
|
|
||||||
# Get metrics
|
# Get metrics
|
||||||
|
|
@ -23,7 +27,6 @@ def test_metrics(runtime):
|
||||||
print(f" Runtime: {metrics['runtime']:.2f} seconds")
|
print(f" Runtime: {metrics['runtime']:.2f} seconds")
|
||||||
|
|
||||||
# Test that metrics are reasonable
|
# Test that metrics are reasonable
|
||||||
success = True
|
|
||||||
|
|
||||||
# Frame time should be positive
|
# Frame time should be positive
|
||||||
if metrics['frame_time'] <= 0:
|
if metrics['frame_time'] <= 0:
|
||||||
|
|
@ -71,8 +74,13 @@ def test_metrics(runtime):
|
||||||
# Test metrics update over multiple frames
|
# Test metrics update over multiple frames
|
||||||
print("\n\nTesting metrics over multiple frames...")
|
print("\n\nTesting metrics over multiple frames...")
|
||||||
|
|
||||||
|
# Store initial metrics for comparison
|
||||||
|
initial_frame = metrics['current_frame']
|
||||||
|
initial_runtime = metrics['runtime']
|
||||||
|
|
||||||
# Schedule another check after 100ms
|
# Schedule another check after 100ms
|
||||||
def check_later(runtime2):
|
def check_later(timer2, runtime2):
|
||||||
|
global success
|
||||||
metrics2 = mcrfpy.getMetrics()
|
metrics2 = mcrfpy.getMetrics()
|
||||||
|
|
||||||
print(f"\nMetrics after 100ms:")
|
print(f"\nMetrics after 100ms:")
|
||||||
|
|
@ -82,15 +90,14 @@ def test_metrics(runtime):
|
||||||
print(f" Current Frame: {metrics2['current_frame']}")
|
print(f" Current Frame: {metrics2['current_frame']}")
|
||||||
|
|
||||||
# Frame count should have increased
|
# Frame count should have increased
|
||||||
if metrics2['current_frame'] > metrics['current_frame']:
|
if metrics2['current_frame'] > initial_frame:
|
||||||
print(" PASS: Frame count increased")
|
print(" PASS: Frame count increased")
|
||||||
else:
|
else:
|
||||||
print(" FAIL: Frame count did not increase")
|
print(" FAIL: Frame count did not increase")
|
||||||
nonlocal success
|
|
||||||
success = False
|
success = False
|
||||||
|
|
||||||
# Runtime should have increased
|
# Runtime should have increased
|
||||||
if metrics2['runtime'] > metrics['runtime']:
|
if metrics2['runtime'] > initial_runtime:
|
||||||
print(" PASS: Runtime increased")
|
print(" PASS: Runtime increased")
|
||||||
else:
|
else:
|
||||||
print(" FAIL: Runtime did not increase")
|
print(" FAIL: Runtime did not increase")
|
||||||
|
|
@ -104,7 +111,7 @@ def test_metrics(runtime):
|
||||||
|
|
||||||
sys.exit(0 if success else 1)
|
sys.exit(0 if success else 1)
|
||||||
|
|
||||||
mcrfpy.setTimer("check_later", check_later, 100)
|
mcrfpy.Timer("check_later", check_later, 100, once=True)
|
||||||
|
|
||||||
# Set up test scene
|
# Set up test scene
|
||||||
print("Setting up metrics test scene...")
|
print("Setting up metrics test scene...")
|
||||||
|
|
@ -136,4 +143,4 @@ ui.append(grid)
|
||||||
print(f"Created {len(ui)} UI elements (1 invisible)")
|
print(f"Created {len(ui)} UI elements (1 invisible)")
|
||||||
|
|
||||||
# Schedule test to run after render loop starts
|
# Schedule test to run after render loop starts
|
||||||
mcrfpy.setTimer("test", test_metrics, 50)
|
mcrfpy.Timer("test", test_metrics, 50, once=True)
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ def test_enter_exit_simulation():
|
||||||
automation.moveTo(50, 50)
|
automation.moveTo(50, 50)
|
||||||
|
|
||||||
# Give time for callbacks to execute
|
# Give time for callbacks to execute
|
||||||
def check_results(runtime):
|
def check_results(timer, runtime):
|
||||||
global enter_count, exit_count
|
global enter_count, exit_count
|
||||||
|
|
||||||
if enter_count >= 1 and exit_count >= 1:
|
if enter_count >= 1 and exit_count >= 1:
|
||||||
|
|
@ -166,7 +166,7 @@ def test_enter_exit_simulation():
|
||||||
print("\n=== Basic Mouse Enter/Exit tests passed! ===")
|
print("\n=== Basic Mouse Enter/Exit tests passed! ===")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
mcrfpy.setTimer("check", check_results, 200)
|
mcrfpy.Timer("check", check_results, 200, once=True)
|
||||||
|
|
||||||
|
|
||||||
def run_basic_tests():
|
def run_basic_tests():
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ This verifies the fix for requiring arguments even with safe default constructor
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def test_ui_constructors(runtime):
|
def test_ui_constructors(timer, runtime):
|
||||||
"""Test that UI classes can be instantiated without arguments"""
|
"""Test that UI classes can be instantiated without arguments"""
|
||||||
|
|
||||||
print("Testing UI class instantiation without arguments...")
|
print("Testing UI class instantiation without arguments...")
|
||||||
|
|
@ -88,4 +88,4 @@ def test_ui_constructors(runtime):
|
||||||
test = mcrfpy.Scene("test")
|
test = mcrfpy.Scene("test")
|
||||||
|
|
||||||
# Schedule the test to run after game initialization
|
# Schedule the test to run after game initialization
|
||||||
mcrfpy.setTimer("test", test_ui_constructors, 100)
|
test_timer = mcrfpy.Timer("test", test_ui_constructors, 100, once=True)
|
||||||
|
|
@ -57,9 +57,7 @@ def test_on_move_fires():
|
||||||
automation.moveTo(200, 200)
|
automation.moveTo(200, 200)
|
||||||
automation.moveTo(250, 250)
|
automation.moveTo(250, 250)
|
||||||
|
|
||||||
def check_results(runtime):
|
def check_results(timer, runtime):
|
||||||
mcrfpy.delTimer("check_move")
|
|
||||||
|
|
||||||
if move_count[0] >= 2:
|
if move_count[0] >= 2:
|
||||||
print(f" - on_move fired {move_count[0]} times: PASS")
|
print(f" - on_move fired {move_count[0]} times: PASS")
|
||||||
print(f" Positions: {positions[:5]}...")
|
print(f" Positions: {positions[:5]}...")
|
||||||
|
|
@ -71,7 +69,7 @@ def test_on_move_fires():
|
||||||
print("\n=== on_move basic tests passed! ===")
|
print("\n=== on_move basic tests passed! ===")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
mcrfpy.setTimer("check_move", check_results, 200)
|
mcrfpy.Timer("check_move", check_results, 200, once=True)
|
||||||
|
|
||||||
|
|
||||||
def test_on_move_not_outside():
|
def test_on_move_not_outside():
|
||||||
|
|
@ -99,9 +97,7 @@ def test_on_move_not_outside():
|
||||||
automation.moveTo(60, 60)
|
automation.moveTo(60, 60)
|
||||||
automation.moveTo(70, 70)
|
automation.moveTo(70, 70)
|
||||||
|
|
||||||
def check_results(runtime):
|
def check_results(timer, runtime):
|
||||||
mcrfpy.delTimer("check_outside")
|
|
||||||
|
|
||||||
if move_count[0] == 0:
|
if move_count[0] == 0:
|
||||||
print(" - No on_move outside bounds: PASS")
|
print(" - No on_move outside bounds: PASS")
|
||||||
# Chain to the firing test
|
# Chain to the firing test
|
||||||
|
|
@ -110,7 +106,7 @@ def test_on_move_not_outside():
|
||||||
print(f" - Unexpected {move_count[0]} move(s) outside bounds: FAIL")
|
print(f" - Unexpected {move_count[0]} move(s) outside bounds: FAIL")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
mcrfpy.setTimer("check_outside", check_results, 200)
|
mcrfpy.Timer("check_outside", check_results, 200, once=True)
|
||||||
|
|
||||||
|
|
||||||
def test_all_types_have_on_move():
|
def test_all_types_have_on_move():
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ for y in range(5):
|
||||||
print("\nIf colors are changing in data but not visually, it may be a rendering issue.")
|
print("\nIf colors are changing in data but not visually, it may be a rendering issue.")
|
||||||
|
|
||||||
# Quick visual test
|
# Quick visual test
|
||||||
def check_visual(runtime):
|
def check_visual(timer, runtime):
|
||||||
print("\nTimer fired - checking if scene is rendering...")
|
print("\nTimer fired - checking if scene is rendering...")
|
||||||
# Take screenshot to see actual rendering
|
# Take screenshot to see actual rendering
|
||||||
try:
|
try:
|
||||||
|
|
@ -81,6 +81,6 @@ grid.position = (50, 50)
|
||||||
grid.size = (250, 250)
|
grid.size = (250, 250)
|
||||||
|
|
||||||
test.activate()
|
test.activate()
|
||||||
mcrfpy.setTimer("check", check_visual, 500)
|
check_timer = mcrfpy.Timer("check", check_visual, 500, once=True)
|
||||||
|
|
||||||
print("\nStarting render test...")
|
print("\nStarting render test...")
|
||||||
|
|
@ -48,11 +48,11 @@ print("\n✓ Pathfinding integration working correctly!")
|
||||||
print("Enhanced demos are ready for interactive use.")
|
print("Enhanced demos are ready for interactive use.")
|
||||||
|
|
||||||
# Quick animation test
|
# Quick animation test
|
||||||
def test_timer(dt):
|
def test_timer(timer, runtime):
|
||||||
print(f"Timer callback received: dt={dt}ms")
|
print(f"Timer callback received: runtime={runtime}ms")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# Set a quick timer to test animation system
|
# Set a quick timer to test animation system
|
||||||
mcrfpy.setTimer("test", test_timer, 100)
|
timer = mcrfpy.Timer("test", test_timer, 100, once=True)
|
||||||
|
|
||||||
print("\nTesting timer system for animations...")
|
print("\nTesting timer system for animations...")
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def test_properties(runtime):
|
def test_properties(timer, runtime):
|
||||||
mcrfpy.delTimer("test_properties")
|
timer.stop()
|
||||||
|
|
||||||
print("\n=== Testing Properties ===")
|
print("\n=== Testing Properties ===")
|
||||||
|
|
||||||
|
|
@ -54,4 +54,4 @@ def test_properties(runtime):
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
test = mcrfpy.Scene("test")
|
test = mcrfpy.Scene("test")
|
||||||
mcrfpy.setTimer("test_properties", test_properties, 100)
|
test_properties_timer = mcrfpy.Timer("test_properties", test_properties, 100, once=True)
|
||||||
|
|
@ -21,7 +21,7 @@ def test(condition, message):
|
||||||
test_results.append(f"✗ {message}")
|
test_results.append(f"✗ {message}")
|
||||||
test_passed = False
|
test_passed = False
|
||||||
|
|
||||||
def run_tests(runtime):
|
def run_tests(timer, runtime):
|
||||||
"""Timer callback to run tests after game loop starts"""
|
"""Timer callback to run tests after game loop starts"""
|
||||||
global test_passed
|
global test_passed
|
||||||
|
|
||||||
|
|
@ -146,6 +146,6 @@ test_scene = mcrfpy.Scene("test_scene")
|
||||||
test_scene.activate()
|
test_scene.activate()
|
||||||
|
|
||||||
# Schedule tests to run after game loop starts
|
# Schedule tests to run after game loop starts
|
||||||
mcrfpy.setTimer("test", run_tests, 100)
|
test_timer = mcrfpy.Timer("test", run_tests, 100, once=True)
|
||||||
|
|
||||||
print("Python object cache test initialized. Running tests...")
|
print("Python object cache test initialized. Running tests...")
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ for s in (red_scene, blue_scene, green_scene, menu_scene):
|
||||||
|
|
||||||
# Option to run automatic test
|
# Option to run automatic test
|
||||||
if len(sys.argv) > 1 and sys.argv[1] == "--auto":
|
if len(sys.argv) > 1 and sys.argv[1] == "--auto":
|
||||||
mcrfpy.setTimer("auto_test", test_automatic_transitions, 1000)
|
mcrfpy.Timer("auto_test", lambda t, r: test_automatic_transitions(r), 1000, once=True)
|
||||||
else:
|
else:
|
||||||
print("\nManual test mode. Use keyboard controls shown on screen.")
|
print("\nManual test mode. Use keyboard controls shown on screen.")
|
||||||
print("Run with --auto flag for automatic transition demo.")
|
print("Run with --auto flag for automatic transition demo.")
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,4 @@ test.activate()
|
||||||
e = mcrfpy.Entity((0, 0), texture=None, sprite_index=0)
|
e = mcrfpy.Entity((0, 0), texture=None, sprite_index=0)
|
||||||
a = mcrfpy.Animation("x", 1.0, 0.1, "linear", callback=cb)
|
a = mcrfpy.Animation("x", 1.0, 0.1, "linear", callback=cb)
|
||||||
a.start(e)
|
a.start(e)
|
||||||
mcrfpy.setTimer("exit", lambda r: sys.exit(0), 200)
|
mcrfpy.Timer("exit", lambda t, r: sys.exit(0), 200, once=True)
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def simple_test(runtime):
|
def simple_test(timer, runtime):
|
||||||
mcrfpy.delTimer("simple_test")
|
timer.stop()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Test basic functionality
|
# Test basic functionality
|
||||||
|
|
@ -27,4 +27,4 @@ def simple_test(runtime):
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
test = mcrfpy.Scene("test")
|
test = mcrfpy.Scene("test")
|
||||||
mcrfpy.setTimer("simple_test", simple_test, 100)
|
simple_test_timer = mcrfpy.Timer("simple_test", simple_test, 100, once=True)
|
||||||
|
|
@ -63,13 +63,13 @@ def run_tests():
|
||||||
print("Test 5: Timer fires after step() advances past interval")
|
print("Test 5: Timer fires after step() advances past interval")
|
||||||
timer_fired = [False] # Use list for mutable closure
|
timer_fired = [False] # Use list for mutable closure
|
||||||
|
|
||||||
def on_timer(runtime):
|
def on_timer(timer, runtime):
|
||||||
"""Timer callback - receives runtime in ms"""
|
"""Timer callback - receives timer object and runtime in ms"""
|
||||||
timer_fired[0] = True
|
timer_fired[0] = True
|
||||||
print(f" Timer fired at simulation time={runtime}ms")
|
print(f" Timer fired at simulation time={runtime}ms")
|
||||||
|
|
||||||
# Set a timer for 500ms
|
# Set a timer for 500ms
|
||||||
mcrfpy.setTimer("test_timer", on_timer, 500)
|
test_timer = mcrfpy.Timer("test_timer", on_timer, 500)
|
||||||
|
|
||||||
# Step 600ms - timer should fire (500ms interval + some buffer)
|
# Step 600ms - timer should fire (500ms interval + some buffer)
|
||||||
dt = mcrfpy.step(0.6)
|
dt = mcrfpy.step(0.6)
|
||||||
|
|
@ -88,7 +88,7 @@ def run_tests():
|
||||||
print(" Skipping timer test in windowed mode")
|
print(" Skipping timer test in windowed mode")
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
mcrfpy.delTimer("test_timer")
|
test_timer.stop()
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Test 6: Error handling - invalid argument type
|
# Test 6: Error handling - invalid argument type
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ def create_demo():
|
||||||
text_demo.activate()
|
text_demo.activate()
|
||||||
|
|
||||||
# Run demo test
|
# Run demo test
|
||||||
def run_test(timer_name):
|
def run_test(timer, runtime):
|
||||||
print("\n=== Text Input Widget Test ===")
|
print("\n=== Text Input Widget Test ===")
|
||||||
print("Features:")
|
print("Features:")
|
||||||
print("- Click to focus fields")
|
print("- Click to focus fields")
|
||||||
|
|
@ -103,7 +103,7 @@ def create_demo():
|
||||||
print("- Press Escape to exit")
|
print("- Press Escape to exit")
|
||||||
print("\nTry it out!")
|
print("\nTry it out!")
|
||||||
|
|
||||||
mcrfpy.setTimer("info", run_test, 100)
|
info_timer = mcrfpy.Timer("info", run_test, 100, once=True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,27 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Test timer callback arguments
|
Test timer callback arguments with new Timer API (#173)
|
||||||
"""
|
"""
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
call_count = 0
|
call_count = 0
|
||||||
|
|
||||||
def old_style_callback(arg):
|
def new_style_callback(timer, runtime):
|
||||||
"""Old style callback - should receive just runtime"""
|
"""New style callback - receives timer object and runtime"""
|
||||||
global call_count
|
global call_count
|
||||||
call_count += 1
|
call_count += 1
|
||||||
print(f"Old style callback called with: {arg} (type: {type(arg)})")
|
print(f"Callback called with: timer={timer} (type: {type(timer)}), runtime={runtime} (type: {type(runtime)})")
|
||||||
|
if hasattr(timer, 'once'):
|
||||||
|
print(f"Got Timer object! once={timer.once}")
|
||||||
if call_count >= 2:
|
if call_count >= 2:
|
||||||
sys.exit(0)
|
print("PASS")
|
||||||
|
|
||||||
def new_style_callback(arg1, arg2=None):
|
|
||||||
"""New style callback - should receive timer object and runtime"""
|
|
||||||
print(f"New style callback called with: arg1={arg1} (type: {type(arg1)}), arg2={arg2} (type: {type(arg2) if arg2 else 'None'})")
|
|
||||||
if hasattr(arg1, 'once'):
|
|
||||||
print(f"Got Timer object! once={arg1.once}")
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# Set up the scene
|
# Set up the scene
|
||||||
test_scene = mcrfpy.Scene("test_scene")
|
test_scene = mcrfpy.Scene("test_scene")
|
||||||
test_scene.activate()
|
test_scene.activate()
|
||||||
|
|
||||||
print("Testing old style timer with setTimer...")
|
print("Testing new Timer callback signature (timer, runtime)...")
|
||||||
mcrfpy.setTimer("old_timer", old_style_callback, 100)
|
timer = mcrfpy.Timer("test_timer", new_style_callback, 100)
|
||||||
|
|
||||||
print("\nTesting new style timer with Timer object...")
|
|
||||||
timer = mcrfpy.Timer("new_timer", new_style_callback, 200)
|
|
||||||
print(f"Timer created: {timer}")
|
print(f"Timer created: {timer}")
|
||||||
|
|
@ -1,26 +1,28 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Test legacy timer API still works
|
Test Timer API works correctly (#173)
|
||||||
|
Replaces old legacy setTimer test
|
||||||
"""
|
"""
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
def timer_callback(runtime):
|
def timer_callback(timer, runtime):
|
||||||
global count
|
global count
|
||||||
count += 1
|
count += 1
|
||||||
print(f"Timer fired! Count: {count}, Runtime: {runtime}")
|
print(f"Timer fired! Count: {count}, Runtime: {runtime}")
|
||||||
|
|
||||||
if count >= 3:
|
if count >= 3:
|
||||||
print("Test passed - timer fired 3 times")
|
print("Test passed - timer fired 3 times")
|
||||||
|
print("PASS")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# Set up the scene
|
# Set up the scene
|
||||||
test_scene = mcrfpy.Scene("test_scene")
|
test_scene = mcrfpy.Scene("test_scene")
|
||||||
test_scene.activate()
|
test_scene.activate()
|
||||||
|
|
||||||
# Create a timer the old way
|
# Create a timer with new API
|
||||||
mcrfpy.setTimer("test_timer", timer_callback, 100)
|
timer = mcrfpy.Timer("test_timer", timer_callback, 100)
|
||||||
|
|
||||||
print("Legacy timer test starting...")
|
print("Timer test starting...")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Test the new mcrfpy.Timer object with pause/resume/cancel functionality
|
Test the new mcrfpy.Timer object with pause/resume/stop functionality
|
||||||
|
Updated for new Timer API (#173)
|
||||||
"""
|
"""
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -25,10 +26,10 @@ def cancel_test_callback(timer, runtime):
|
||||||
cancel_test_count += 1
|
cancel_test_count += 1
|
||||||
print(f"Cancel test timer: {cancel_test_count} - This should only print once!")
|
print(f"Cancel test timer: {cancel_test_count} - This should only print once!")
|
||||||
|
|
||||||
def run_tests(runtime):
|
def run_tests(timer, runtime):
|
||||||
"""Main test function that runs after game loop starts"""
|
"""Main test function that runs after game loop starts"""
|
||||||
# Delete the timer that called us to prevent re-running
|
# Stop the timer that called us to prevent re-running
|
||||||
mcrfpy.delTimer("run_tests")
|
timer.stop()
|
||||||
|
|
||||||
print("\n=== Testing mcrfpy.Timer object ===\n")
|
print("\n=== Testing mcrfpy.Timer object ===\n")
|
||||||
|
|
||||||
|
|
@ -45,35 +46,34 @@ def run_tests(runtime):
|
||||||
timer2 = mcrfpy.Timer("pause_test", pause_test_callback, 200)
|
timer2 = mcrfpy.Timer("pause_test", pause_test_callback, 200)
|
||||||
|
|
||||||
# Schedule pause after 250ms
|
# Schedule pause after 250ms
|
||||||
def pause_timer2(runtime):
|
def pause_timer2(t, rt):
|
||||||
print(" Pausing timer2...")
|
print(" Pausing timer2...")
|
||||||
timer2.pause()
|
timer2.pause()
|
||||||
print(f" Timer2 paused: {timer2.paused}")
|
print(f" Timer2 paused: {timer2.paused}")
|
||||||
print(f" Timer2 active: {timer2.active}")
|
print(f" Timer2 active: {timer2.active}")
|
||||||
|
|
||||||
# Schedule resume after another 400ms
|
# Schedule resume after another 400ms
|
||||||
def resume_timer2(runtime):
|
def resume_timer2(t2, rt2):
|
||||||
print(" Resuming timer2...")
|
print(" Resuming timer2...")
|
||||||
timer2.resume()
|
timer2.resume()
|
||||||
print(f" Timer2 paused: {timer2.paused}")
|
print(f" Timer2 paused: {timer2.paused}")
|
||||||
print(f" Timer2 active: {timer2.active}")
|
print(f" Timer2 active: {timer2.active}")
|
||||||
|
|
||||||
mcrfpy.setTimer("resume_timer2", resume_timer2, 400)
|
mcrfpy.Timer("resume_timer2", resume_timer2, 400, once=True)
|
||||||
|
|
||||||
mcrfpy.setTimer("pause_timer2", pause_timer2, 250)
|
mcrfpy.Timer("pause_timer2", pause_timer2, 250, once=True)
|
||||||
|
|
||||||
# Test 3: Test cancel
|
# Test 3: Test cancel/stop
|
||||||
print("\nTest 3: Testing cancel functionality")
|
print("\nTest 3: Testing stop functionality")
|
||||||
timer3 = mcrfpy.Timer("cancel_test", cancel_test_callback, 300)
|
timer3 = mcrfpy.Timer("cancel_test", cancel_test_callback, 300)
|
||||||
|
|
||||||
# Cancel after 350ms (should fire once)
|
# Cancel after 350ms (should fire once)
|
||||||
def cancel_timer3(runtime):
|
def cancel_timer3(t, rt):
|
||||||
mcrfpy.delTimer("cancel_timer3") # Make this a one-shot timer
|
print(" Stopping timer3...")
|
||||||
print(" Canceling timer3...")
|
timer3.stop()
|
||||||
timer3.cancel()
|
print(" Timer3 stopped")
|
||||||
print(" Timer3 canceled")
|
|
||||||
|
|
||||||
mcrfpy.setTimer("cancel_timer3", cancel_timer3, 350)
|
mcrfpy.Timer("cancel_timer3", cancel_timer3, 350, once=True)
|
||||||
|
|
||||||
# Test 4: Test interval modification
|
# Test 4: Test interval modification
|
||||||
print("\nTest 4: Testing interval modification")
|
print("\nTest 4: Testing interval modification")
|
||||||
|
|
@ -87,13 +87,13 @@ def run_tests(runtime):
|
||||||
|
|
||||||
# Test 5: Test remaining time
|
# Test 5: Test remaining time
|
||||||
print("\nTest 5: Testing remaining time")
|
print("\nTest 5: Testing remaining time")
|
||||||
def check_remaining(runtime):
|
def check_remaining(t, rt):
|
||||||
if timer1.active:
|
if timer1.active:
|
||||||
print(f" Timer1 remaining: {timer1.remaining}ms")
|
print(f" Timer1 remaining: {timer1.remaining}ms")
|
||||||
if timer2.active or timer2.paused:
|
if timer2.active or timer2.paused:
|
||||||
print(f" Timer2 remaining: {timer2.remaining}ms (paused: {timer2.paused})")
|
print(f" Timer2 remaining: {timer2.remaining}ms (paused: {timer2.paused})")
|
||||||
|
|
||||||
mcrfpy.setTimer("check_remaining", check_remaining, 150)
|
remaining_timer = mcrfpy.Timer("check_remaining", check_remaining, 150)
|
||||||
|
|
||||||
# Test 6: Test restart
|
# Test 6: Test restart
|
||||||
print("\nTest 6: Testing restart functionality")
|
print("\nTest 6: Testing restart functionality")
|
||||||
|
|
@ -109,7 +109,7 @@ def run_tests(runtime):
|
||||||
timer5 = mcrfpy.Timer("restart_test", restart_test, 400)
|
timer5 = mcrfpy.Timer("restart_test", restart_test, 400)
|
||||||
|
|
||||||
# Final verification after 2 seconds
|
# Final verification after 2 seconds
|
||||||
def final_check(runtime):
|
def final_check(t, rt):
|
||||||
print("\n=== Final Results ===")
|
print("\n=== Final Results ===")
|
||||||
print(f"Timer1 call count: {call_count} (expected: ~4)")
|
print(f"Timer1 call count: {call_count} (expected: ~4)")
|
||||||
print(f"Pause test count: {pause_test_count} (expected: ~6-7, with pause gap)")
|
print(f"Pause test count: {pause_test_count} (expected: ~6-7, with pause gap)")
|
||||||
|
|
@ -120,7 +120,7 @@ def run_tests(runtime):
|
||||||
try:
|
try:
|
||||||
print(f"\nTimer1 active: {timer1.active}")
|
print(f"\nTimer1 active: {timer1.active}")
|
||||||
print(f"Timer2 active: {timer2.active}")
|
print(f"Timer2 active: {timer2.active}")
|
||||||
print(f"Timer3 active: {timer3.active} (should be False after cancel)")
|
print(f"Timer3 active: {timer3.active} (should be False after stop)")
|
||||||
print(f"Timer4 active: {timer4.active}")
|
print(f"Timer4 active: {timer4.active}")
|
||||||
print(f"Timer5 active: {timer5.active}")
|
print(f"Timer5 active: {timer5.active}")
|
||||||
except:
|
except:
|
||||||
|
|
@ -129,13 +129,13 @@ def run_tests(runtime):
|
||||||
print("\n✓ All Timer object tests completed!")
|
print("\n✓ All Timer object tests completed!")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
mcrfpy.setTimer("final_check", final_check, 2000)
|
mcrfpy.Timer("final_check", final_check, 2000, once=True)
|
||||||
|
|
||||||
# Create a minimal scene
|
# Create a minimal scene
|
||||||
timer_test = mcrfpy.Scene("timer_test")
|
timer_test = mcrfpy.Scene("timer_test")
|
||||||
timer_test.activate()
|
timer_test.activate()
|
||||||
|
|
||||||
# Start tests after game loop begins
|
# Start tests after game loop begins
|
||||||
mcrfpy.setTimer("run_tests", run_tests, 100)
|
mcrfpy.Timer("run_tests", run_tests, 100, once=True)
|
||||||
|
|
||||||
print("Timer object tests starting...")
|
print("Timer object tests starting...")
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Test once=True timer functionality
|
Test once=True timer functionality
|
||||||
|
Uses mcrfpy.step() to advance time in headless mode.
|
||||||
"""
|
"""
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
|
|
@ -18,18 +19,6 @@ def repeat_callback(timer, runtime):
|
||||||
repeat_count += 1
|
repeat_count += 1
|
||||||
print(f"Repeat timer fired! Count: {repeat_count}, Timer.once: {timer.once}")
|
print(f"Repeat timer fired! Count: {repeat_count}, Timer.once: {timer.once}")
|
||||||
|
|
||||||
def check_results(runtime):
|
|
||||||
print(f"\nFinal results:")
|
|
||||||
print(f"Once timer fired {once_count} times (expected: 1)")
|
|
||||||
print(f"Repeat timer fired {repeat_count} times (expected: 3+)")
|
|
||||||
|
|
||||||
if once_count == 1 and repeat_count >= 3:
|
|
||||||
print("PASS: Once timer fired exactly once, repeat timer fired multiple times")
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
print("FAIL: Timer behavior incorrect")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Set up the scene
|
# Set up the scene
|
||||||
test_scene = mcrfpy.Scene("test_scene")
|
test_scene = mcrfpy.Scene("test_scene")
|
||||||
test_scene.activate()
|
test_scene.activate()
|
||||||
|
|
@ -43,5 +32,20 @@ print("\nCreating repeat timer with once=False (default)...")
|
||||||
repeat_timer = mcrfpy.Timer("repeat_timer", repeat_callback, 100)
|
repeat_timer = mcrfpy.Timer("repeat_timer", repeat_callback, 100)
|
||||||
print(f"Timer: {repeat_timer}, once={repeat_timer.once}")
|
print(f"Timer: {repeat_timer}, once={repeat_timer.once}")
|
||||||
|
|
||||||
# Check results after 500ms
|
# Advance time using step() to let timers fire
|
||||||
mcrfpy.setTimer("check", check_results, 500)
|
# Step 600ms total - once timer (100ms) fires once, repeat timer fires ~6 times
|
||||||
|
print("\nAdvancing time with step()...")
|
||||||
|
for i in range(6):
|
||||||
|
mcrfpy.step(0.1) # 100ms each
|
||||||
|
|
||||||
|
# Check results
|
||||||
|
print(f"\nFinal results:")
|
||||||
|
print(f"Once timer fired {once_count} times (expected: 1)")
|
||||||
|
print(f"Repeat timer fired {repeat_count} times (expected: 3+)")
|
||||||
|
|
||||||
|
if once_count == 1 and repeat_count >= 3:
|
||||||
|
print("PASS: Once timer fired exactly once, repeat timer fired multiple times")
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print("FAIL: Timer behavior incorrect")
|
||||||
|
sys.exit(1)
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,18 @@ import mcrfpy
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def take_screenshot(runtime):
|
def take_screenshot(timer, runtime):
|
||||||
"""Take screenshot after render completes"""
|
"""Take screenshot after render completes"""
|
||||||
mcrfpy.delTimer("screenshot")
|
timer.stop()
|
||||||
automation.screenshot("test_uiarc_result.png")
|
automation.screenshot("test_uiarc_result.png")
|
||||||
|
|
||||||
print("Screenshot saved to test_uiarc_result.png")
|
print("Screenshot saved to test_uiarc_result.png")
|
||||||
print("PASS - UIArc test completed")
|
print("PASS - UIArc test completed")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
"""Main test - runs after scene is set up"""
|
"""Main test - runs after scene is set up"""
|
||||||
mcrfpy.delTimer("test")
|
timer.stop()
|
||||||
|
|
||||||
# Get the scene UI
|
# Get the scene UI
|
||||||
ui = test.children
|
ui = test.children
|
||||||
|
|
@ -127,11 +127,12 @@ def run_test(runtime):
|
||||||
print(f" Arc 10 (reverse): {a10}")
|
print(f" Arc 10 (reverse): {a10}")
|
||||||
|
|
||||||
# Schedule screenshot for next frame
|
# Schedule screenshot for next frame
|
||||||
mcrfpy.setTimer("screenshot", take_screenshot, 50)
|
global screenshot_timer
|
||||||
|
screenshot_timer = mcrfpy.Timer("screenshot", take_screenshot, 50, once=True)
|
||||||
|
|
||||||
# Create a test scene
|
# Create a test scene
|
||||||
test = mcrfpy.Scene("test")
|
test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
# Schedule test to run after game loop starts
|
# Schedule test to run after game loop starts
|
||||||
mcrfpy.setTimer("test", run_test, 50)
|
test_timer = mcrfpy.Timer("test", run_test, 50, once=True)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from mcrfpy import automation
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
def run_visual_test(runtime):
|
def run_visual_test(timer, runtime):
|
||||||
"""Timer callback to run visual tests and take screenshots."""
|
"""Timer callback to run visual tests and take screenshots."""
|
||||||
print("\nRunning visual tests...")
|
print("\nRunning visual tests...")
|
||||||
|
|
||||||
|
|
@ -91,7 +91,7 @@ def main():
|
||||||
print("Scene setup complete. Scheduling visual tests...")
|
print("Scene setup complete. Scheduling visual tests...")
|
||||||
|
|
||||||
# Schedule visual test to run after render loop starts
|
# Schedule visual test to run after render loop starts
|
||||||
mcrfpy.setTimer("visual_test", run_visual_test, 100)
|
visual_test_timer = mcrfpy.Timer("visual_test", run_visual_test, 100, once=True)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
@ -4,18 +4,18 @@ import mcrfpy
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def take_screenshot(runtime):
|
def take_screenshot(timer, runtime):
|
||||||
"""Take screenshot after render completes"""
|
"""Take screenshot after render completes"""
|
||||||
mcrfpy.delTimer("screenshot")
|
timer.stop()
|
||||||
automation.screenshot("test_uicircle_result.png")
|
automation.screenshot("test_uicircle_result.png")
|
||||||
|
|
||||||
print("Screenshot saved to test_uicircle_result.png")
|
print("Screenshot saved to test_uicircle_result.png")
|
||||||
print("PASS - UICircle test completed")
|
print("PASS - UICircle test completed")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
def run_test(runtime):
|
def run_test(timer, runtime):
|
||||||
"""Main test - runs after scene is set up"""
|
"""Main test - runs after scene is set up"""
|
||||||
mcrfpy.delTimer("test")
|
timer.stop()
|
||||||
|
|
||||||
# Get the scene UI
|
# Get the scene UI
|
||||||
ui = test.children
|
ui = test.children
|
||||||
|
|
@ -118,11 +118,12 @@ def run_test(runtime):
|
||||||
print(f" c1 moved from {old_center} to {new_center}")
|
print(f" c1 moved from {old_center} to {new_center}")
|
||||||
|
|
||||||
# Schedule screenshot for next frame
|
# Schedule screenshot for next frame
|
||||||
mcrfpy.setTimer("screenshot", take_screenshot, 50)
|
global screenshot_timer
|
||||||
|
screenshot_timer = mcrfpy.Timer("screenshot", take_screenshot, 50, once=True)
|
||||||
|
|
||||||
# Create a test scene
|
# Create a test scene
|
||||||
test = mcrfpy.Scene("test")
|
test = mcrfpy.Scene("test")
|
||||||
test.activate()
|
test.activate()
|
||||||
|
|
||||||
# Schedule test to run after game loop starts
|
# Schedule test to run after game loop starts
|
||||||
mcrfpy.setTimer("test", run_test, 50)
|
test_timer = mcrfpy.Timer("test", run_test, 50, once=True)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ Test UTF-8 encoding support
|
||||||
import mcrfpy
|
import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def test_utf8(runtime):
|
def test_utf8(timer, runtime):
|
||||||
"""Test UTF-8 encoding in print statements"""
|
"""Test UTF-8 encoding in print statements"""
|
||||||
|
|
||||||
# Test various unicode characters
|
# Test various unicode characters
|
||||||
|
|
@ -32,4 +32,4 @@ def test_utf8(runtime):
|
||||||
|
|
||||||
# Run test
|
# Run test
|
||||||
test = mcrfpy.Scene("test")
|
test = mcrfpy.Scene("test")
|
||||||
mcrfpy.setTimer("test", test_utf8, 100)
|
test_timer = mcrfpy.Timer("test", test_utf8, 100, once=True)
|
||||||
|
|
@ -7,7 +7,7 @@ import mcrfpy
|
||||||
import sys
|
import sys
|
||||||
import math
|
import math
|
||||||
|
|
||||||
def test_vector_arithmetic(runtime):
|
def test_vector_arithmetic(timer, runtime):
|
||||||
"""Test vector arithmetic operations"""
|
"""Test vector arithmetic operations"""
|
||||||
|
|
||||||
all_pass = True
|
all_pass = True
|
||||||
|
|
@ -244,4 +244,4 @@ def test_vector_arithmetic(runtime):
|
||||||
|
|
||||||
# Run test
|
# Run test
|
||||||
test = mcrfpy.Scene("test")
|
test = mcrfpy.Scene("test")
|
||||||
mcrfpy.setTimer("test", test_vector_arithmetic, 100)
|
test_timer = mcrfpy.Timer("test", test_vector_arithmetic, 100, once=True)
|
||||||
|
|
@ -5,9 +5,9 @@ import mcrfpy
|
||||||
from mcrfpy import Window, Frame, Caption, Color, Vector
|
from mcrfpy import Window, Frame, Caption, Color, Vector
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def test_viewport_modes(runtime):
|
def test_viewport_modes(timer, runtime):
|
||||||
"""Test all three viewport scaling modes"""
|
"""Test all three viewport scaling modes"""
|
||||||
mcrfpy.delTimer("test_viewport")
|
timer.stop()
|
||||||
|
|
||||||
print("Testing viewport scaling modes...")
|
print("Testing viewport scaling modes...")
|
||||||
|
|
||||||
|
|
@ -82,8 +82,8 @@ def test_viewport_modes(runtime):
|
||||||
scene.append(instructions)
|
scene.append(instructions)
|
||||||
|
|
||||||
# Test changing modes
|
# Test changing modes
|
||||||
def test_mode_changes(runtime):
|
def test_mode_changes(t, r):
|
||||||
mcrfpy.delTimer("test_modes")
|
t.stop()
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
|
|
||||||
print("\nTesting scaling modes:")
|
print("\nTesting scaling modes:")
|
||||||
|
|
@ -95,10 +95,10 @@ def test_viewport_modes(runtime):
|
||||||
automation.screenshot("viewport_center_mode.png")
|
automation.screenshot("viewport_center_mode.png")
|
||||||
|
|
||||||
# Schedule next mode test
|
# Schedule next mode test
|
||||||
mcrfpy.setTimer("test_stretch", test_stretch_mode, 1000)
|
mcrfpy.Timer("test_stretch", test_stretch_mode, 1000, once=True)
|
||||||
|
|
||||||
def test_stretch_mode(runtime):
|
def test_stretch_mode(t, r):
|
||||||
mcrfpy.delTimer("test_stretch")
|
t.stop()
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
|
|
||||||
window.scaling_mode = "stretch"
|
window.scaling_mode = "stretch"
|
||||||
|
|
@ -107,10 +107,10 @@ def test_viewport_modes(runtime):
|
||||||
automation.screenshot("viewport_stretch_mode.png")
|
automation.screenshot("viewport_stretch_mode.png")
|
||||||
|
|
||||||
# Schedule next mode test
|
# Schedule next mode test
|
||||||
mcrfpy.setTimer("test_fit", test_fit_mode, 1000)
|
mcrfpy.Timer("test_fit", test_fit_mode, 1000, once=True)
|
||||||
|
|
||||||
def test_fit_mode(runtime):
|
def test_fit_mode(t, r):
|
||||||
mcrfpy.delTimer("test_fit")
|
t.stop()
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
|
|
||||||
window.scaling_mode = "fit"
|
window.scaling_mode = "fit"
|
||||||
|
|
@ -119,10 +119,10 @@ def test_viewport_modes(runtime):
|
||||||
automation.screenshot("viewport_fit_mode.png")
|
automation.screenshot("viewport_fit_mode.png")
|
||||||
|
|
||||||
# Test different window sizes
|
# Test different window sizes
|
||||||
mcrfpy.setTimer("test_resize", test_window_resize, 1000)
|
mcrfpy.Timer("test_resize", test_window_resize, 1000, once=True)
|
||||||
|
|
||||||
def test_window_resize(runtime):
|
def test_window_resize(t, r):
|
||||||
mcrfpy.delTimer("test_resize")
|
t.stop()
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
|
|
||||||
print("\nTesting window resize with fit mode:")
|
print("\nTesting window resize with fit mode:")
|
||||||
|
|
@ -133,13 +133,13 @@ def test_viewport_modes(runtime):
|
||||||
print(f"Window resized to: {window.resolution}")
|
print(f"Window resized to: {window.resolution}")
|
||||||
automation.screenshot("viewport_fit_wide.png")
|
automation.screenshot("viewport_fit_wide.png")
|
||||||
# Make window taller
|
# Make window taller
|
||||||
mcrfpy.setTimer("test_tall", test_tall_window, 1000)
|
mcrfpy.Timer("test_tall", test_tall_window, 1000, once=True)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
print(f" Skipping window resize tests (headless mode): {e}")
|
print(f" Skipping window resize tests (headless mode): {e}")
|
||||||
mcrfpy.setTimer("test_game_res", test_game_resolution, 100)
|
mcrfpy.Timer("test_game_res", test_game_resolution, 100, once=True)
|
||||||
|
|
||||||
def test_tall_window(runtime):
|
def test_tall_window(t, r):
|
||||||
mcrfpy.delTimer("test_tall")
|
t.stop()
|
||||||
from mcrfpy import automation
|
from mcrfpy import automation
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -150,10 +150,10 @@ def test_viewport_modes(runtime):
|
||||||
print(f" Skipping tall window test (headless mode): {e}")
|
print(f" Skipping tall window test (headless mode): {e}")
|
||||||
|
|
||||||
# Test game resolution change
|
# Test game resolution change
|
||||||
mcrfpy.setTimer("test_game_res", test_game_resolution, 1000)
|
mcrfpy.Timer("test_game_res", test_game_resolution, 1000, once=True)
|
||||||
|
|
||||||
def test_game_resolution(runtime):
|
def test_game_resolution(t, r):
|
||||||
mcrfpy.delTimer("test_game_res")
|
t.stop()
|
||||||
|
|
||||||
print("\nTesting game resolution change:")
|
print("\nTesting game resolution change:")
|
||||||
window.game_resolution = (800, 600)
|
window.game_resolution = (800, 600)
|
||||||
|
|
@ -180,7 +180,7 @@ def test_viewport_modes(runtime):
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# Start test sequence
|
# Start test sequence
|
||||||
mcrfpy.setTimer("test_modes", test_mode_changes, 500)
|
mcrfpy.Timer("test_modes", test_mode_changes, 500, once=True)
|
||||||
|
|
||||||
# Set up keyboard handler for manual testing
|
# Set up keyboard handler for manual testing
|
||||||
def handle_keypress(key, state):
|
def handle_keypress(key, state):
|
||||||
|
|
@ -240,7 +240,7 @@ test.activate()
|
||||||
test.on_key = handle_keypress
|
test.on_key = handle_keypress
|
||||||
|
|
||||||
# Schedule the test
|
# Schedule the test
|
||||||
mcrfpy.setTimer("test_viewport", test_viewport_modes, 100)
|
test_viewport_timer = mcrfpy.Timer("test_viewport", test_viewport_modes, 100, once=True)
|
||||||
|
|
||||||
print("Viewport test running...")
|
print("Viewport test running...")
|
||||||
print("Use number keys to switch modes, R to resize window, G to change game resolution")
|
print("Use number keys to switch modes, R to resize window, G to change game resolution")
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue