[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

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -5867,6 +5867,18 @@ class SpineDartBindings {
late final _spine_array_update_buffer =
_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(
spine_atlas self,
) {
@ -6557,7 +6569,7 @@ class SpineDartBindings {
late final _spine_vector_get_y = _spine_vector_get_yPtr.asFunction<double Function(spine_vector)>();
/// Atlas functions
spine_atlas spine_atlas_load(
spine_atlas_result spine_atlas_load(
ffi.Pointer<ffi.Char> atlasData,
) {
return _spine_atlas_load(
@ -6566,10 +6578,10 @@ class SpineDartBindings {
}
late final _spine_atlas_loadPtr =
_lookup<ffi.NativeFunction<spine_atlas Function(ffi.Pointer<ffi.Char>)>>('spine_atlas_load');
late final _spine_atlas_load = _spine_atlas_loadPtr.asFunction<spine_atlas Function(ffi.Pointer<ffi.Char>)>();
_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_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> 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.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');
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)>();
int spine_atlas_get_num_image_paths(
spine_atlas atlas,
ffi.Pointer<ffi.Char> 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<ffi.NativeFunction<ffi.Int32 Function(spine_atlas)>>('spine_atlas_get_num_image_paths');
late final _spine_atlas_get_num_image_paths =
_spine_atlas_get_num_image_pathsPtr.asFunction<int Function(spine_atlas)>();
late final _spine_atlas_result_get_errorPtr =
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(spine_atlas_result)>>('spine_atlas_result_get_error');
late final _spine_atlas_result_get_error =
_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 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<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function(spine_atlas, ffi.Int32)>>('spine_atlas_get_image_path');
late final _spine_atlas_get_image_path =
_spine_atlas_get_image_pathPtr.asFunction<ffi.Pointer<ffi.Char> Function(spine_atlas, int)>();
late final _spine_atlas_result_get_atlasPtr =
_lookup<ffi.NativeFunction<spine_atlas Function(spine_atlas_result)>>('spine_atlas_result_get_atlas');
late final _spine_atlas_result_get_atlas =
_spine_atlas_result_get_atlasPtr.asFunction<spine_atlas Function(spine_atlas_result)>();
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<ffi.NativeFunction<ffi.Bool Function(spine_atlas)>>('spine_atlas_is_pma');
late final _spine_atlas_is_pma = _spine_atlas_is_pmaPtr.asFunction<bool Function(spine_atlas)>();
ffi.Pointer<ffi.Char> spine_atlas_get_error(
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)>();
late final _spine_atlas_result_disposePtr =
_lookup<ffi.NativeFunction<ffi.Void Function(spine_atlas_result)>>('spine_atlas_result_dispose');
late final _spine_atlas_result_dispose =
_spine_atlas_result_disposePtr.asFunction<void Function(spine_atlas_result)>();
/// Skeleton data functions
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_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 {
@ffi.Char()
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_vector = ffi.Pointer<spine_vector_wrapper>;
typedef spine_atlas_result = ffi.Pointer<spine_atlas_result_wrapper>;
/// Texture loader callbacks
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 '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<void> 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<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...');
// 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}');