Fix animation segfaults with RAII weak_ptr implementation
Resolved two critical segmentation faults in AnimationManager: 1. Race condition when creating multiple animations in timer callbacks 2. Exit crash when animations outlive their target objects Changes: - Replace raw pointers with std::weak_ptr for automatic target invalidation - Add Animation::complete() to jump animations to final value - Add Animation::hasValidTarget() to check if target still exists - Update AnimationManager to auto-remove invalid animations - Add AnimationManager::clear() call to GameEngine::cleanup() - Update Python bindings to pass shared_ptr instead of raw pointers This ensures animations can never reference destroyed objects, following proper RAII principles. Tested with sizzle_reel_final.py and stress tests creating/destroying hundreds of animated objects. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
98fc49a978
commit
062e4dadc4
|
@ -23,60 +23,60 @@ Animation::Animation(const std::string& targetProperty,
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::start(UIDrawable* target) {
|
void Animation::start(std::shared_ptr<UIDrawable> target) {
|
||||||
currentTarget = target;
|
if (!target) return;
|
||||||
|
|
||||||
|
targetWeak = target;
|
||||||
elapsed = 0.0f;
|
elapsed = 0.0f;
|
||||||
|
|
||||||
// Capture startValue from target based on targetProperty
|
// Capture start value from target
|
||||||
if (!currentTarget) return;
|
std::visit([this, &target](const auto& targetVal) {
|
||||||
|
|
||||||
// Try to get the current value based on the expected type
|
|
||||||
std::visit([this](const auto& targetVal) {
|
|
||||||
using T = std::decay_t<decltype(targetVal)>;
|
using T = std::decay_t<decltype(targetVal)>;
|
||||||
|
|
||||||
if constexpr (std::is_same_v<T, float>) {
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
float value;
|
float value;
|
||||||
if (currentTarget->getProperty(targetProperty, value)) {
|
if (target->getProperty(targetProperty, value)) {
|
||||||
startValue = value;
|
startValue = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, int>) {
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
int value;
|
int value;
|
||||||
if (currentTarget->getProperty(targetProperty, value)) {
|
if (target->getProperty(targetProperty, value)) {
|
||||||
startValue = value;
|
startValue = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
else if constexpr (std::is_same_v<T, std::vector<int>>) {
|
||||||
// For sprite animation, get current sprite index
|
// For sprite animation, get current sprite index
|
||||||
int value;
|
int value;
|
||||||
if (currentTarget->getProperty(targetProperty, value)) {
|
if (target->getProperty(targetProperty, value)) {
|
||||||
startValue = value;
|
startValue = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, sf::Color>) {
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
||||||
sf::Color value;
|
sf::Color value;
|
||||||
if (currentTarget->getProperty(targetProperty, value)) {
|
if (target->getProperty(targetProperty, value)) {
|
||||||
startValue = value;
|
startValue = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
||||||
sf::Vector2f value;
|
sf::Vector2f value;
|
||||||
if (currentTarget->getProperty(targetProperty, value)) {
|
if (target->getProperty(targetProperty, value)) {
|
||||||
startValue = value;
|
startValue = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, std::string>) {
|
else if constexpr (std::is_same_v<T, std::string>) {
|
||||||
std::string value;
|
std::string value;
|
||||||
if (currentTarget->getProperty(targetProperty, value)) {
|
if (target->getProperty(targetProperty, value)) {
|
||||||
startValue = value;
|
startValue = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, targetValue);
|
}, targetValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::startEntity(UIEntity* target) {
|
void Animation::startEntity(std::shared_ptr<UIEntity> target) {
|
||||||
currentEntityTarget = target;
|
if (!target) return;
|
||||||
currentTarget = nullptr; // Clear drawable target
|
|
||||||
|
entityTargetWeak = target;
|
||||||
elapsed = 0.0f;
|
elapsed = 0.0f;
|
||||||
|
|
||||||
// Capture the starting value from the entity
|
// Capture the starting value from the entity
|
||||||
|
@ -99,8 +99,36 @@ void Animation::startEntity(UIEntity* target) {
|
||||||
}, targetValue);
|
}, targetValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Animation::hasValidTarget() const {
|
||||||
|
return !targetWeak.expired() || !entityTargetWeak.expired();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::complete() {
|
||||||
|
// Jump to end of animation
|
||||||
|
elapsed = duration;
|
||||||
|
|
||||||
|
// Apply final value
|
||||||
|
if (auto target = targetWeak.lock()) {
|
||||||
|
AnimationValue finalValue = interpolate(1.0f);
|
||||||
|
applyValue(target.get(), finalValue);
|
||||||
|
}
|
||||||
|
else if (auto entity = entityTargetWeak.lock()) {
|
||||||
|
AnimationValue finalValue = interpolate(1.0f);
|
||||||
|
applyValue(entity.get(), finalValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool Animation::update(float deltaTime) {
|
bool Animation::update(float deltaTime) {
|
||||||
if ((!currentTarget && !currentEntityTarget) || isComplete()) {
|
// Try to lock weak_ptr to get shared_ptr
|
||||||
|
std::shared_ptr<UIDrawable> target = targetWeak.lock();
|
||||||
|
std::shared_ptr<UIEntity> entity = entityTargetWeak.lock();
|
||||||
|
|
||||||
|
// If both are null, target was destroyed
|
||||||
|
if (!target && !entity) {
|
||||||
|
return false; // Remove this animation
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isComplete()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,39 +142,12 @@ bool Animation::update(float deltaTime) {
|
||||||
// Get interpolated value
|
// Get interpolated value
|
||||||
AnimationValue currentValue = interpolate(easedT);
|
AnimationValue currentValue = interpolate(easedT);
|
||||||
|
|
||||||
// Apply currentValue to target (either drawable or entity)
|
// Apply to whichever target is valid
|
||||||
std::visit([this](const auto& value) {
|
if (target) {
|
||||||
using T = std::decay_t<decltype(value)>;
|
applyValue(target.get(), currentValue);
|
||||||
|
} else if (entity) {
|
||||||
if (currentTarget) {
|
applyValue(entity.get(), currentValue);
|
||||||
// Handle UIDrawable targets
|
}
|
||||||
if constexpr (std::is_same_v<T, float>) {
|
|
||||||
currentTarget->setProperty(targetProperty, value);
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, int>) {
|
|
||||||
currentTarget->setProperty(targetProperty, value);
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, sf::Color>) {
|
|
||||||
currentTarget->setProperty(targetProperty, value);
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
|
||||||
currentTarget->setProperty(targetProperty, value);
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, std::string>) {
|
|
||||||
currentTarget->setProperty(targetProperty, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (currentEntityTarget) {
|
|
||||||
// Handle UIEntity targets
|
|
||||||
if constexpr (std::is_same_v<T, float>) {
|
|
||||||
currentEntityTarget->setProperty(targetProperty, value);
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<T, int>) {
|
|
||||||
currentEntityTarget->setProperty(targetProperty, value);
|
|
||||||
}
|
|
||||||
// Entities don't support other types yet
|
|
||||||
}
|
|
||||||
}, currentValue);
|
|
||||||
|
|
||||||
return !isComplete();
|
return !isComplete();
|
||||||
}
|
}
|
||||||
|
@ -254,6 +255,46 @@ AnimationValue Animation::interpolate(float t) const {
|
||||||
}, targetValue);
|
}, targetValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Animation::applyValue(UIDrawable* target, const AnimationValue& value) {
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
std::visit([this, target](const auto& val) {
|
||||||
|
using T = std::decay_t<decltype(val)>;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
target->setProperty(targetProperty, val);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
target->setProperty(targetProperty, val);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Color>) {
|
||||||
|
target->setProperty(targetProperty, val);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
|
||||||
|
target->setProperty(targetProperty, val);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, std::string>) {
|
||||||
|
target->setProperty(targetProperty, val);
|
||||||
|
}
|
||||||
|
}, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::applyValue(UIEntity* entity, const AnimationValue& value) {
|
||||||
|
if (!entity) return;
|
||||||
|
|
||||||
|
std::visit([this, entity](const auto& val) {
|
||||||
|
using T = std::decay_t<decltype(val)>;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<T, float>) {
|
||||||
|
entity->setProperty(targetProperty, val);
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<T, int>) {
|
||||||
|
entity->setProperty(targetProperty, val);
|
||||||
|
}
|
||||||
|
// Entities don't support other types yet
|
||||||
|
}, value);
|
||||||
|
}
|
||||||
|
|
||||||
// Easing functions implementation
|
// Easing functions implementation
|
||||||
namespace EasingFunctions {
|
namespace EasingFunctions {
|
||||||
|
|
||||||
|
@ -502,26 +543,31 @@ AnimationManager& AnimationManager::getInstance() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimationManager::addAnimation(std::shared_ptr<Animation> animation) {
|
void AnimationManager::addAnimation(std::shared_ptr<Animation> animation) {
|
||||||
activeAnimations.push_back(animation);
|
if (animation && animation->hasValidTarget()) {
|
||||||
|
activeAnimations.push_back(animation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimationManager::update(float deltaTime) {
|
void AnimationManager::update(float deltaTime) {
|
||||||
for (auto& anim : activeAnimations) {
|
// Remove completed or invalid animations
|
||||||
anim->update(deltaTime);
|
|
||||||
}
|
|
||||||
cleanup();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AnimationManager::cleanup() {
|
|
||||||
activeAnimations.erase(
|
activeAnimations.erase(
|
||||||
std::remove_if(activeAnimations.begin(), activeAnimations.end(),
|
std::remove_if(activeAnimations.begin(), activeAnimations.end(),
|
||||||
[](const std::shared_ptr<Animation>& anim) {
|
[deltaTime](std::shared_ptr<Animation>& anim) {
|
||||||
return anim->isComplete();
|
return !anim || !anim->update(deltaTime);
|
||||||
}),
|
}),
|
||||||
activeAnimations.end()
|
activeAnimations.end()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimationManager::clear() {
|
|
||||||
|
void AnimationManager::clear(bool completeAnimations) {
|
||||||
|
if (completeAnimations) {
|
||||||
|
// Complete all animations before clearing
|
||||||
|
for (auto& anim : activeAnimations) {
|
||||||
|
if (anim) {
|
||||||
|
anim->complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
activeAnimations.clear();
|
activeAnimations.clear();
|
||||||
}
|
}
|
|
@ -39,10 +39,13 @@ public:
|
||||||
bool delta = false);
|
bool delta = false);
|
||||||
|
|
||||||
// Apply this animation to a drawable
|
// Apply this animation to a drawable
|
||||||
void start(UIDrawable* target);
|
void start(std::shared_ptr<UIDrawable> target);
|
||||||
|
|
||||||
// Apply this animation to an entity (special case since Entity doesn't inherit from UIDrawable)
|
// Apply this animation to an entity (special case since Entity doesn't inherit from UIDrawable)
|
||||||
void startEntity(UIEntity* target);
|
void startEntity(std::shared_ptr<UIEntity> target);
|
||||||
|
|
||||||
|
// Complete the animation immediately (jump to final value)
|
||||||
|
void complete();
|
||||||
|
|
||||||
// Update animation (called each frame)
|
// Update animation (called each frame)
|
||||||
// Returns true if animation is still running, false if complete
|
// Returns true if animation is still running, false if complete
|
||||||
|
@ -51,6 +54,9 @@ public:
|
||||||
// Get current interpolated value
|
// Get current interpolated value
|
||||||
AnimationValue getCurrentValue() const;
|
AnimationValue getCurrentValue() const;
|
||||||
|
|
||||||
|
// Check if animation has valid target
|
||||||
|
bool hasValidTarget() const;
|
||||||
|
|
||||||
// Animation properties
|
// Animation properties
|
||||||
std::string getTargetProperty() const { return targetProperty; }
|
std::string getTargetProperty() const { return targetProperty; }
|
||||||
float getDuration() const { return duration; }
|
float getDuration() const { return duration; }
|
||||||
|
@ -67,11 +73,16 @@ private:
|
||||||
EasingFunction easingFunc; // Easing function to use
|
EasingFunction easingFunc; // Easing function to use
|
||||||
bool delta; // If true, targetValue is relative to start
|
bool delta; // If true, targetValue is relative to start
|
||||||
|
|
||||||
UIDrawable* currentTarget = nullptr; // Current target being animated
|
// RAII: Use weak_ptr for safe target tracking
|
||||||
UIEntity* currentEntityTarget = nullptr; // Current entity target (alternative to drawable)
|
std::weak_ptr<UIDrawable> targetWeak;
|
||||||
|
std::weak_ptr<UIEntity> entityTargetWeak;
|
||||||
|
|
||||||
// Helper to interpolate between values
|
// Helper to interpolate between values
|
||||||
AnimationValue interpolate(float t) const;
|
AnimationValue interpolate(float t) const;
|
||||||
|
|
||||||
|
// Helper to apply value to target
|
||||||
|
void applyValue(UIDrawable* target, const AnimationValue& value);
|
||||||
|
void applyValue(UIEntity* entity, const AnimationValue& value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Easing functions library
|
// Easing functions library
|
||||||
|
@ -134,11 +145,8 @@ public:
|
||||||
// Update all animations
|
// Update all animations
|
||||||
void update(float deltaTime);
|
void update(float deltaTime);
|
||||||
|
|
||||||
// Remove completed animations
|
// Clear all animations (optionally completing them first)
|
||||||
void cleanup();
|
void clear(bool completeAnimations = false);
|
||||||
|
|
||||||
// Clear all animations
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AnimationManager() = default;
|
AnimationManager() = default;
|
||||||
|
|
|
@ -16,7 +16,7 @@ GameEngine::GameEngine(const McRogueFaceConfig& cfg)
|
||||||
{
|
{
|
||||||
Resources::font.loadFromFile("./assets/JetbrainsMono.ttf");
|
Resources::font.loadFromFile("./assets/JetbrainsMono.ttf");
|
||||||
Resources::game = this;
|
Resources::game = this;
|
||||||
window_title = "Crypt of Sokoban - 7DRL 2025, McRogueface Engine";
|
window_title = "McRogueFace Engine";
|
||||||
|
|
||||||
// Initialize rendering based on headless mode
|
// Initialize rendering based on headless mode
|
||||||
if (headless) {
|
if (headless) {
|
||||||
|
@ -91,6 +91,9 @@ void GameEngine::cleanup()
|
||||||
if (cleaned_up) return;
|
if (cleaned_up) return;
|
||||||
cleaned_up = true;
|
cleaned_up = true;
|
||||||
|
|
||||||
|
// Clear all animations first (RAII handles invalidation)
|
||||||
|
AnimationManager::getInstance().clear();
|
||||||
|
|
||||||
// Clear Python references before destroying C++ objects
|
// Clear Python references before destroying C++ objects
|
||||||
// Clear all timers (they hold Python callables)
|
// Clear all timers (they hold Python callables)
|
||||||
timers.clear();
|
timers.clear();
|
||||||
|
@ -182,7 +185,7 @@ void GameEngine::setWindowScale(float multiplier)
|
||||||
|
|
||||||
void GameEngine::run()
|
void GameEngine::run()
|
||||||
{
|
{
|
||||||
std::cout << "GameEngine::run() starting main loop..." << std::endl;
|
//std::cout << "GameEngine::run() starting main loop..." << std::endl;
|
||||||
float fps = 0.0;
|
float fps = 0.0;
|
||||||
frameTime = 0.016f; // Initialize to ~60 FPS
|
frameTime = 0.016f; // Initialize to ~60 FPS
|
||||||
clock.restart();
|
clock.restart();
|
||||||
|
@ -259,7 +262,7 @@ void GameEngine::run()
|
||||||
int tenth_fps = (metrics.fps * 10) % 10;
|
int tenth_fps = (metrics.fps * 10) % 10;
|
||||||
|
|
||||||
if (!headless && window) {
|
if (!headless && window) {
|
||||||
window->setTitle(window_title + " " + std::to_string(whole_fps) + "." + std::to_string(tenth_fps) + " FPS");
|
window->setTitle(window_title);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In windowed mode, check if window was closed
|
// In windowed mode, check if window was closed
|
||||||
|
|
|
@ -126,50 +126,50 @@ PyObject* PyAnimation::start(PyAnimationObject* self, PyObject* args) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the UIDrawable from the Python object
|
|
||||||
UIDrawable* drawable = nullptr;
|
|
||||||
|
|
||||||
// Check type by comparing type names
|
// Check type by comparing type names
|
||||||
const char* type_name = Py_TYPE(target_obj)->tp_name;
|
const char* type_name = Py_TYPE(target_obj)->tp_name;
|
||||||
|
|
||||||
if (strcmp(type_name, "mcrfpy.Frame") == 0) {
|
if (strcmp(type_name, "mcrfpy.Frame") == 0) {
|
||||||
PyUIFrameObject* frame = (PyUIFrameObject*)target_obj;
|
PyUIFrameObject* frame = (PyUIFrameObject*)target_obj;
|
||||||
drawable = frame->data.get();
|
if (frame->data) {
|
||||||
|
self->data->start(frame->data);
|
||||||
|
AnimationManager::getInstance().addAnimation(self->data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (strcmp(type_name, "mcrfpy.Caption") == 0) {
|
else if (strcmp(type_name, "mcrfpy.Caption") == 0) {
|
||||||
PyUICaptionObject* caption = (PyUICaptionObject*)target_obj;
|
PyUICaptionObject* caption = (PyUICaptionObject*)target_obj;
|
||||||
drawable = caption->data.get();
|
if (caption->data) {
|
||||||
|
self->data->start(caption->data);
|
||||||
|
AnimationManager::getInstance().addAnimation(self->data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (strcmp(type_name, "mcrfpy.Sprite") == 0) {
|
else if (strcmp(type_name, "mcrfpy.Sprite") == 0) {
|
||||||
PyUISpriteObject* sprite = (PyUISpriteObject*)target_obj;
|
PyUISpriteObject* sprite = (PyUISpriteObject*)target_obj;
|
||||||
drawable = sprite->data.get();
|
if (sprite->data) {
|
||||||
|
self->data->start(sprite->data);
|
||||||
|
AnimationManager::getInstance().addAnimation(self->data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (strcmp(type_name, "mcrfpy.Grid") == 0) {
|
else if (strcmp(type_name, "mcrfpy.Grid") == 0) {
|
||||||
PyUIGridObject* grid = (PyUIGridObject*)target_obj;
|
PyUIGridObject* grid = (PyUIGridObject*)target_obj;
|
||||||
drawable = grid->data.get();
|
if (grid->data) {
|
||||||
|
self->data->start(grid->data);
|
||||||
|
AnimationManager::getInstance().addAnimation(self->data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (strcmp(type_name, "mcrfpy.Entity") == 0) {
|
else if (strcmp(type_name, "mcrfpy.Entity") == 0) {
|
||||||
// Special handling for Entity since it doesn't inherit from UIDrawable
|
// Special handling for Entity since it doesn't inherit from UIDrawable
|
||||||
PyUIEntityObject* entity = (PyUIEntityObject*)target_obj;
|
PyUIEntityObject* entity = (PyUIEntityObject*)target_obj;
|
||||||
// Start the animation directly on the entity
|
if (entity->data) {
|
||||||
self->data->startEntity(entity->data.get());
|
self->data->startEntity(entity->data);
|
||||||
|
AnimationManager::getInstance().addAnimation(self->data);
|
||||||
// Add to AnimationManager
|
}
|
||||||
AnimationManager::getInstance().addAnimation(self->data);
|
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
PyErr_SetString(PyExc_TypeError, "Target must be a Frame, Caption, Sprite, Grid, or Entity");
|
PyErr_SetString(PyExc_TypeError, "Target must be a Frame, Caption, Sprite, Grid, or Entity");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the animation
|
|
||||||
self->data->start(drawable);
|
|
||||||
|
|
||||||
// Add to AnimationManager
|
|
||||||
AnimationManager::getInstance().addAnimation(self->data);
|
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,6 +214,20 @@ PyObject* PyAnimation::get_current_value(PyAnimationObject* self, PyObject* args
|
||||||
}, value);
|
}, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::complete(PyAnimationObject* self, PyObject* args) {
|
||||||
|
if (self->data) {
|
||||||
|
self->data->complete();
|
||||||
|
}
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* PyAnimation::has_valid_target(PyAnimationObject* self, PyObject* args) {
|
||||||
|
if (self->data && self->data->hasValidTarget()) {
|
||||||
|
Py_RETURN_TRUE;
|
||||||
|
}
|
||||||
|
Py_RETURN_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
PyGetSetDef PyAnimation::getsetters[] = {
|
PyGetSetDef PyAnimation::getsetters[] = {
|
||||||
{"property", (getter)get_property, NULL, "Target property name", NULL},
|
{"property", (getter)get_property, NULL, "Target property name", NULL},
|
||||||
{"duration", (getter)get_duration, NULL, "Animation duration in seconds", NULL},
|
{"duration", (getter)get_duration, NULL, "Animation duration in seconds", NULL},
|
||||||
|
@ -225,10 +239,23 @@ PyGetSetDef PyAnimation::getsetters[] = {
|
||||||
|
|
||||||
PyMethodDef PyAnimation::methods[] = {
|
PyMethodDef PyAnimation::methods[] = {
|
||||||
{"start", (PyCFunction)start, METH_VARARGS,
|
{"start", (PyCFunction)start, METH_VARARGS,
|
||||||
"Start the animation on a target UIDrawable"},
|
"start(target) -> None\n\n"
|
||||||
|
"Start the animation on a target UI element.\n\n"
|
||||||
|
"Args:\n"
|
||||||
|
" target: The UI element to animate (Frame, Caption, Sprite, Grid, or Entity)\n\n"
|
||||||
|
"Note:\n"
|
||||||
|
" The animation will automatically stop if the target is destroyed."},
|
||||||
{"update", (PyCFunction)update, METH_VARARGS,
|
{"update", (PyCFunction)update, METH_VARARGS,
|
||||||
"Update the animation by deltaTime (returns True if still running)"},
|
"Update the animation by deltaTime (returns True if still running)"},
|
||||||
{"get_current_value", (PyCFunction)get_current_value, METH_NOARGS,
|
{"get_current_value", (PyCFunction)get_current_value, METH_NOARGS,
|
||||||
"Get the current interpolated value"},
|
"Get the current interpolated value"},
|
||||||
|
{"complete", (PyCFunction)complete, METH_NOARGS,
|
||||||
|
"complete() -> None\n\n"
|
||||||
|
"Complete the animation immediately by jumping to the final value."},
|
||||||
|
{"hasValidTarget", (PyCFunction)has_valid_target, METH_NOARGS,
|
||||||
|
"hasValidTarget() -> bool\n\n"
|
||||||
|
"Check if the animation still has a valid target.\n\n"
|
||||||
|
"Returns:\n"
|
||||||
|
" True if the target still exists, False if it was destroyed."},
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
|
@ -28,6 +28,8 @@ public:
|
||||||
static PyObject* start(PyAnimationObject* self, PyObject* args);
|
static PyObject* start(PyAnimationObject* self, PyObject* args);
|
||||||
static PyObject* update(PyAnimationObject* self, PyObject* args);
|
static PyObject* update(PyAnimationObject* self, PyObject* args);
|
||||||
static PyObject* get_current_value(PyAnimationObject* self, PyObject* args);
|
static PyObject* get_current_value(PyAnimationObject* self, PyObject* args);
|
||||||
|
static PyObject* complete(PyAnimationObject* self, PyObject* args);
|
||||||
|
static PyObject* has_valid_target(PyAnimationObject* self, PyObject* args);
|
||||||
|
|
||||||
static PyGetSetDef getsetters[];
|
static PyGetSetDef getsetters[];
|
||||||
static PyMethodDef methods[];
|
static PyMethodDef methods[];
|
||||||
|
|
Loading…
Reference in New Issue