[flutter] Use new loading infra from spine-c

This commit is contained in:
Mario Zechner 2025-07-25 02:19:53 +02:00
parent da70193a36
commit 6ad01acc6a
11 changed files with 145 additions and 131 deletions

View File

@ -3,6 +3,10 @@
using namespace spine; using namespace spine;
void spine_atlas_dispose(spine_atlas self) {
delete (Atlas *) self;
}
void spine_atlas_flip_v(spine_atlas self) { void spine_atlas_flip_v(spine_atlas self) {
Atlas *_self = (Atlas *) self; Atlas *_self = (Atlas *) self;
_self->flipV(); _self->flipV();

View File

@ -9,6 +9,8 @@
extern "C" { extern "C" {
#endif #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 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_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); SPINE_C_API spine_array_atlas_page spine_atlas_get_pages(spine_atlas self);

View File

@ -197,7 +197,7 @@ namespace spine {
DebugEntry *_head; DebugEntry *_head;
size_t _size; size_t _size;
}; };
#endif // SPINE_NO_CPP_RT #endif// SPINE_NO_CPP_RT
class SP_API DebugExtension : public SpineExtension { class SP_API DebugExtension : public SpineExtension {
struct Allocation { 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); printf("\"%s:%i (%zu bytes at %p)\n", pair.value.fileName, pair.value.line, pair.value.size, pair.value.address);
} }
#else #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); printf("\"%s:%i (%zu bytes at %p)\n", pair.second.fileName, pair.second.line, pair.second.size, pair.second.address);
} }
#endif #endif

View File

@ -21,7 +21,7 @@ fi
npx tsx codegen/src/index.ts npx tsx codegen/src/index.ts
# Build test spine_flutter shared library # Build test spine_flutter shared library
pushd ../spine_flutter/test > /dev/null pushd test > /dev/null
./build.sh ./build.sh
popd popd

View File

@ -45,6 +45,10 @@ class Atlas {
/// Get the native pointer for FFI calls /// Get the native pointer for FFI calls
Pointer get nativePtr => _ptr; Pointer get nativePtr => _ptr;
void dispose() {
SpineBindings.bindings.spine_atlas_dispose(_ptr);
}
void flipV() { void flipV() {
SpineBindings.bindings.spine_atlas_flip_v(_ptr); SpineBindings.bindings.spine_atlas_flip_v(_ptr);
} }

View File

@ -5867,6 +5867,18 @@ class SpineDartBindings {
late final _spine_array_update_buffer = late final _spine_array_update_buffer =
_spine_array_update_bufferPtr.asFunction<ffi.Pointer<spine_update> Function(spine_array_update)>(); _spine_array_update_bufferPtr.asFunction<ffi.Pointer<spine_update> Function(spine_array_update)>();
void spine_atlas_dispose(
spine_atlas self,
) {
return _spine_atlas_dispose(
self,
);
}
late final _spine_atlas_disposePtr =
_lookup<ffi.NativeFunction<ffi.Void Function(spine_atlas)>>('spine_atlas_dispose');
late final _spine_atlas_dispose = _spine_atlas_disposePtr.asFunction<void Function(spine_atlas)>();
void spine_atlas_flip_v( void spine_atlas_flip_v(
spine_atlas self, spine_atlas self,
) { ) {
@ -6557,7 +6569,7 @@ class SpineDartBindings {
late final _spine_vector_get_y = _spine_vector_get_yPtr.asFunction<double Function(spine_vector)>(); late final _spine_vector_get_y = _spine_vector_get_yPtr.asFunction<double Function(spine_vector)>();
/// Atlas functions /// Atlas functions
spine_atlas spine_atlas_load( spine_atlas_result spine_atlas_load(
ffi.Pointer<ffi.Char> atlasData, ffi.Pointer<ffi.Char> atlasData,
) { ) {
return _spine_atlas_load( return _spine_atlas_load(
@ -6566,10 +6578,10 @@ class SpineDartBindings {
} }
late final _spine_atlas_loadPtr = late final _spine_atlas_loadPtr =
_lookup<ffi.NativeFunction<spine_atlas Function(ffi.Pointer<ffi.Char>)>>('spine_atlas_load'); _lookup<ffi.NativeFunction<spine_atlas_result Function(ffi.Pointer<ffi.Char>)>>('spine_atlas_load');
late final _spine_atlas_load = _spine_atlas_loadPtr.asFunction<spine_atlas Function(ffi.Pointer<ffi.Char>)>(); late final _spine_atlas_load = _spine_atlas_loadPtr.asFunction<spine_atlas_result Function(ffi.Pointer<ffi.Char>)>();
spine_atlas spine_atlas_load_callback( spine_atlas_result spine_atlas_load_callback(
ffi.Pointer<ffi.Char> atlasData, ffi.Pointer<ffi.Char> atlasData,
ffi.Pointer<ffi.Char> atlasDir, ffi.Pointer<ffi.Char> atlasDir,
spine_texture_loader_load_func load, spine_texture_loader_load_func load,
@ -6585,75 +6597,50 @@ class SpineDartBindings {
late final _spine_atlas_load_callbackPtr = _lookup< late final _spine_atlas_load_callbackPtr = _lookup<
ffi.NativeFunction< ffi.NativeFunction<
spine_atlas Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, spine_texture_loader_load_func, spine_atlas_result Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, spine_texture_loader_load_func,
spine_texture_loader_unload_func)>>('spine_atlas_load_callback'); spine_texture_loader_unload_func)>>('spine_atlas_load_callback');
late final _spine_atlas_load_callback = _spine_atlas_load_callbackPtr.asFunction< late final _spine_atlas_load_callback = _spine_atlas_load_callbackPtr.asFunction<
spine_atlas Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, spine_texture_loader_load_func, spine_atlas_result Function(ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>, spine_texture_loader_load_func,
spine_texture_loader_unload_func)>(); spine_texture_loader_unload_func)>();
int spine_atlas_get_num_image_paths( ffi.Pointer<ffi.Char> spine_atlas_result_get_error(
spine_atlas atlas, spine_atlas_result result,
) { ) {
return _spine_atlas_get_num_image_paths( return _spine_atlas_result_get_error(
atlas, result,
); );
} }
late final _spine_atlas_get_num_image_pathsPtr = late final _spine_atlas_result_get_errorPtr =
_lookup<ffi.NativeFunction<ffi.Int32 Function(spine_atlas)>>('spine_atlas_get_num_image_paths'); _lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(spine_atlas_result)>>('spine_atlas_result_get_error');
late final _spine_atlas_get_num_image_paths = late final _spine_atlas_result_get_error =
_spine_atlas_get_num_image_pathsPtr.asFunction<int Function(spine_atlas)>(); _spine_atlas_result_get_errorPtr.asFunction<ffi.Pointer<ffi.Char> Function(spine_atlas_result)>();
ffi.Pointer<ffi.Char> spine_atlas_get_image_path( spine_atlas spine_atlas_result_get_atlas(
spine_atlas atlas, spine_atlas_result result,
int index,
) { ) {
return _spine_atlas_get_image_path( return _spine_atlas_result_get_atlas(
atlas, result,
index,
); );
} }
late final _spine_atlas_get_image_pathPtr = late final _spine_atlas_result_get_atlasPtr =
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(spine_atlas, ffi.Int32)>>('spine_atlas_get_image_path'); _lookup<ffi.NativeFunction<spine_atlas Function(spine_atlas_result)>>('spine_atlas_result_get_atlas');
late final _spine_atlas_get_image_path = late final _spine_atlas_result_get_atlas =
_spine_atlas_get_image_pathPtr.asFunction<ffi.Pointer<ffi.Char> Function(spine_atlas, int)>(); _spine_atlas_result_get_atlasPtr.asFunction<spine_atlas Function(spine_atlas_result)>();
bool spine_atlas_is_pma( void spine_atlas_result_dispose(
spine_atlas atlas, spine_atlas_result result,
) { ) {
return _spine_atlas_is_pma( return _spine_atlas_result_dispose(
atlas, result,
); );
} }
late final _spine_atlas_is_pmaPtr = _lookup<ffi.NativeFunction<ffi.Bool Function(spine_atlas)>>('spine_atlas_is_pma'); late final _spine_atlas_result_disposePtr =
late final _spine_atlas_is_pma = _spine_atlas_is_pmaPtr.asFunction<bool Function(spine_atlas)>(); _lookup<ffi.NativeFunction<ffi.Void Function(spine_atlas_result)>>('spine_atlas_result_dispose');
late final _spine_atlas_result_dispose =
ffi.Pointer<ffi.Char> spine_atlas_get_error( _spine_atlas_result_disposePtr.asFunction<void Function(spine_atlas_result)>();
spine_atlas atlas,
) {
return _spine_atlas_get_error(
atlas,
);
}
late final _spine_atlas_get_errorPtr =
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(spine_atlas)>>('spine_atlas_get_error');
late final _spine_atlas_get_error =
_spine_atlas_get_errorPtr.asFunction<ffi.Pointer<ffi.Char> Function(spine_atlas)>();
void spine_atlas_dispose(
spine_atlas atlas,
) {
return _spine_atlas_dispose(
atlas,
);
}
late final _spine_atlas_disposePtr =
_lookup<ffi.NativeFunction<ffi.Void Function(spine_atlas)>>('spine_atlas_dispose');
late final _spine_atlas_dispose = _spine_atlas_disposePtr.asFunction<void Function(spine_atlas)>();
/// Skeleton data functions /// Skeleton data functions
spine_skeleton_data_result spine_skeleton_data_load_json( spine_skeleton_data_result spine_skeleton_data_load_json(
@ -40773,6 +40760,11 @@ typedef spine_update = ffi.Pointer<spine_update_wrapper>;
typedef spine_atlas = ffi.Pointer<spine_atlas_wrapper>; typedef spine_atlas = ffi.Pointer<spine_atlas_wrapper>;
typedef spine_skeleton_data = ffi.Pointer<spine_skeleton_data_wrapper>; typedef spine_skeleton_data = ffi.Pointer<spine_skeleton_data_wrapper>;
final class spine_atlas_result_wrapper extends ffi.Struct {
@ffi.Char()
external int _dummy;
}
final class spine_skeleton_data_result_wrapper extends ffi.Struct { final class spine_skeleton_data_result_wrapper extends ffi.Struct {
@ffi.Char() @ffi.Char()
external int _dummy; external int _dummy;
@ -40815,6 +40807,7 @@ final class spine_texture_loader_wrapper extends ffi.Struct {
typedef spine_bounds = ffi.Pointer<spine_bounds_wrapper>; typedef spine_bounds = ffi.Pointer<spine_bounds_wrapper>;
typedef spine_vector = ffi.Pointer<spine_vector_wrapper>; typedef spine_vector = ffi.Pointer<spine_vector_wrapper>;
typedef spine_atlas_result = ffi.Pointer<spine_atlas_result_wrapper>;
/// Texture loader callbacks /// Texture loader callbacks
typedef spine_texture_loader_load_func = ffi.Pointer<ffi.NativeFunction<spine_texture_loader_load_funcFunction>>; typedef spine_texture_loader_load_func = ffi.Pointer<ffi.NativeFunction<spine_texture_loader_load_funcFunction>>;

View File

@ -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<Char>());
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<Char>(), pathNative.cast<Char>());
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<Utf8>().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;
}
}

View File

@ -29,13 +29,15 @@ library;
import 'generated/spine_dart_bindings_generated.dart'; import 'generated/spine_dart_bindings_generated.dart';
import 'spine_bindings.dart'; import 'spine_bindings.dart';
import 'spine_dart_init.dart' if (dart.library.html) 'spine_flutter_init_web.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 classes
export 'generated/spine_dart.dart'; export 'generated/spine_dart.dart';
// Export extensions
export 'new_extensions.dart';
Future<void> initSpineFlutter({bool useStaticLinkage = false, bool enableMemoryDebugging = false}) async { Future<void> initSpineFlutter({bool useStaticLinkage = false, bool enableMemoryDebugging = false}) async {
final ffi = await initSpineDartFFI(useStaticLinkage); final ffi = await initSpineDartFFI(useStaticLinkage);
final bindings = SpineDartBindings(ffi.dylib); final bindings = SpineDartBindings(ffi.dylib);
@ -52,3 +54,77 @@ int majorVersion() => SpineBindings.bindings.spine_major_version();
int minorVersion() => SpineBindings.bindings.spine_minor_version(); int minorVersion() => SpineBindings.bindings.spine_minor_version();
void reportLeaks() => SpineBindings.bindings.spine_report_leaks(); 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<Char>());
malloc.free(atlasDataNative);
// Check for error
final errorPtr = SpineBindings.bindings.spine_atlas_result_get_error(resultPtr.cast());
if (errorPtr != nullptr) {
final error = errorPtr.cast<Utf8>().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<Char>(), pathNative.cast<Char>());
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<Utf8>().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<Uint8> 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<Char>());
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<Utf8>().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;
}

View File

@ -5,18 +5,18 @@ void main() async {
print('Testing atlas and skeleton data loading...'); print('Testing atlas and skeleton data loading...');
// Initialize with debug extension enabled // Initialize with debug extension enabled
await initSpineFlutter(enableMemoryDebugging: false); await initSpineFlutter(enableMemoryDebugging: true);
// Load atlas // Load atlas
final atlasData = File('../example/assets/spineboy.atlas').readAsStringSync(); final atlasData = File('../example/assets/spineboy.atlas').readAsStringSync();
final atlas = AtlasExtensions.fromString(atlasData); final atlas = loadAtlas(atlasData);
print('Atlas loaded successfully'); print('Atlas loaded successfully');
print('Number of regions: ${atlas.regions.length}'); print('Number of regions: ${atlas.regions.length}');
// Load skeleton data // Load skeleton data
final skeletonJson = File('../example/assets/spineboy-pro.json').readAsStringSync(); 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('Skeleton data loaded successfully');
print('Number of bones: ${skeletonData.bones.length}'); print('Number of bones: ${skeletonData.bones.length}');