mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-06 15:24:55 +08:00
Addresses the loader/saver registration issue in #2899. Additionally, makes it such that you can adjust the SpineSkeletonData in the inspector without crashes. This appears to have been caused by a dangling pointer. Finally, double-clicking on JSON in the inspector opens the text editor and does not crash the Godot editor.
This commit is contained in:
parent
bb228f9b64
commit
b6d76309f3
172
spine-godot/BUGFIX_SUMMARY_V2.md
Normal file
172
spine-godot/BUGFIX_SUMMARY_V2.md
Normal file
@ -0,0 +1,172 @@
|
||||
# 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
|
||||
853
spine-godot/SpineSkeletonDataResource_old.cpp
Normal file
853
spine-godot/SpineSkeletonDataResource_old.cpp
Normal file
@ -0,0 +1,853 @@
|
||||
/******************************************************************************
|
||||
* 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);
|
||||
}
|
||||
220
spine-godot/SpineSkeletonDataResource_old.h
Normal file
220
spine-godot/SpineSkeletonDataResource_old.h
Normal file
@ -0,0 +1,220 @@
|
||||
/******************************************************************************
|
||||
* 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
|
||||
};
|
||||
@ -1,3 +1,4 @@
|
||||
// this is my version (v1), generated with Claude Code
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
@ -233,6 +234,8 @@ SpineSkeletonDataResource::SpineSkeletonDataResource()
|
||||
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));
|
||||
}
|
||||
}
|
||||
@ -240,6 +243,8 @@ SpineSkeletonDataResource::SpineSkeletonDataResource()
|
||||
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");
|
||||
}
|
||||
}
|
||||
@ -251,14 +256,18 @@ SpineSkeletonDataResource::~SpineSkeletonDataResource() {
|
||||
#ifdef TOOLS_ENABLED
|
||||
#if VERSION_MAJOR > 3
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
EditorFileSystem *efs = get_editor_file_system();
|
||||
// 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()) {
|
||||
EditorFileSystem *efs = EditorFileSystem::get_singleton();
|
||||
// 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");
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
// this is my version (v1), generated with Claude Code
|
||||
/******************************************************************************
|
||||
* Spine Runtimes License Agreement
|
||||
* Last updated April 5, 2025. Replaces all prior versions.
|
||||
@ -82,6 +83,13 @@ private:
|
||||
spine::SkeletonData *skeleton_data;
|
||||
spine::AnimationStateData *animation_state_data;
|
||||
|
||||
#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
|
||||
|
||||
void update_skeleton_data();
|
||||
|
||||
#ifdef SPINE_GODOT_EXTENSION
|
||||
|
||||
@ -88,6 +88,12 @@ void initialize_spine_godot_module(ModuleInitializationLevel level) {
|
||||
EditorPlugins::add_plugin_class(StringName("SpineEditorPlugin"));
|
||||
#endif
|
||||
}
|
||||
if (level == MODULE_INITIALIZATION_LEVEL_CORE) {
|
||||
GDREGISTER_CLASS(SpineAtlasResourceFormatLoader);
|
||||
GDREGISTER_CLASS(SpineAtlasResourceFormatSaver);
|
||||
GDREGISTER_CLASS(SpineSkeletonFileResourceFormatLoader);
|
||||
GDREGISTER_CLASS(SpineSkeletonFileResourceFormatSaver);
|
||||
}
|
||||
if (level != MODULE_INITIALIZATION_LEVEL_SCENE) return;
|
||||
#else
|
||||
#if VERSION_MAJOR > 3
|
||||
@ -110,10 +116,12 @@ void register_spine_godot_types() {
|
||||
#endif
|
||||
spine::Bone::setYDown(true);
|
||||
|
||||
#ifndef SPINE_GODOT_EXTENSION
|
||||
GDREGISTER_CLASS(SpineAtlasResourceFormatLoader);
|
||||
GDREGISTER_CLASS(SpineAtlasResourceFormatSaver);
|
||||
GDREGISTER_CLASS(SpineSkeletonFileResourceFormatLoader);
|
||||
GDREGISTER_CLASS(SpineSkeletonFileResourceFormatSaver);
|
||||
#endif
|
||||
|
||||
GDREGISTER_CLASS(SpineObjectWrapper);
|
||||
GDREGISTER_CLASS(SpineAtlasResource);
|
||||
@ -223,7 +231,7 @@ extern "C" GDExtensionBool GDE_EXPORT spine_godot_library_init(GDExtensionInterf
|
||||
GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization);
|
||||
init_obj.register_initializer(initialize_spine_godot_module);
|
||||
init_obj.register_terminator(uninitialize_spine_godot_module);
|
||||
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE);
|
||||
init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_CORE);
|
||||
return init_obj.init();
|
||||
}
|
||||
#endif
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user