[godot] Removes unnecessary files and comments.

Additionally, CI builds were failing due to the commit where SpineSkin
had an init() method added, but this caused Godot 3.x builds to fail.

Our formatter was also run, so it should no longer fail the format
check.
This commit is contained in:
Luke Ingram 2025-12-15 18:12:36 -04:00
parent 52b114d18a
commit 55d46f2019
7 changed files with 4 additions and 1256 deletions

View File

@ -1,172 +0,0 @@
# Crash Fix: SpineSkeletonDataResource Destructor (v2)
## TL;DR
Fixed segmentation fault during Godot editor shutdown by using `ObjectDB::get_instance()` to safely validate the `EditorFileSystem` pointer before attempting to disconnect the signal in the destructor. The original code used a raw pointer that could become dangling during shutdown; the fix stores the `ObjectID` at construction time and validates it in the destructor.
**Changed files:**
- `spine_godot/SpineSkeletonDataResource.h` - Added `ObjectID editor_file_system_id` member variable
- `spine_godot/SpineSkeletonDataResource.cpp` - Modified constructor and destructor
---
## Context
Mario requested a reference for the claim that removing the disconnect code entirely was safe. Upon investigation, that claim was not fully accurate. Godot's automatic signal cleanup in `Object::~Object()` also calls methods on the signal owner, which could crash if the owner is already destroyed.
The proper fix is to retain the disconnect logic but use Godot's `ObjectDB` system to safely validate that `EditorFileSystem` still exists before calling any methods on it.
---
## The Problem
The Godot editor crashed with signal 11 (SIGSEGV) during shutdown. The crash occurred in the `SpineSkeletonDataResource` destructor when attempting to disconnect from the `EditorFileSystem` singleton's "resources_reimported" signal.
**Original crash backtrace:**
```
[14] SpineSkeletonDataResource::~SpineSkeletonDataResource()
(spine_godot/SpineSkeletonDataResource.cpp:255)
```
**Root cause:** During editor shutdown, singletons are destroyed in an undefined order. The destructor used `get_editor_file_system()` which returns a raw pointer. If `EditorFileSystem` was destroyed before `SpineSkeletonDataResource` objects, the pointer became dangling. Calling `efs->is_connected()` on a dangling pointer caused the segfault.
The check `if (efs)` only verifies the pointer is non-null, not that the memory it points to is valid.
---
## The Solution
Use Godot's `ObjectDB` system to safely validate object existence:
1. Store the `ObjectID` of `EditorFileSystem` when connecting the signal (in constructor)
2. Use `ObjectDB::get_instance(id)` in the destructor to check if the object still exists
3. Only proceed with disconnect if the object is valid
`ObjectDB::get_instance()` is safe to call even if the object has been destroyed - it returns `nullptr` in that case, rather than accessing freed memory.
---
## Code Changes
### Header file (SpineSkeletonDataResource.h)
**Added member variable (inside private section, under TOOLS_ENABLED):**
```cpp
#ifdef TOOLS_ENABLED
// Store the ObjectID of EditorFileSystem to safely validate it in destructor.
// Raw pointers to singletons can become dangling during editor shutdown,
// but ObjectID can be safely validated via ObjectDB::get_instance().
ObjectID editor_file_system_id;
#endif
```
### Source file (SpineSkeletonDataResource.cpp)
**Constructor - store the ObjectID when connecting:**
```cpp
SpineSkeletonDataResource::SpineSkeletonDataResource()
: default_mix(0), skeleton_data(nullptr), animation_state_data(nullptr) {
#ifdef TOOLS_ENABLED
#if VERSION_MAJOR > 3
if (Engine::get_singleton()->is_editor_hint()) {
EditorFileSystem *efs = get_editor_file_system();
if (efs) {
// Store the ObjectID for safe validation in destructor
editor_file_system_id = efs->get_instance_id();
efs->connect("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported));
}
}
#else
if (Engine::get_singleton()->is_editor_hint()) {
EditorFileSystem *efs = EditorFileSystem::get_singleton();
if (efs) {
// Store the ObjectID for safe validation in destructor
editor_file_system_id = efs->get_instance_id();
efs->connect("resources_reimported", this, "_on_resources_reimported");
}
}
#endif
#endif
}
```
**Destructor - validate via ObjectDB before disconnecting:**
```cpp
SpineSkeletonDataResource::~SpineSkeletonDataResource() {
#ifdef TOOLS_ENABLED
#if VERSION_MAJOR > 3
if (Engine::get_singleton()->is_editor_hint()) {
// Use ObjectDB::get_instance() to safely check if EditorFileSystem still exists.
// This avoids the dangling pointer problem during editor shutdown where
// EditorFileSystem may be destroyed before SpineSkeletonDataResource objects.
EditorFileSystem *efs = Object::cast_to<EditorFileSystem>(ObjectDB::get_instance(editor_file_system_id));
if (efs && efs->is_connected("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported))) {
efs->disconnect("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported));
}
}
#else
if (Engine::get_singleton()->is_editor_hint()) {
// Use ObjectDB::get_instance() to safely check if EditorFileSystem still exists.
EditorFileSystem *efs = Object::cast_to<EditorFileSystem>(ObjectDB::get_instance(editor_file_system_id));
if (efs && efs->is_connected("resources_reimported", this, "_on_resources_reimported")) {
efs->disconnect("resources_reimported", this, "_on_resources_reimported");
}
}
#endif
#endif
delete skeleton_data;
delete animation_state_data;
}
```
---
## Why This Works
1. **ObjectID is just a uint64_t** - It's safe to store and doesn't hold a reference to the object
2. **ObjectDB::get_instance() is safe** - It performs a lookup in Godot's object registry and returns `nullptr` if the object no longer exists, without accessing any potentially-freed memory
3. **The pattern is used throughout Godot** - See `godot/core/object/undo_redo.cpp`, `godot/core/object/message_queue.cpp`, and `godot/core/object/callable_method_pointer.h` for examples
**From godot/core/object/undo_redo.cpp:355:**
```cpp
Object *obj = ObjectDB::get_instance(op.object);
if (!obj) { //may have been deleted and this is fine
continue;
}
```
---
## Why Not Just Remove the Disconnect?
Mario correctly questioned whether removing the disconnect entirely was safe. Investigation revealed:
1. Godot's `Object::~Object()` does perform automatic signal cleanup (lines 2186-2198 in object.cpp)
2. However, that cleanup also calls `c.signal.get_object()->_disconnect(...)` which accesses the signal owner
3. If the signal owner is destroyed first, the automatic cleanup would also crash
The automatic cleanup uses `c.callable.get_object()` which does perform ObjectDB validation, but the crash was happening in our custom destructor code before the automatic cleanup ran.
Retaining explicit disconnect with proper validation is the safer approach and follows patterns used elsewhere in the Godot codebase.
---
## Testing Checklist
- [ ] Editor starts without errors
- [ ] Editor closes cleanly without crashes
- [ ] Hot-reload works (modify and reimport a Spine asset while editor is running)
- [ ] Normal gameplay with Spine assets works
- [ ] Test with both module build and GDExtension build
- [ ] Test with C# build if applicable
---
## References
- Godot ObjectDB implementation: `godot/core/object/object.h:1044-1064`
- Godot Object destructor signal cleanup: `godot/core/object/object.cpp:2135-2198`
- Example usage in undo_redo: `godot/core/object/undo_redo.cpp:355`
- GitHub Issue on signal disconnection: https://github.com/godotengine/godot/issues/70414

View File

@ -1,853 +0,0 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2025, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
#include "SpineSkeletonDataResource.h"
#include "SpineCommon.h"
#ifdef SPINE_GODOT_EXTENSION
#include <godot_cpp/classes/encoded_object_as_id.hpp>
#include <godot_cpp/classes/engine.hpp>
#ifdef TOOLS_ENABLED
#include <godot_cpp/classes/editor_interface.hpp>
#endif
#else
#if VERSION_MAJOR > 3
#include "core/config/engine.h"
#ifdef TOOLS_ENABLED
#include "editor/editor_interface.h"
#endif
#else
#include "core/engine.h"
#endif
#include <core/io/marshalls.h>
#endif
#ifdef TOOLS_ENABLED
#ifdef SPINE_GODOT_EXTENSION
#include <godot_cpp/classes/editor_file_system.hpp>
#else
#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5)
#include "editor/file_system/editor_file_system.h"
#else
#include "editor/editor_file_system.h"
#endif
#endif
#endif
void SpineAnimationMix::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_from", "from"),
&SpineAnimationMix::set_from);
ClassDB::bind_method(D_METHOD("get_from"), &SpineAnimationMix::get_from);
ClassDB::bind_method(D_METHOD("set_to", "to"), &SpineAnimationMix::set_to);
ClassDB::bind_method(D_METHOD("get_to"), &SpineAnimationMix::get_to);
ClassDB::bind_method(D_METHOD("set_mix", "mix"), &SpineAnimationMix::set_mix);
ClassDB::bind_method(D_METHOD("get_mix"), &SpineAnimationMix::get_mix);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "from"), "set_from", "get_from");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "to"), "set_to", "get_to");
#if VERSION_MAJOR > 3
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mix"), "set_mix", "get_mix");
#else
ADD_PROPERTY(PropertyInfo(Variant::REAL, "mix"), "set_mix", "get_mix");
#endif
}
SpineAnimationMix::SpineAnimationMix() : from(""), to(""), mix(0) {}
void SpineAnimationMix::set_from(const String &_from) { this->from = _from; }
String SpineAnimationMix::get_from() { return from; }
void SpineAnimationMix::set_to(const String &_to) { this->to = _to; }
String SpineAnimationMix::get_to() { return to; }
void SpineAnimationMix::set_mix(float _mix) { this->mix = _mix; }
float SpineAnimationMix::get_mix() { return mix; }
void SpineSkeletonDataResource::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_skeleton_data_loaded"),
&SpineSkeletonDataResource::is_skeleton_data_loaded);
ClassDB::bind_method(D_METHOD("set_atlas_res", "atlas_res"),
&SpineSkeletonDataResource::set_atlas_res);
ClassDB::bind_method(D_METHOD("get_atlas_res"),
&SpineSkeletonDataResource::get_atlas_res);
ClassDB::bind_method(D_METHOD("set_skeleton_file_res", "skeleton_file_res"),
&SpineSkeletonDataResource::set_skeleton_file_res);
ClassDB::bind_method(D_METHOD("get_skeleton_file_res"),
&SpineSkeletonDataResource::get_skeleton_file_res);
ClassDB::bind_method(D_METHOD("set_default_mix", "default_mix"),
&SpineSkeletonDataResource::set_default_mix);
ClassDB::bind_method(D_METHOD("get_default_mix"),
&SpineSkeletonDataResource::get_default_mix);
ClassDB::bind_method(D_METHOD("set_animation_mixes", "mixes"),
&SpineSkeletonDataResource::set_animation_mixes);
ClassDB::bind_method(D_METHOD("get_animation_mixes"),
&SpineSkeletonDataResource::get_animation_mixes);
// Spine API
ClassDB::bind_method(D_METHOD("find_bone", "bone_name"),
&SpineSkeletonDataResource::find_bone);
ClassDB::bind_method(D_METHOD("find_slot", "slot_name"),
&SpineSkeletonDataResource::find_slot);
ClassDB::bind_method(D_METHOD("find_skin", "skin_name"),
&SpineSkeletonDataResource::find_skin);
ClassDB::bind_method(D_METHOD("find_event", "event_data_name"),
&SpineSkeletonDataResource::find_event);
ClassDB::bind_method(D_METHOD("find_animation", "animation_name"),
&SpineSkeletonDataResource::find_animation);
ClassDB::bind_method(D_METHOD("find_ik_constraint_data", "constraint_name"),
&SpineSkeletonDataResource::find_ik_constraint);
ClassDB::bind_method(
D_METHOD("find_transform_constraint_data", "constraint_name"),
&SpineSkeletonDataResource::find_transform_constraint);
ClassDB::bind_method(D_METHOD("find_path_constraint_data", "constraint_name"),
&SpineSkeletonDataResource::find_path_constraint);
ClassDB::bind_method(D_METHOD("find_physics_constraint_data", "constraint_name"),
&SpineSkeletonDataResource::find_physics_constraint);
ClassDB::bind_method(D_METHOD("get_skeleton_name"),
&SpineSkeletonDataResource::get_skeleton_name);
ClassDB::bind_method(D_METHOD("get_bones"),
&SpineSkeletonDataResource::get_bones);
ClassDB::bind_method(D_METHOD("get_slots"),
&SpineSkeletonDataResource::get_slots);
ClassDB::bind_method(D_METHOD("get_skins"),
&SpineSkeletonDataResource::get_skins);
ClassDB::bind_method(D_METHOD("get_default_skin"),
&SpineSkeletonDataResource::get_default_skin);
ClassDB::bind_method(D_METHOD("set_default_skin", "skin"),
&SpineSkeletonDataResource::set_default_skin);
ClassDB::bind_method(D_METHOD("get_events"),
&SpineSkeletonDataResource::get_events);
ClassDB::bind_method(D_METHOD("get_animations"),
&SpineSkeletonDataResource::get_animations);
ClassDB::bind_method(D_METHOD("get_ik_constraints"),
&SpineSkeletonDataResource::get_ik_constraints);
ClassDB::bind_method(D_METHOD("get_transform_constraints"),
&SpineSkeletonDataResource::get_transform_constraints);
ClassDB::bind_method(D_METHOD("get_path_constraints"),
&SpineSkeletonDataResource::get_path_constraints);
ClassDB::bind_method(D_METHOD("get_physics_constraints"),
&SpineSkeletonDataResource::get_physics_constraints);
ClassDB::bind_method(D_METHOD("get_x"), &SpineSkeletonDataResource::get_x);
ClassDB::bind_method(D_METHOD("get_y"), &SpineSkeletonDataResource::get_y);
ClassDB::bind_method(D_METHOD("get_width"),
&SpineSkeletonDataResource::get_width);
ClassDB::bind_method(D_METHOD("get_height"),
&SpineSkeletonDataResource::get_height);
ClassDB::bind_method(D_METHOD("get_version"),
&SpineSkeletonDataResource::get_version);
ClassDB::bind_method(D_METHOD("get_hash"),
&SpineSkeletonDataResource::get_hash);
ClassDB::bind_method(D_METHOD("get_images_path"),
&SpineSkeletonDataResource::get_images_path);
ClassDB::bind_method(D_METHOD("get_audio_path"),
&SpineSkeletonDataResource::get_audio_path);
ClassDB::bind_method(D_METHOD("get_fps"),
&SpineSkeletonDataResource::get_fps);
ClassDB::bind_method(D_METHOD("get_reference_scale"),
&SpineSkeletonDataResource::get_reference_scale);
ClassDB::bind_method(D_METHOD("set_reference_scale", "reference_scale"),
&SpineSkeletonDataResource::set_reference_scale);
ClassDB::bind_method(D_METHOD("update_skeleton_data"),
&SpineSkeletonDataResource::update_skeleton_data);
ADD_SIGNAL(MethodInfo("skeleton_data_changed"));
ADD_SIGNAL(MethodInfo("_internal_spine_objects_invalidated"));
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "atlas_res",
PropertyHint::PROPERTY_HINT_RESOURCE_TYPE,
"SpineAtlasResource"),
"set_atlas_res", "get_atlas_res");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "skeleton_file_res",
PropertyHint::PROPERTY_HINT_RESOURCE_TYPE,
"SpineSkeletonFileResource"),
"set_skeleton_file_res", "get_skeleton_file_res");
#if VERSION_MAJOR > 3
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "default_mix"), "set_default_mix",
"get_default_mix");
#else
ADD_PROPERTY(PropertyInfo(Variant::REAL, "default_mix"), "set_default_mix",
"get_default_mix");
#endif
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "animation_mixes"),
"set_animation_mixes", "get_animation_mixes");
#ifdef TOOLS_ENABLED
#if VERSION_MAJOR > 3
ClassDB::bind_method(D_METHOD("_on_resources_reimported", "resources"),
&SpineSkeletonDataResource::_on_resources_reimported);
#else
ClassDB::bind_method(D_METHOD("_on_resources_reimported", "resources"),
&SpineSkeletonDataResource::_on_resources_reimported);
#endif
#endif
}
#ifdef TOOLS_ENABLED
EditorFileSystem *get_editor_file_system() {
#ifdef SPINE_GODOT_EXTENSION
EditorInterface *editor_interface = EditorInterface::get_singleton();
if (editor_interface) {
return editor_interface->get_resource_filesystem();
}
return nullptr;
#else
return EditorFileSystem::get_singleton();
#endif
}
#endif
SpineSkeletonDataResource::SpineSkeletonDataResource()
: default_mix(0), skeleton_data(nullptr), animation_state_data(nullptr) {
#ifdef TOOLS_ENABLED
#if VERSION_MAJOR > 3
if (Engine::get_singleton()->is_editor_hint()) {
EditorFileSystem *efs = get_editor_file_system();
if (efs) {
efs->connect("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported));
}
}
#else
if (Engine::get_singleton()->is_editor_hint()) {
EditorFileSystem *efs = EditorFileSystem::get_singleton();
if (efs) {
efs->connect("resources_reimported", this, "_on_resources_reimported");
}
}
#endif
#endif
}
SpineSkeletonDataResource::~SpineSkeletonDataResource() {
/*#ifdef TOOLS_ENABLED
#if VERSION_MAJOR > 3
if (Engine::get_singleton()->is_editor_hint()) {
EditorFileSystem *efs = get_editor_file_system();
if (efs && efs->is_connected("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported))) {
efs->disconnect("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported));
}
}
#else
if (Engine::get_singleton()->is_editor_hint()) {
EditorFileSystem *efs = EditorFileSystem::get_singleton();
if (efs && efs->is_connected("resources_reimported", this, "_on_resources_reimported")) {
efs->disconnect("resources_reimported", this, "_on_resources_reimported");
}
}
#endif
#endif
*/
delete skeleton_data;
delete animation_state_data;
}
#ifdef TOOLS_ENABLED
#if VERSION_MAJOR > 3
void SpineSkeletonDataResource::_on_resources_reimported(const PackedStringArray &resources) {
for (int i = 0; i < resources.size(); i++) {
if (atlas_res.is_valid() && atlas_res->get_path() == resources[i]) {
#ifdef SPINE_GODOT_EXTENSION
atlas_res = ResourceLoader::get_singleton()->load(resources[i], "SpineAtlasResource", ResourceLoader::CACHE_MODE_IGNORE);
#else
atlas_res = ResourceLoader::load(resources[i], "SpineAtlasResource", ResourceFormatLoader::CACHE_MODE_IGNORE);
#endif
update_skeleton_data();
} else if (skeleton_file_res.is_valid() && skeleton_file_res->get_path() == resources[i]) {
#ifdef SPINE_GODOT_EXTENSION
skeleton_file_res = ResourceLoader::get_singleton()->load(resources[i], "SpineSkeletonFileResource", ResourceLoader::CACHE_MODE_IGNORE);
#else
skeleton_file_res = ResourceLoader::load(resources[i], "SpineSkeletonFileResource", ResourceFormatLoader::CACHE_MODE_IGNORE);
#endif
update_skeleton_data();
}
}
}
#else
void SpineSkeletonDataResource::_on_resources_reimported(const PoolStringArray &resources) {
for (int i = 0; i < resources.size(); i++) {
if (atlas_res.is_valid() && atlas_res->get_path() == resources[i]) {
atlas_res = ResourceLoader::load(resources[i]);
update_skeleton_data();
} else if (skeleton_file_res.is_valid() && skeleton_file_res->get_path() == resources[i]) {
skeleton_file_res = ResourceLoader::load(resources[i]);
update_skeleton_data();
}
}
}
#endif
#endif
void SpineSkeletonDataResource::update_skeleton_data() {
if (skeleton_data) {
delete skeleton_data;
skeleton_data = nullptr;
}
if (animation_state_data) {
delete animation_state_data;
animation_state_data = nullptr;
}
emit_signal(SNAME("_internal_spine_objects_invalidated"));
if (atlas_res.is_valid() && skeleton_file_res.is_valid()) {
load_resources(atlas_res->get_spine_atlas(), skeleton_file_res->get_json(),
skeleton_file_res->get_binary());
}
emit_signal(SNAME("skeleton_data_changed"));
#ifdef TOOLS_ENABLED
NOTIFY_PROPERTY_LIST_CHANGED();
#endif
}
#ifdef SPINE_GODOT_EXTENSION
void SpineSkeletonDataResource::load_resources(spine::Atlas *atlas,
const String &json,
const PackedByteArray &binary) {
#else
void SpineSkeletonDataResource::load_resources(spine::Atlas *atlas,
const String &json,
const Vector<uint8_t> &binary) {
#endif
if ((EMPTY(json) && EMPTY(binary)) || atlas == nullptr)
return;
spine::SkeletonData *data;
if (!EMPTY(json)) {
spine::SkeletonJson skeletonJson(atlas);
data = skeletonJson.readSkeletonData(json.utf8().ptr());
if (!data) {
ERR_PRINT(String("Error while loading skeleton data: ") + get_path());
ERR_PRINT(String("Error message: ") + skeletonJson.getError().buffer());
return;
}
} else {
spine::SkeletonBinary skeletonBinary(atlas);
data = skeletonBinary.readSkeletonData(binary.ptr(), binary.size());
if (!data) {
ERR_PRINT(String("Error while loading skeleton data: ") + get_path());
ERR_PRINT(String("Error message: ") + skeletonBinary.getError().buffer());
return;
}
}
skeleton_data = data;
animation_state_data = new spine::AnimationStateData(data);
update_mixes();
}
bool SpineSkeletonDataResource::is_skeleton_data_loaded() const {
return skeleton_data != nullptr;
}
void SpineSkeletonDataResource::set_atlas_res(
const Ref<SpineAtlasResource> &atlas) {
atlas_res = atlas;
update_skeleton_data();
}
Ref<SpineAtlasResource> SpineSkeletonDataResource::get_atlas_res() {
return atlas_res;
}
void SpineSkeletonDataResource::set_skeleton_file_res(
const Ref<SpineSkeletonFileResource> &skeleton_file) {
skeleton_file_res = skeleton_file;
update_skeleton_data();
}
Ref<SpineSkeletonFileResource>
SpineSkeletonDataResource::get_skeleton_file_res() {
return skeleton_file_res;
}
#ifdef SPINE_GODOT_EXTENSION
void SpineSkeletonDataResource::get_animation_names(PackedStringArray &animation_names) const {
#else
void SpineSkeletonDataResource::get_animation_names(Vector<String> &animation_names) const {
#endif
animation_names.clear();
if (!is_skeleton_data_loaded())
return;
auto animations = skeleton_data->getAnimations();
for (size_t i = 0; i < animations.size(); ++i) {
auto animation = animations[i];
String name;
#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5)
name = String::utf8(animation->getName().buffer());
#else
name.parse_utf8(animation->getName().buffer());
#endif
animation_names.push_back(name);
}
}
#ifdef SPINE_GODOT_EXTENSION
void SpineSkeletonDataResource::get_skin_names(PackedStringArray &skin_names) const {
#else
void SpineSkeletonDataResource::get_skin_names(Vector<String> &skin_names) const {
#endif
skin_names.clear();
if (!is_skeleton_data_loaded())
return;
auto skins = skeleton_data->getSkins();
for (size_t i = 0; i < skins.size(); ++i) {
auto skin = skins[i];
String name;
#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5)
name = String::utf8(skin->getName().buffer());
#else
name.parse_utf8(skin->getName().buffer());
#endif
skin_names.push_back(name);
}
}
#ifdef SPINE_GODOT_EXTENSION
void SpineSkeletonDataResource::get_slot_names(PackedStringArray &slot_names) {
#else
void SpineSkeletonDataResource::get_slot_names(Vector<String> &slot_names) {
#endif
slot_names.clear();
if (!is_skeleton_data_loaded())
return;
auto slots = skeleton_data->getSlots();
for (size_t i = 0; i < slots.size(); ++i) {
auto slot = slots[i];
String name;
#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5)
name = String::utf8(slot->getName().buffer());
#else
name.parse_utf8(slot->getName().buffer());
#endif
slot_names.push_back(name);
}
}
#ifdef SPINE_GODOT_EXTENSION
void SpineSkeletonDataResource::get_bone_names(PackedStringArray &bone_names) {
#else
void SpineSkeletonDataResource::get_bone_names(Vector<String> &bone_names) {
#endif
bone_names.clear();
if (!is_skeleton_data_loaded())
return;
auto bones = skeleton_data->getBones();
for (size_t i = 0; i < bones.size(); ++i) {
auto bone = bones[i];
String name;
#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5)
name = String::utf8(bone->getName().buffer());
#else
name.parse_utf8(bone->getName().buffer());
#endif
bone_names.push_back(name);
}
}
void SpineSkeletonDataResource::set_default_mix(float _default_mix) {
this->default_mix = _default_mix;
update_mixes();
}
float SpineSkeletonDataResource::get_default_mix() { return default_mix; }
void SpineSkeletonDataResource::set_animation_mixes(Array _animation_mixes) {
for (int i = 0; i < _animation_mixes.size(); i++) {
auto objectId = Object::cast_to<EncodedObjectAsID>(_animation_mixes[0]);
if (objectId) {
ERR_PRINT("Live-editing of animation mixes is not supported.");
return;
}
}
this->animation_mixes = _animation_mixes;
update_mixes();
}
Array SpineSkeletonDataResource::get_animation_mixes() {
return animation_mixes;
}
void SpineSkeletonDataResource::update_mixes() {
if (!is_skeleton_data_loaded())
return;
animation_state_data->clear();
animation_state_data->setDefaultMix(default_mix);
for (int i = 0; i < animation_mixes.size(); i++) {
Ref<SpineAnimationMix> mix = animation_mixes[i];
spine::Animation *from =
skeleton_data->findAnimation(mix->get_from().utf8().ptr());
spine::Animation *to =
skeleton_data->findAnimation(mix->get_to().utf8().ptr());
if (!from) {
ERR_PRINT(vformat("Failed to set animation mix %s->%s. Animation %s does "
"not exist in skeleton.",
from, to, from));
continue;
}
if (!to) {
ERR_PRINT(vformat("Failed to set animation mix %s->%s. Animation %s does "
"not exist in skeleton.",
from, to, to));
continue;
}
animation_state_data->setMix(from, to, mix->get_mix());
}
}
Ref<SpineAnimation>
SpineSkeletonDataResource::find_animation(const String &animation_name) const {
SPINE_CHECK(skeleton_data, nullptr)
if (EMPTY(animation_name))
return nullptr;
auto animation =
skeleton_data->findAnimation(SPINE_STRING_TMP(animation_name));
if (!animation)
return nullptr;
Ref<SpineAnimation> animation_ref(memnew(SpineAnimation));
animation_ref->set_spine_object(this, animation);
return animation_ref;
}
Ref<SpineBoneData>
SpineSkeletonDataResource::find_bone(const String &bone_name) const {
SPINE_CHECK(skeleton_data, nullptr)
if (EMPTY(bone_name))
return nullptr;
auto bone = skeleton_data->findBone(SPINE_STRING_TMP(bone_name));
if (!bone)
return nullptr;
Ref<SpineBoneData> bone_ref(memnew(SpineBoneData));
bone_ref->set_spine_object(this, bone);
return bone_ref;
}
Ref<SpineSlotData>
SpineSkeletonDataResource::find_slot(const String &slot_name) const {
SPINE_CHECK(skeleton_data, nullptr)
if (EMPTY(slot_name))
return nullptr;
auto slot = skeleton_data->findSlot(SPINE_STRING_TMP(slot_name));
if (!slot)
return nullptr;
Ref<SpineSlotData> slot_ref(memnew(SpineSlotData));
slot_ref->set_spine_object(this, slot);
return slot_ref;
}
Ref<SpineSkin>
SpineSkeletonDataResource::find_skin(const String &skin_name) const {
SPINE_CHECK(skeleton_data, nullptr)
if (EMPTY(skin_name))
return nullptr;
auto skin = skeleton_data->findSkin(SPINE_STRING_TMP(skin_name));
if (!skin)
return nullptr;
Ref<SpineSkin> skin_ref(memnew(SpineSkin));
skin_ref->set_spine_object(this, skin);
return skin_ref;
}
Ref<SpineEventData>
SpineSkeletonDataResource::find_event(const String &event_data_name) const {
SPINE_CHECK(skeleton_data, nullptr)
if (EMPTY(event_data_name))
return nullptr;
auto event = skeleton_data->findEvent(SPINE_STRING_TMP(event_data_name));
if (!event)
return nullptr;
Ref<SpineEventData> event_ref(memnew(SpineEventData));
event_ref->set_spine_object(this, event);
return event_ref;
}
Ref<SpineIkConstraintData> SpineSkeletonDataResource::find_ik_constraint(
const String &constraint_name) const {
SPINE_CHECK(skeleton_data, nullptr)
if (EMPTY(constraint_name))
return nullptr;
auto constraint =
skeleton_data->findIkConstraint(SPINE_STRING_TMP(constraint_name));
if (!constraint)
return nullptr;
Ref<SpineIkConstraintData> constraint_ref(memnew(SpineIkConstraintData));
constraint_ref->set_spine_object(this, constraint);
return constraint_ref;
}
Ref<SpineTransformConstraintData>
SpineSkeletonDataResource::find_transform_constraint(
const String &constraint_name) const {
SPINE_CHECK(skeleton_data, nullptr)
if (EMPTY(constraint_name))
return nullptr;
auto constraint =
skeleton_data->findTransformConstraint(SPINE_STRING_TMP(constraint_name));
if (!constraint)
return nullptr;
Ref<SpineTransformConstraintData> constraint_ref(
memnew(SpineTransformConstraintData));
constraint_ref->set_spine_object(this, constraint);
return constraint_ref;
}
Ref<SpinePathConstraintData> SpineSkeletonDataResource::find_path_constraint(
const String &constraint_name) const {
SPINE_CHECK(skeleton_data, nullptr)
if (EMPTY(constraint_name))
return nullptr;
auto constraint =
skeleton_data->findPathConstraint(SPINE_STRING_TMP(constraint_name));
if (constraint == nullptr)
return nullptr;
Ref<SpinePathConstraintData> constraint_ref(memnew(SpinePathConstraintData));
constraint_ref->set_spine_object(this, constraint);
return constraint_ref;
}
Ref<SpinePhysicsConstraintData>
SpineSkeletonDataResource::find_physics_constraint(
const String &constraint_name) const {
SPINE_CHECK(skeleton_data, nullptr)
if (EMPTY(constraint_name))
return nullptr;
auto constraint =
skeleton_data->findPhysicsConstraint(SPINE_STRING_TMP(constraint_name));
if (constraint == nullptr)
return nullptr;
Ref<SpinePhysicsConstraintData> constraint_ref(
memnew(SpinePhysicsConstraintData));
constraint_ref->set_spine_object(this, constraint);
return constraint_ref;
}
String SpineSkeletonDataResource::get_skeleton_name() const {
SPINE_CHECK(skeleton_data, "")
String name;
#if (VERSION_MAJOR >= 4 && VERSION_MINOR >= 5)
name = String::utf8(skeleton_data->getName().buffer());
#else
name.parse_utf8(skeleton_data->getName().buffer());
#endif
return name;
}
Array SpineSkeletonDataResource::get_bones() const {
Array result;
SPINE_CHECK(skeleton_data, result)
auto bones = skeleton_data->getBones();
result.resize((int) bones.size());
for (int i = 0; i < bones.size(); ++i) {
Ref<SpineBoneData> bone_ref(memnew(SpineBoneData));
bone_ref->set_spine_object(this, bones[i]);
result[i] = bone_ref;
}
return result;
}
Array SpineSkeletonDataResource::get_slots() const {
Array result;
SPINE_CHECK(skeleton_data, result)
auto slots = skeleton_data->getSlots();
result.resize((int) slots.size());
for (int i = 0; i < slots.size(); ++i) {
Ref<SpineSlotData> slot_ref(memnew(SpineSlotData));
slot_ref->set_spine_object(this, slots[i]);
result[i] = slot_ref;
}
return result;
}
Array SpineSkeletonDataResource::get_skins() const {
Array result;
SPINE_CHECK(skeleton_data, result)
auto skins = skeleton_data->getSkins();
result.resize((int) skins.size());
for (int i = 0; i < skins.size(); ++i) {
Ref<SpineSkin> skin_ref(memnew(SpineSkin));
skin_ref->set_spine_object(this, skins[i]);
result[i] = skin_ref;
}
return result;
}
Ref<SpineSkin> SpineSkeletonDataResource::get_default_skin() const {
SPINE_CHECK(skeleton_data, nullptr)
auto skin = skeleton_data->getDefaultSkin();
if (skin)
return nullptr;
Ref<SpineSkin> skin_ref(memnew(SpineSkin));
skin_ref->set_spine_object(this, skin);
return skin_ref;
}
void SpineSkeletonDataResource::set_default_skin(Ref<SpineSkin> skin) {
SPINE_CHECK(skeleton_data, )
skeleton_data->setDefaultSkin(skin.is_valid() && skin->get_spine_object()
? skin->get_spine_object()
: nullptr);
}
Array SpineSkeletonDataResource::get_events() const {
Array result;
SPINE_CHECK(skeleton_data, result)
auto events = skeleton_data->getEvents();
result.resize((int) events.size());
for (int i = 0; i < events.size(); ++i) {
Ref<SpineEventData> event_ref(memnew(SpineEventData));
event_ref->set_spine_object(this, events[i]);
result[i] = event_ref;
}
return result;
}
Array SpineSkeletonDataResource::get_animations() const {
Array result;
SPINE_CHECK(skeleton_data, result)
auto animations = skeleton_data->getAnimations();
result.resize((int) animations.size());
for (int i = 0; i < animations.size(); ++i) {
Ref<SpineAnimation> animation_ref(memnew(SpineAnimation));
animation_ref->set_spine_object(this, animations[i]);
result[i] = animation_ref;
}
return result;
}
Array SpineSkeletonDataResource::get_ik_constraints() const {
Array result;
SPINE_CHECK(skeleton_data, result)
auto constraints = skeleton_data->getIkConstraints();
result.resize((int) constraints.size());
for (int i = 0; i < constraints.size(); ++i) {
Ref<SpineIkConstraintData> constraint_ref(memnew(SpineIkConstraintData));
constraint_ref->set_spine_object(this, constraints[i]);
result[i] = constraint_ref;
}
return result;
}
Array SpineSkeletonDataResource::get_transform_constraints() const {
Array result;
SPINE_CHECK(skeleton_data, result)
auto constraints = skeleton_data->getTransformConstraints();
result.resize((int) constraints.size());
for (int i = 0; i < constraints.size(); ++i) {
Ref<SpineTransformConstraintData> constraint_ref(
memnew(SpineTransformConstraintData));
constraint_ref->set_spine_object(this, constraints[i]);
result[i] = constraint_ref;
}
return result;
}
Array SpineSkeletonDataResource::get_path_constraints() const {
Array result;
SPINE_CHECK(skeleton_data, result)
auto constraints = skeleton_data->getPathConstraints();
result.resize((int) constraints.size());
for (int i = 0; i < constraints.size(); ++i) {
Ref<SpinePathConstraintData> constraint_ref(
memnew(SpinePathConstraintData));
constraint_ref->set_spine_object(this, constraints[i]);
result[i] = constraint_ref;
}
return result;
}
Array SpineSkeletonDataResource::get_physics_constraints() const {
Array result;
SPINE_CHECK(skeleton_data, result)
auto constraints = skeleton_data->getPhysicsConstraints();
result.resize((int) constraints.size());
for (int i = 0; i < constraints.size(); ++i) {
Ref<SpinePhysicsConstraintData> constraint_ref(
memnew(SpinePhysicsConstraintData));
constraint_ref->set_spine_object(this, constraints[i]);
result[i] = constraint_ref;
}
return result;
}
float SpineSkeletonDataResource::get_x() const {
SPINE_CHECK(skeleton_data, 0)
return skeleton_data->getX();
}
float SpineSkeletonDataResource::get_y() const {
SPINE_CHECK(skeleton_data, 0)
return skeleton_data->getY();
}
float SpineSkeletonDataResource::get_width() const {
SPINE_CHECK(skeleton_data, 0)
return skeleton_data->getWidth();
}
float SpineSkeletonDataResource::get_height() const {
SPINE_CHECK(skeleton_data, 0)
return skeleton_data->getHeight();
}
String SpineSkeletonDataResource::get_version() const {
SPINE_CHECK(skeleton_data, "")
return skeleton_data->getVersion().buffer();
}
String SpineSkeletonDataResource::get_hash() const {
SPINE_CHECK(skeleton_data, "")
return skeleton_data->getHash().buffer();
}
String SpineSkeletonDataResource::get_images_path() const {
SPINE_CHECK(skeleton_data, "")
return skeleton_data->getImagesPath().buffer();
}
String SpineSkeletonDataResource::get_audio_path() const {
SPINE_CHECK(skeleton_data, "")
return skeleton_data->getAudioPath().buffer();
}
float SpineSkeletonDataResource::get_fps() const {
SPINE_CHECK(skeleton_data, 0)
return skeleton_data->getFps();
}
float SpineSkeletonDataResource::get_reference_scale() const {
SPINE_CHECK(skeleton_data, 100);
return skeleton_data->getReferenceScale();
}
void SpineSkeletonDataResource::set_reference_scale(float reference_scale) {
SPINE_CHECK(skeleton_data, )
skeleton_data->setReferenceScale(reference_scale);
}

View File

@ -1,220 +0,0 @@
/******************************************************************************
* Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions.
*
* Copyright (c) 2013-2025, Esoteric Software LLC
*
* Integration of the Spine Runtimes into software or otherwise creating
* derivative works of the Spine Runtimes is permitted under the terms and
* conditions of Section 2 of the Spine Editor License Agreement:
* http://esotericsoftware.com/spine-editor-license
*
* Otherwise, it is permitted to integrate the Spine Runtimes into software
* or otherwise create derivative works of the Spine Runtimes (collectively,
* "Products"), provided that each user of the Products must obtain their own
* Spine Editor license and redistribution of the Products in any form must
* include this license and copyright notice.
*
* THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
* BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*****************************************************************************/
#pragma once
#include "SpineAnimation.h"
#include "SpineAtlasResource.h"
#include "SpineBoneData.h"
#include "SpineEventData.h"
#include "SpineIkConstraintData.h"
#include "SpinePathConstraintData.h"
#include "SpinePhysicsConstraintData.h"
#include "SpineSkeletonFileResource.h"
#include "SpineSkin.h"
#include "SpineSlotData.h"
#include "SpineTransformConstraintData.h"
class SpineAnimationMix : public Resource {
GDCLASS(SpineAnimationMix, Resource)
protected:
static void _bind_methods();
String from;
String to;
float mix;
public:
SpineAnimationMix();
void set_from(const String &from);
String get_from();
void set_to(const String &to);
String get_to();
void set_mix(float mix);
float get_mix();
};
class SpineSkeletonDataResource : public Resource {
GDCLASS(SpineSkeletonDataResource, Resource)
protected:
static void _bind_methods();
private:
Ref<SpineAtlasResource> atlas_res;
Ref<SpineSkeletonFileResource> skeleton_file_res;
float default_mix;
Array animation_mixes;
spine::SkeletonData *skeleton_data;
spine::AnimationStateData *animation_state_data;
void update_skeleton_data();
#ifdef SPINE_GODOT_EXTENSION
void load_resources(spine::Atlas *atlas, const String &json,
const PackedByteArray &binary);
#else
void load_resources(spine::Atlas *atlas, const String &json,
const Vector<uint8_t> &binary);
#endif
public:
SpineSkeletonDataResource();
virtual ~SpineSkeletonDataResource();
bool is_skeleton_data_loaded() const;
void set_atlas_res(const Ref<SpineAtlasResource> &atlas);
Ref<SpineAtlasResource> get_atlas_res();
void
set_skeleton_file_res(const Ref<SpineSkeletonFileResource> &skeleton_file);
Ref<SpineSkeletonFileResource> get_skeleton_file_res();
spine::SkeletonData *get_skeleton_data() const { return skeleton_data; }
spine::AnimationStateData *get_animation_state_data() const {
return animation_state_data;
}
#ifdef SPINE_GODOT_EXTENSION
void get_animation_names(PackedStringArray &animation_names) const;
void get_skin_names(PackedStringArray &l) const;
void get_slot_names(PackedStringArray &slot_names);
void get_bone_names(PackedStringArray &bone_names);
#else
void get_animation_names(Vector<String> &animation_names) const;
void get_skin_names(Vector<String> &l) const;
void get_slot_names(Vector<String> &slot_names);
void get_bone_names(Vector<String> &bone_names);
#endif
void set_default_mix(float default_mix);
float get_default_mix();
void set_animation_mixes(Array animation_mixes);
Array get_animation_mixes();
// Used by SpineEditorPropertyAnimationMix(es) to update the underlying
// AnimationState
void update_mixes();
// Spine API
Ref<SpineBoneData> find_bone(const String &bone_name) const;
Ref<SpineSlotData> find_slot(const String &slot_name) const;
Ref<SpineSkin> find_skin(const String &skin_name) const;
Ref<SpineEventData> find_event(const String &event_data_name) const;
Ref<SpineAnimation> find_animation(const String &animation_name) const;
Ref<SpineIkConstraintData>
find_ik_constraint(const String &constraint_name) const;
Ref<SpineTransformConstraintData>
find_transform_constraint(const String &constraint_name) const;
Ref<SpinePathConstraintData>
find_path_constraint(const String &constraint_name) const;
Ref<SpinePhysicsConstraintData>
find_physics_constraint(const String &constraint_name) const;
String get_skeleton_name() const;
Array get_bones() const;
Array get_slots() const;
Array get_skins() const;
Ref<SpineSkin> get_default_skin() const;
void set_default_skin(Ref<SpineSkin> skin);
Array get_events() const;
Array get_animations() const;
Array get_ik_constraints() const;
Array get_transform_constraints() const;
Array get_path_constraints() const;
Array get_physics_constraints() const;
float get_x() const;
float get_y() const;
float get_width() const;
float get_height() const;
String get_version() const;
String get_hash() const;
String get_images_path() const;
String get_audio_path() const;
float get_fps() const;
float get_reference_scale() const;
void set_reference_scale(float reference_scale);
#ifdef TOOLS_ENABLED
#if VERSION_MAJOR > 3
void _on_resources_reimported(const PackedStringArray &resources);
#else
void _on_resources_reimported(const PoolStringArray &resources);
#endif
#endif
};

View File

@ -1,4 +1,3 @@
// this is my version (v1), generated with Claude Code
/****************************************************************************** /******************************************************************************
* Spine Runtimes License Agreement * Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions. * Last updated April 5, 2025. Replaces all prior versions.
@ -256,9 +255,6 @@ SpineSkeletonDataResource::~SpineSkeletonDataResource() {
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
#if VERSION_MAJOR > 3 #if VERSION_MAJOR > 3
if (Engine::get_singleton()->is_editor_hint()) { if (Engine::get_singleton()->is_editor_hint()) {
// Use ObjectDB::get_instance() to safely check if EditorFileSystem still exists.
// This avoids the dangling pointer problem during editor shutdown where
// EditorFileSystem may be destroyed before SpineSkeletonDataResource objects.
EditorFileSystem *efs = Object::cast_to<EditorFileSystem>(ObjectDB::get_instance(editor_file_system_id)); EditorFileSystem *efs = Object::cast_to<EditorFileSystem>(ObjectDB::get_instance(editor_file_system_id));
if (efs && efs->is_connected("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported))) { if (efs && efs->is_connected("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported))) {
efs->disconnect("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported)); efs->disconnect("resources_reimported", callable_mp(this, &SpineSkeletonDataResource::_on_resources_reimported));
@ -266,7 +262,6 @@ SpineSkeletonDataResource::~SpineSkeletonDataResource() {
} }
#else #else
if (Engine::get_singleton()->is_editor_hint()) { if (Engine::get_singleton()->is_editor_hint()) {
// Use ObjectDB::get_instance() to safely check if EditorFileSystem still exists.
EditorFileSystem *efs = Object::cast_to<EditorFileSystem>(ObjectDB::get_instance(editor_file_system_id)); EditorFileSystem *efs = Object::cast_to<EditorFileSystem>(ObjectDB::get_instance(editor_file_system_id));
if (efs && efs->is_connected("resources_reimported", this, "_on_resources_reimported")) { if (efs && efs->is_connected("resources_reimported", this, "_on_resources_reimported")) {
efs->disconnect("resources_reimported", this, "_on_resources_reimported"); efs->disconnect("resources_reimported", this, "_on_resources_reimported");

View File

@ -1,4 +1,3 @@
// this is my version (v1), generated with Claude Code
/****************************************************************************** /******************************************************************************
* Spine Runtimes License Agreement * Spine Runtimes License Agreement
* Last updated April 5, 2025. Replaces all prior versions. * Last updated April 5, 2025. Replaces all prior versions.
@ -84,9 +83,6 @@ private:
spine::AnimationStateData *animation_state_data; spine::AnimationStateData *animation_state_data;
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
// Store the ObjectID of EditorFileSystem to safely validate it in destructor.
// Raw pointers to singletons can become dangling during editor shutdown,
// but ObjectID can be safely validated via ObjectDB::get_instance().
ObjectID editor_file_system_id; ObjectID editor_file_system_id;
#endif #endif

View File

@ -45,7 +45,9 @@ void SpineSkin::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_attachments"), &SpineSkin::get_attachments); ClassDB::bind_method(D_METHOD("get_attachments"), &SpineSkin::get_attachments);
ClassDB::bind_method(D_METHOD("get_bones"), &SpineSkin::get_bones); ClassDB::bind_method(D_METHOD("get_bones"), &SpineSkin::get_bones);
ClassDB::bind_method(D_METHOD("get_constraints"), &SpineSkin::get_constraints); ClassDB::bind_method(D_METHOD("get_constraints"), &SpineSkin::get_constraints);
#if VERSION_MAJOR >= 4
ClassDB::bind_method(D_METHOD("init", "name", "sprite"), &SpineSkin::init); ClassDB::bind_method(D_METHOD("init", "name", "sprite"), &SpineSkin::init);
#endif
} }
SpineSkin::SpineSkin() : owns_skin(false) { SpineSkin::SpineSkin() : owns_skin(false) {

View File

@ -116,12 +116,12 @@ void register_spine_godot_types() {
#endif #endif
spine::Bone::setYDown(true); spine::Bone::setYDown(true);
#ifndef SPINE_GODOT_EXTENSION #ifndef SPINE_GODOT_EXTENSION
GDREGISTER_CLASS(SpineAtlasResourceFormatLoader); GDREGISTER_CLASS(SpineAtlasResourceFormatLoader);
GDREGISTER_CLASS(SpineAtlasResourceFormatSaver); GDREGISTER_CLASS(SpineAtlasResourceFormatSaver);
GDREGISTER_CLASS(SpineSkeletonFileResourceFormatLoader); GDREGISTER_CLASS(SpineSkeletonFileResourceFormatLoader);
GDREGISTER_CLASS(SpineSkeletonFileResourceFormatSaver); GDREGISTER_CLASS(SpineSkeletonFileResourceFormatSaver);
#endif #endif
GDREGISTER_CLASS(SpineObjectWrapper); GDREGISTER_CLASS(SpineObjectWrapper);
GDREGISTER_CLASS(SpineAtlasResource); GDREGISTER_CLASS(SpineAtlasResource);