From 6ad01acc6acef42787a49cc1767701d93a67586b Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Fri, 25 Jul 2025 02:19:53 +0200 Subject: [PATCH] [flutter] Use new loading infra from spine-c --- spine-c/src/extensions.cpp | 2 +- spine-c/src/extensions.h | 2 +- spine-c/src/generated/atlas.cpp | 4 + spine-c/src/generated/atlas.h | 2 + spine-cpp/include/spine/Debug.h | 4 +- spine-flutter/generate-bindings.sh | 2 +- spine-flutter/lib/generated/atlas.dart | 4 + .../spine_dart_bindings_generated.dart | 103 ++++++++---------- spine-flutter/lib/new_extensions.dart | 65 ----------- spine-flutter/lib/spine_flutter.dart | 82 +++++++++++++- spine-flutter/test/headless_test.dart | 6 +- 11 files changed, 145 insertions(+), 131 deletions(-) delete mode 100644 spine-flutter/lib/new_extensions.dart diff --git a/spine-c/src/extensions.cpp b/spine-c/src/extensions.cpp index ae107718a..55e047f0e 100644 --- a/spine-c/src/extensions.cpp +++ b/spine-c/src/extensions.cpp @@ -214,7 +214,7 @@ public: static CallbackTextureLoad callbackLoader; spine_atlas_result spine_atlas_load_callback(const char *atlasData, const char *atlasDir, spine_texture_loader_load_func load, - spine_texture_loader_unload_func unload) { + spine_texture_loader_unload_func unload) { if (!atlasData) return nullptr; _spine_atlas_result *result = SpineExtension::calloc<_spine_atlas_result>(1, __FILE__, __LINE__); int32_t length = (int32_t) strlen(atlasData); diff --git a/spine-c/src/extensions.h b/spine-c/src/extensions.h index 8943be052..90f974b5d 100644 --- a/spine-c/src/extensions.h +++ b/spine-c/src/extensions.h @@ -76,7 +76,7 @@ SPINE_C_API float spine_vector_get_y(spine_vector vector); // Atlas functions SPINE_C_API spine_atlas_result spine_atlas_load(const char *atlasData); SPINE_C_API spine_atlas_result spine_atlas_load_callback(const char *atlasData, const char *atlasDir, spine_texture_loader_load_func load, - spine_texture_loader_unload_func unload); + spine_texture_loader_unload_func unload); SPINE_C_API const char *spine_atlas_result_get_error(spine_atlas_result result); SPINE_C_API spine_atlas spine_atlas_result_get_atlas(spine_atlas_result result); SPINE_C_API void spine_atlas_result_dispose(spine_atlas_result result); diff --git a/spine-c/src/generated/atlas.cpp b/spine-c/src/generated/atlas.cpp index e1502c933..f8dbcbe70 100644 --- a/spine-c/src/generated/atlas.cpp +++ b/spine-c/src/generated/atlas.cpp @@ -3,6 +3,10 @@ using namespace spine; +void spine_atlas_dispose(spine_atlas self) { + delete (Atlas *) self; +} + void spine_atlas_flip_v(spine_atlas self) { Atlas *_self = (Atlas *) self; _self->flipV(); diff --git a/spine-c/src/generated/atlas.h b/spine-c/src/generated/atlas.h index 94a4b9526..8ae5d231f 100644 --- a/spine-c/src/generated/atlas.h +++ b/spine-c/src/generated/atlas.h @@ -9,6 +9,8 @@ extern "C" { #endif +SPINE_C_API void spine_atlas_dispose(spine_atlas self); + SPINE_C_API void spine_atlas_flip_v(spine_atlas self); SPINE_C_API spine_atlas_region spine_atlas_find_region(spine_atlas self, const char *name); SPINE_C_API spine_array_atlas_page spine_atlas_get_pages(spine_atlas self); diff --git a/spine-cpp/include/spine/Debug.h b/spine-cpp/include/spine/Debug.h index ac4d9404b..b3a3227b6 100644 --- a/spine-cpp/include/spine/Debug.h +++ b/spine-cpp/include/spine/Debug.h @@ -197,7 +197,7 @@ namespace spine { DebugEntry *_head; size_t _size; }; -#endif // SPINE_NO_CPP_RT +#endif// SPINE_NO_CPP_RT class SP_API DebugExtension : public SpineExtension { struct Allocation { @@ -225,7 +225,7 @@ namespace spine { printf("\"%s:%i (%zu bytes at %p)\n", pair.value.fileName, pair.value.line, pair.value.size, pair.value.address); } #else - for (const auto& pair : _allocated) { + for (const auto &pair : _allocated) { printf("\"%s:%i (%zu bytes at %p)\n", pair.second.fileName, pair.second.line, pair.second.size, pair.second.address); } #endif diff --git a/spine-flutter/generate-bindings.sh b/spine-flutter/generate-bindings.sh index 28fb6dbc8..885fddc42 100755 --- a/spine-flutter/generate-bindings.sh +++ b/spine-flutter/generate-bindings.sh @@ -21,7 +21,7 @@ fi npx tsx codegen/src/index.ts # Build test spine_flutter shared library -pushd ../spine_flutter/test > /dev/null +pushd test > /dev/null ./build.sh popd diff --git a/spine-flutter/lib/generated/atlas.dart b/spine-flutter/lib/generated/atlas.dart index 7eb4fb5e8..19d07b7f2 100644 --- a/spine-flutter/lib/generated/atlas.dart +++ b/spine-flutter/lib/generated/atlas.dart @@ -45,6 +45,10 @@ class Atlas { /// Get the native pointer for FFI calls Pointer get nativePtr => _ptr; + void dispose() { + SpineBindings.bindings.spine_atlas_dispose(_ptr); + } + void flipV() { SpineBindings.bindings.spine_atlas_flip_v(_ptr); } diff --git a/spine-flutter/lib/generated/spine_dart_bindings_generated.dart b/spine-flutter/lib/generated/spine_dart_bindings_generated.dart index f6d2468c3..ebb887325 100644 --- a/spine-flutter/lib/generated/spine_dart_bindings_generated.dart +++ b/spine-flutter/lib/generated/spine_dart_bindings_generated.dart @@ -5867,6 +5867,18 @@ class SpineDartBindings { late final _spine_array_update_buffer = _spine_array_update_bufferPtr.asFunction Function(spine_array_update)>(); + void spine_atlas_dispose( + spine_atlas self, + ) { + return _spine_atlas_dispose( + self, + ); + } + + late final _spine_atlas_disposePtr = + _lookup>('spine_atlas_dispose'); + late final _spine_atlas_dispose = _spine_atlas_disposePtr.asFunction(); + void spine_atlas_flip_v( spine_atlas self, ) { @@ -6557,7 +6569,7 @@ class SpineDartBindings { late final _spine_vector_get_y = _spine_vector_get_yPtr.asFunction(); /// Atlas functions - spine_atlas spine_atlas_load( + spine_atlas_result spine_atlas_load( ffi.Pointer atlasData, ) { return _spine_atlas_load( @@ -6566,10 +6578,10 @@ class SpineDartBindings { } late final _spine_atlas_loadPtr = - _lookup)>>('spine_atlas_load'); - late final _spine_atlas_load = _spine_atlas_loadPtr.asFunction)>(); + _lookup)>>('spine_atlas_load'); + late final _spine_atlas_load = _spine_atlas_loadPtr.asFunction)>(); - spine_atlas spine_atlas_load_callback( + spine_atlas_result spine_atlas_load_callback( ffi.Pointer atlasData, ffi.Pointer atlasDir, spine_texture_loader_load_func load, @@ -6585,75 +6597,50 @@ class SpineDartBindings { late final _spine_atlas_load_callbackPtr = _lookup< ffi.NativeFunction< - spine_atlas Function(ffi.Pointer, ffi.Pointer, spine_texture_loader_load_func, + spine_atlas_result Function(ffi.Pointer, ffi.Pointer, spine_texture_loader_load_func, spine_texture_loader_unload_func)>>('spine_atlas_load_callback'); late final _spine_atlas_load_callback = _spine_atlas_load_callbackPtr.asFunction< - spine_atlas Function(ffi.Pointer, ffi.Pointer, spine_texture_loader_load_func, + spine_atlas_result Function(ffi.Pointer, ffi.Pointer, spine_texture_loader_load_func, spine_texture_loader_unload_func)>(); - int spine_atlas_get_num_image_paths( - spine_atlas atlas, + ffi.Pointer spine_atlas_result_get_error( + spine_atlas_result result, ) { - return _spine_atlas_get_num_image_paths( - atlas, + return _spine_atlas_result_get_error( + result, ); } - late final _spine_atlas_get_num_image_pathsPtr = - _lookup>('spine_atlas_get_num_image_paths'); - late final _spine_atlas_get_num_image_paths = - _spine_atlas_get_num_image_pathsPtr.asFunction(); + late final _spine_atlas_result_get_errorPtr = + _lookup Function(spine_atlas_result)>>('spine_atlas_result_get_error'); + late final _spine_atlas_result_get_error = + _spine_atlas_result_get_errorPtr.asFunction Function(spine_atlas_result)>(); - ffi.Pointer spine_atlas_get_image_path( - spine_atlas atlas, - int index, + spine_atlas spine_atlas_result_get_atlas( + spine_atlas_result result, ) { - return _spine_atlas_get_image_path( - atlas, - index, + return _spine_atlas_result_get_atlas( + result, ); } - late final _spine_atlas_get_image_pathPtr = - _lookup Function(spine_atlas, ffi.Int32)>>('spine_atlas_get_image_path'); - late final _spine_atlas_get_image_path = - _spine_atlas_get_image_pathPtr.asFunction Function(spine_atlas, int)>(); + late final _spine_atlas_result_get_atlasPtr = + _lookup>('spine_atlas_result_get_atlas'); + late final _spine_atlas_result_get_atlas = + _spine_atlas_result_get_atlasPtr.asFunction(); - bool spine_atlas_is_pma( - spine_atlas atlas, + void spine_atlas_result_dispose( + spine_atlas_result result, ) { - return _spine_atlas_is_pma( - atlas, + return _spine_atlas_result_dispose( + result, ); } - late final _spine_atlas_is_pmaPtr = _lookup>('spine_atlas_is_pma'); - late final _spine_atlas_is_pma = _spine_atlas_is_pmaPtr.asFunction(); - - ffi.Pointer spine_atlas_get_error( - spine_atlas atlas, - ) { - return _spine_atlas_get_error( - atlas, - ); - } - - late final _spine_atlas_get_errorPtr = - _lookup Function(spine_atlas)>>('spine_atlas_get_error'); - late final _spine_atlas_get_error = - _spine_atlas_get_errorPtr.asFunction Function(spine_atlas)>(); - - void spine_atlas_dispose( - spine_atlas atlas, - ) { - return _spine_atlas_dispose( - atlas, - ); - } - - late final _spine_atlas_disposePtr = - _lookup>('spine_atlas_dispose'); - late final _spine_atlas_dispose = _spine_atlas_disposePtr.asFunction(); + late final _spine_atlas_result_disposePtr = + _lookup>('spine_atlas_result_dispose'); + late final _spine_atlas_result_dispose = + _spine_atlas_result_disposePtr.asFunction(); /// Skeleton data functions spine_skeleton_data_result spine_skeleton_data_load_json( @@ -40773,6 +40760,11 @@ typedef spine_update = ffi.Pointer; typedef spine_atlas = ffi.Pointer; typedef spine_skeleton_data = ffi.Pointer; +final class spine_atlas_result_wrapper extends ffi.Struct { + @ffi.Char() + external int _dummy; +} + final class spine_skeleton_data_result_wrapper extends ffi.Struct { @ffi.Char() external int _dummy; @@ -40815,6 +40807,7 @@ final class spine_texture_loader_wrapper extends ffi.Struct { typedef spine_bounds = ffi.Pointer; typedef spine_vector = ffi.Pointer; +typedef spine_atlas_result = ffi.Pointer; /// Texture loader callbacks typedef spine_texture_loader_load_func = ffi.Pointer>; diff --git a/spine-flutter/lib/new_extensions.dart b/spine-flutter/lib/new_extensions.dart deleted file mode 100644 index 78c46b180..000000000 --- a/spine-flutter/lib/new_extensions.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'dart:ffi'; -import 'package:ffi/ffi.dart'; -import 'generated/atlas.dart'; -import 'generated/skeleton_data.dart'; -import 'spine_bindings.dart'; - -// Atlas Extensions -extension AtlasExtensions on Atlas { - /// Creates an Atlas from atlas data string (without loading images) - static Atlas fromString(String atlasData) { - final atlasDataNative = atlasData.toNativeUtf8(); - final atlasPtr = SpineBindings.bindings.spine_atlas_load(atlasDataNative.cast()); - malloc.free(atlasDataNative); - return Atlas.fromPointer(atlasPtr); - } - - /// Dispose the atlas and free its memory - void dispose() { - SpineBindings.bindings.spine_atlas_dispose(nativePtr.cast()); - } -} - -// Skeleton Data Result wrapper -class SkeletonDataResult { - final String? error; - final SkeletonData? skeletonData; - final Pointer? _resultPtr; - - SkeletonDataResult._(this.error, this.skeletonData, this._resultPtr); - - void dispose() { - if (_resultPtr != null) { - SpineBindings.bindings.spine_skeleton_data_result_dispose(_resultPtr!.cast()); - } - } -} - -// Skeleton Data Extensions -extension SkeletonDataExtensions on SkeletonData { - /// Load skeleton data from JSON string - static SkeletonData fromJson(Atlas atlas, String jsonData, {String? path}) { - final jsonDataNative = jsonData.toNativeUtf8(); - final pathNative = (path ?? '').toNativeUtf8(); - - final resultPtr = SpineBindings.bindings - .spine_skeleton_data_load_json(atlas.nativePtr.cast(), jsonDataNative.cast(), pathNative.cast()); - - malloc.free(jsonDataNative); - malloc.free(pathNative); - - // Check for error - final errorPtr = SpineBindings.bindings.spine_skeleton_data_result_get_error(resultPtr.cast()); - if (errorPtr != nullptr) { - final error = errorPtr.cast().toDartString(); - SpineBindings.bindings.spine_skeleton_data_result_dispose(resultPtr.cast()); - throw Exception("Couldn't load skeleton data: $error"); - } - - // Get skeleton data - final skeletonDataPtr = SpineBindings.bindings.spine_skeleton_data_result_get_data(resultPtr.cast()); - final skeletonData = SkeletonData.fromPointer(skeletonDataPtr); - SpineBindings.bindings.spine_skeleton_data_result_dispose(resultPtr.cast()); - return skeletonData; - } -} diff --git a/spine-flutter/lib/spine_flutter.dart b/spine-flutter/lib/spine_flutter.dart index 937567e92..72828169a 100644 --- a/spine-flutter/lib/spine_flutter.dart +++ b/spine-flutter/lib/spine_flutter.dart @@ -29,13 +29,15 @@ library; import 'generated/spine_dart_bindings_generated.dart'; import 'spine_bindings.dart'; import 'spine_dart_init.dart' if (dart.library.html) 'spine_flutter_init_web.dart'; +import 'dart:ffi'; +import 'dart:typed_data'; +import 'package:ffi/ffi.dart'; +import 'generated/atlas.dart'; +import 'generated/skeleton_data.dart'; // Export generated classes export 'generated/spine_dart.dart'; -// Export extensions -export 'new_extensions.dart'; - Future initSpineFlutter({bool useStaticLinkage = false, bool enableMemoryDebugging = false}) async { final ffi = await initSpineDartFFI(useStaticLinkage); final bindings = SpineDartBindings(ffi.dylib); @@ -52,3 +54,77 @@ int majorVersion() => SpineBindings.bindings.spine_major_version(); int minorVersion() => SpineBindings.bindings.spine_minor_version(); void reportLeaks() => SpineBindings.bindings.spine_report_leaks(); + +/// Load an Atlas from atlas data string +Atlas loadAtlas(String atlasData) { + final atlasDataNative = atlasData.toNativeUtf8(); + final resultPtr = SpineBindings.bindings.spine_atlas_load(atlasDataNative.cast()); + malloc.free(atlasDataNative); + + // Check for error + final errorPtr = SpineBindings.bindings.spine_atlas_result_get_error(resultPtr.cast()); + if (errorPtr != nullptr) { + final error = errorPtr.cast().toDartString(); + SpineBindings.bindings.spine_atlas_result_dispose(resultPtr.cast()); + throw Exception("Couldn't load atlas: $error"); + } + + // Get atlas + final atlasPtr = SpineBindings.bindings.spine_atlas_result_get_atlas(resultPtr.cast()); + final atlas = Atlas.fromPointer(atlasPtr); + SpineBindings.bindings.spine_atlas_result_dispose(resultPtr.cast()); + return atlas; +} + +/// Load skeleton data from JSON string +SkeletonData loadSkeletonDataJson(Atlas atlas, String jsonData, {String? path}) { + final jsonDataNative = jsonData.toNativeUtf8(); + final pathNative = (path ?? '').toNativeUtf8(); + + final resultPtr = SpineBindings.bindings + .spine_skeleton_data_load_json(atlas.nativePtr.cast(), jsonDataNative.cast(), pathNative.cast()); + + malloc.free(jsonDataNative); + malloc.free(pathNative); + + // Check for error + final errorPtr = SpineBindings.bindings.spine_skeleton_data_result_get_error(resultPtr.cast()); + if (errorPtr != nullptr) { + final error = errorPtr.cast().toDartString(); + SpineBindings.bindings.spine_skeleton_data_result_dispose(resultPtr.cast()); + throw Exception("Couldn't load skeleton data: $error"); + } + + // Get skeleton data + final skeletonDataPtr = SpineBindings.bindings.spine_skeleton_data_result_get_data(resultPtr.cast()); + final skeletonData = SkeletonData.fromPointer(skeletonDataPtr); + SpineBindings.bindings.spine_skeleton_data_result_dispose(resultPtr.cast()); + return skeletonData; +} + +/// Load skeleton data from binary data +SkeletonData loadSkeletonDataBinary(Atlas atlas, Uint8List binaryData, {String? path}) { + final Pointer binaryNative = malloc.allocate(binaryData.lengthInBytes); + binaryNative.asTypedList(binaryData.lengthInBytes).setAll(0, binaryData); + final pathNative = (path ?? '').toNativeUtf8(); + + final resultPtr = SpineBindings.bindings.spine_skeleton_data_load_binary( + atlas.nativePtr.cast(), binaryNative.cast(), binaryData.lengthInBytes, pathNative.cast()); + + malloc.free(binaryNative); + malloc.free(pathNative); + + // Check for error + final errorPtr = SpineBindings.bindings.spine_skeleton_data_result_get_error(resultPtr.cast()); + if (errorPtr != nullptr) { + final error = errorPtr.cast().toDartString(); + SpineBindings.bindings.spine_skeleton_data_result_dispose(resultPtr.cast()); + throw Exception("Couldn't load skeleton data: $error"); + } + + // Get skeleton data + final skeletonDataPtr = SpineBindings.bindings.spine_skeleton_data_result_get_data(resultPtr.cast()); + final skeletonData = SkeletonData.fromPointer(skeletonDataPtr); + SpineBindings.bindings.spine_skeleton_data_result_dispose(resultPtr.cast()); + return skeletonData; +} diff --git a/spine-flutter/test/headless_test.dart b/spine-flutter/test/headless_test.dart index 6226b263a..535929c68 100644 --- a/spine-flutter/test/headless_test.dart +++ b/spine-flutter/test/headless_test.dart @@ -5,18 +5,18 @@ void main() async { print('Testing atlas and skeleton data loading...'); // Initialize with debug extension enabled - await initSpineFlutter(enableMemoryDebugging: false); + await initSpineFlutter(enableMemoryDebugging: true); // Load atlas final atlasData = File('../example/assets/spineboy.atlas').readAsStringSync(); - final atlas = AtlasExtensions.fromString(atlasData); + final atlas = loadAtlas(atlasData); print('Atlas loaded successfully'); print('Number of regions: ${atlas.regions.length}'); // Load skeleton data final skeletonJson = File('../example/assets/spineboy-pro.json').readAsStringSync(); - final skeletonData = SkeletonDataExtensions.fromJson(atlas, skeletonJson); + final skeletonData = loadSkeletonDataJson(atlas, skeletonJson); print('Skeleton data loaded successfully'); print('Number of bones: ${skeletonData.bones.length}');