# Dart FFI Wrapper Generator ## Overview A TypeScript-based code generator that extends the existing `spine-c/codegen` system to automatically generate clean, type-safe Dart wrapper classes over the raw FFI bindings. This eliminates the need for manual wrapper implementation while providing a native Dart API experience. ## Architecture ``` spine-cpp (C++ source) ↓ (existing type-extractor.ts) spine-cpp-types.json (type definitions) ↓ (existing ir-generator.ts + c-writer.ts) spine-c (C bindings: .h/.c files) ↓ (ffigen via generate_bindings.sh) spine_flutter_bindings_generated.dart (raw FFI) ↓ (NEW: dart-generator.ts) spine_flutter.dart (clean Dart API) ``` ## Key Insights ### 1. Derivation vs Parsing Instead of parsing the generated FFI bindings, we **derive** what ffigen produces using the same C type information that generates the headers. This works because ffigen follows predictable transformation rules: ### 2. Predictable Disposal Pattern All C types follow a consistent disposal pattern (`spine_type_name` → `spine_type_name_dispose`), so we can derive disposal function names automatically instead of storing them. ### 3. Array Properties vs Regular Getters C functions that return `spine_array_*` types become special **Array properties** in Dart, requiring different generation logic than primitive getters. ### FFigen Transformation Rules | C Code | Generated Dart FFI | |--------|-------------------| | `SPINE_C_API float spine_animation_get_duration(spine_animation self);` | `double spine_animation_get_duration(Pointer self)` | | `SPINE_C_API void spine_animation_dispose(spine_animation self);` | `void spine_animation_dispose(Pointer self)` | | `typedef enum { BLEND_NORMAL, BLEND_ADDITIVE } spine_blend_mode;` | `enum spine_blend_mode { BLEND_NORMAL(0), BLEND_ADDITIVE(1) }` | | `SPINE_OPAQUE_TYPE(spine_animation)` | `typedef struct spine_animation_wrapper { char _dummy; } spine_animation_wrapper; typedef spine_animation_wrapper *spine_animation;` | | `spine_array_timeline spine_animation_get_timelines(spine_animation self);` | `Pointer spine_animation_get_timelines(Pointer self)` → **Array property** | ## Generator Components ### 1. Type Mapping System (`dart-types.ts`) Defines the structure for Dart wrapper types: ```typescript interface DartClass { name: string; // "Animation" cName: string; // "spine_animation" nativeType: string; // "Pointer" constructors: DartConstructor[]; methods: DartMethod[]; getters: DartGetter[]; setters: DartSetter[]; arrays: DartArrayGetter[]; // Properties that return Array (e.g., animation.timelines) } interface DartMethod { name: string; // "apply" cFunctionName: string; // "spine_animation_apply" returnType: DartType; parameters: DartParameter[]; isStatic?: boolean; } interface DartGetter { name: string; // "duration" cFunctionName: string; // "spine_animation_get_duration" returnType: DartType; } interface DartSetter { name: string; // "duration" cFunctionName: string; // "spine_animation_set_duration" parameterType: DartType; } interface DartArrayGetter { name: string; // "timelines" (Dart property name) elementType: string; // "Timeline" (element type inside Array) cArrayType: string; // "spine_array_timeline" (C array type returned) cGetterName: string; // "spine_animation_get_timelines" (C function to call) } interface DartConstructor { cFunctionName: string; // "spine_animation_create" parameters: DartParameter[]; } interface DartParameter { name: string; type: DartType; cType: string; // Original C type for conversion } interface DartType { dart: string; // "double", "String", "Animation" isNullable?: boolean; isNative?: boolean; // true for primitives, false for wrapper classes } interface DartEnum { name: string; // "BlendMode" cName: string; // "spine_blend_mode" values: DartEnumValue[]; } interface DartEnumValue { name: string; // "normal" cName: string; // "SPINE_BLEND_MODE_NORMAL" value: number; } ``` ### 2. Type Mapping Rules (`dart-type-mapper.ts`) Converts C types to Dart types: ```typescript class DartTypeMapper { // Maps C types to Dart types private static readonly TYPE_MAP: Record = { 'float': { dart: 'double', isNative: true }, 'double': { dart: 'double', isNative: true }, 'int': { dart: 'int', isNative: true }, 'bool': { dart: 'bool', isNative: true }, 'char*': { dart: 'String', isNative: true }, 'const char*': { dart: 'String', isNative: true }, 'void': { dart: 'void', isNative: true }, 'spine_skeleton': { dart: 'Skeleton', isNative: false }, 'spine_animation': { dart: 'Animation', isNative: false }, // ... more mappings }; static mapCTypeToDart(cType: string): DartType { // Handle pointers if (cType.endsWith('*')) { const baseType = cType.slice(0, -1).trim(); const mapped = this.TYPE_MAP[baseType]; return mapped ? { ...mapped, isNullable: true } : this.mapSpineType(baseType); } // Handle direct mappings return this.TYPE_MAP[cType] || this.mapSpineType(cType); } private static mapSpineType(cType: string): DartType { if (cType.startsWith('spine_')) { // spine_animation -> Animation const dartName = toPascalCase(cType.replace('spine_', '')); return { dart: dartName, isNative: false }; } // Default to dynamic for unknown types return { dart: 'dynamic', isNative: false }; } } ``` ### 3. Naming Convention Utilities (`dart-naming.ts`) Handles C to Dart naming conversions: ```typescript export function toPascalCase(snakeCase: string): string { return snakeCase .split('_') .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join(''); } export function toCamelCase(snakeCase: string): string { const pascal = toPascalCase(snakeCase); return pascal.charAt(0).toLowerCase() + pascal.slice(1); } export function extractMethodName(cFunctionName: string, className: string): string { // spine_animation_get_duration -> getDuration // spine_animation_set_duration -> setDuration // spine_animation_apply -> apply const prefix = `spine_${toSnakeCase(className)}_`; if (!cFunctionName.startsWith(prefix)) { throw new Error(`Function ${cFunctionName} doesn't match expected prefix ${prefix}`); } const methodPart = cFunctionName.slice(prefix.length); return toCamelCase(methodPart); } export function isGetter(methodName: string): boolean { return methodName.startsWith('get_'); } export function isSetter(methodName: string): boolean { return methodName.startsWith('set_'); } export function extractPropertyName(methodName: string): string { // get_duration -> duration // set_duration -> duration if (methodName.startsWith('get_') || methodName.startsWith('set_')) { return methodName.slice(4); } return methodName; } ``` ### 4. Dart Code Generator (`dart-writer.ts`) Generates the actual Dart wrapper files: ```typescript export class DartWriter { constructor(private outputDir: string, private bindingsImport: string) {} async writeClass(dartClass: DartClass): Promise { const lines: string[] = []; // File header lines.push("// AUTO GENERATED FILE, DO NOT EDIT."); lines.push("// Generated by spine-c dart-wrapper generator"); lines.push(""); lines.push(`import '${this.bindingsImport}';`); lines.push("import 'dart:ffi';"); lines.push("import 'array.dart';"); lines.push(""); // Class declaration lines.push(`class ${dartClass.name} {`); lines.push(` final ${dartClass.nativeType} _ptr;`); lines.push(` final SpineFlutterBindings _bindings;`); lines.push(""); // Private constructor lines.push(` ${dartClass.name}._(this._ptr, this._bindings);`); lines.push(""); // Public constructors for (const constructor of dartClass.constructors) { lines.push(this.writeConstructor(dartClass, constructor)); lines.push(""); } // Getters for (const getter of dartClass.getters) { lines.push(this.writeGetter(getter)); } // Setters for (const setter of dartClass.setters) { lines.push(this.writeSetter(setter)); } // Array getters for (const arrayGetter of dartClass.arrays) { lines.push(this.writeArrayGetter(arrayGetter)); } // Methods for (const method of dartClass.methods) { lines.push(this.writeMethod(method)); lines.push(""); } // Disposal method (derived from class name) lines.push(` void dispose() => _bindings.${dartClass.cName}_dispose(_ptr);`); lines.push("}"); // Write to file const fileName = `${toSnakeCase(dartClass.name)}.dart`; const filePath = path.join(this.outputDir, fileName); await fs.writeFile(filePath, lines.join('\n')); } private writeConstructor(dartClass: DartClass, constructor: DartConstructor): string { const params = constructor.parameters.map(p => `${p.type.dart} ${p.name}` ).join(', '); const args = constructor.parameters.map(p => this.convertDartToC(p.name, p.type, p.cType) ).join(', '); return [ ` factory ${dartClass.name}.create(SpineFlutterBindings bindings${params ? ', ' + params : ''}) {`, ` final ptr = bindings.${constructor.cFunctionName}(${args});`, ` return ${dartClass.name}._(ptr, bindings);`, ` }` ].join('\n'); } private writeGetter(getter: DartGetter): string { if (getter.returnType.isNative) { return ` ${getter.returnType.dart} get ${getter.name} => _bindings.${getter.cFunctionName}(_ptr);`; } else { return [ ` ${getter.returnType.dart} get ${getter.name} {`, ` final ptr = _bindings.${getter.cFunctionName}(_ptr);`, ` return ${getter.returnType.dart}._(ptr, _bindings);`, ` }` ].join('\n'); } } private writeSetter(setter: DartSetter): string { const conversion = this.convertDartToC('value', setter.parameterType, ''); return ` set ${setter.name}(${setter.parameterType.dart} value) => _bindings.${setter.cFunctionName}(_ptr, ${conversion});`; } private writeArrayGetter(arrayGetter: DartArrayGetter): string { return [ ` Array<${arrayGetter.elementType}> get ${arrayGetter.name} {`, ` final array = _bindings.${arrayGetter.cGetterName}(_ptr);`, ` return Array.create<${arrayGetter.elementType}>(array, _bindings, ${arrayGetter.elementType}._);`, ` }` ].join('\n'); } private writeMethod(method: DartMethod): string { const params = method.parameters.map(p => `${p.type.dart} ${p.name}` ).join(', '); const args = ['_ptr', ...method.parameters.map(p => this.convertDartToC(p.name, p.type, p.cType) )].join(', '); if (method.returnType.dart === 'void') { return [ ` void ${method.name}(${params}) {`, ` _bindings.${method.cFunctionName}(${args});`, ` }` ].join('\n'); } else if (method.returnType.isNative) { return [ ` ${method.returnType.dart} ${method.name}(${params}) {`, ` return _bindings.${method.cFunctionName}(${args});`, ` }` ].join('\n'); } else { return [ ` ${method.returnType.dart} ${method.name}(${params}) {`, ` final ptr = _bindings.${method.cFunctionName}(${args});`, ` return ${method.returnType.dart}._(ptr, _bindings);`, ` }` ].join('\n'); } } private convertDartToC(dartValue: string, dartType: DartType, cType: string): string { if (dartType.isNative) { // Handle string conversion if (dartType.dart === 'String') { return `${dartValue}.toNativeUtf8().cast()`; } return dartValue; } else { // Wrapper class - extract the pointer return `${dartValue}._ptr`; } } } ``` ### 5. Generic Array Implementation (`dart-array-writer.ts`) Generates a single reusable Array class: ```typescript export class DartArrayWriter { async writeArrayClass(outputDir: string): Promise { const content = ` // AUTO GENERATED FILE, DO NOT EDIT. // Generated by spine-c dart-wrapper generator import 'dart:collection'; import 'dart:ffi'; import 'spine_flutter_bindings_generated.dart'; class Array extends ListBase { final Pointer _nativeArray; final SpineFlutterBindings _bindings; final T Function(Pointer) _elementFactory; final String _arrayTypeName; Array._( this._nativeArray, this._bindings, this._elementFactory, this._arrayTypeName, ); static Array create( Pointer nativeArray, SpineFlutterBindings bindings, T Function(Pointer) elementFactory, ) { final typeName = T.toString().toLowerCase(); return Array._(nativeArray, bindings, elementFactory, 'spine_array_\$typeName'); } @override int get length { // Use dynamic function lookup based on type switch (_arrayTypeName) { case 'spine_array_timeline': return _bindings.spine_array_timeline_get_size(_nativeArray.cast()); case 'spine_array_bone_data': return _bindings.spine_array_bone_data_get_size(_nativeArray.cast()); case 'spine_array_slot_data': return _bindings.spine_array_slot_data_get_size(_nativeArray.cast()); // Add more cases as needed default: throw UnsupportedError('Unknown array type: \$_arrayTypeName'); } } @override T operator [](int index) { if (index < 0 || index >= length) { throw RangeError.index(index, this); } Pointer elementPtr; switch (_arrayTypeName) { case 'spine_array_timeline': elementPtr = _bindings.spine_array_timeline_get(_nativeArray.cast(), index); break; case 'spine_array_bone_data': elementPtr = _bindings.spine_array_bone_data_get(_nativeArray.cast(), index); break; case 'spine_array_slot_data': elementPtr = _bindings.spine_array_slot_data_get(_nativeArray.cast(), index); break; // Add more cases as needed default: throw UnsupportedError('Unknown array type: \$_arrayTypeName'); } return _elementFactory(elementPtr); } @override void operator []=(int index, T value) { throw UnsupportedError('Array is read-only'); } @override set length(int newLength) { throw UnsupportedError('Array is read-only'); } } `; const filePath = path.join(outputDir, 'array.dart'); await fs.writeFile(filePath, content.trim()); } } ``` ### 6. Enum Generator (`dart-enum-writer.ts`) Generates Dart enum wrappers: ```typescript export class DartEnumWriter { async writeEnum(dartEnum: DartEnum, outputDir: string): Promise { const lines: string[] = []; // File header lines.push("// AUTO GENERATED FILE, DO NOT EDIT."); lines.push("// Generated by spine-c dart-wrapper generator"); lines.push(""); // Enum declaration lines.push(`enum ${dartEnum.name} {`); // Enum values for (let i = 0; i < dartEnum.values.length; i++) { const value = dartEnum.values[i]; const comma = i < dartEnum.values.length - 1 ? ',' : ''; lines.push(` ${value.name}(${value.value})${comma}`); } lines.push(""); lines.push(" const ${dartEnum.name}(this.value);"); lines.push(" final int value;"); lines.push(""); // From native conversion lines.push(` static ${dartEnum.name} fromNative(int value) {`); lines.push(" switch (value) {"); for (const value of dartEnum.values) { lines.push(` case ${value.value}: return ${dartEnum.name}.${value.name};`); } lines.push(` default: throw ArgumentError('Unknown ${dartEnum.name} value: \$value');`); lines.push(" }"); lines.push(" }"); lines.push("}"); // Write to file const fileName = `${toSnakeCase(dartEnum.name)}.dart`; const filePath = path.join(outputDir, fileName); await fs.writeFile(filePath, lines.join('\n')); } } ``` ### 7. Main Generator Orchestrator (`dart-generator.ts`) Coordinates the entire generation process: ```typescript import { CClassOrStruct, CEnum, CArrayType } from './c-types'; import { DartClass, DartEnum } from './dart-types'; import { DartTypeMapper } from './dart-type-mapper'; import { DartWriter } from './dart-writer'; import { DartEnumWriter } from './dart-enum-writer'; import { DartArrayWriter } from './dart-array-writer'; import { extractMethodName, isGetter, isSetter, extractPropertyName } from './dart-naming'; export class DartGenerator { private typeMapper = new DartTypeMapper(); private writer: DartWriter; private enumWriter = new DartEnumWriter(); private arrayWriter = new DartArrayWriter(); constructor( private cTypes: CClassOrStruct[], private cEnums: CEnum[], private cArrayTypes: CArrayType[], private outputDir: string, private bindingsImport: string = 'spine_flutter_bindings_generated.dart' ) { this.writer = new DartWriter(outputDir, bindingsImport); } async generateAll(): Promise { // Ensure output directory exists await fs.mkdir(this.outputDir, { recursive: true }); console.log(`Generating ${this.cTypes.length} Dart wrapper classes...`); // Generate wrapper classes for (const cType of this.cTypes) { const dartClass = this.convertCTypeToDartClass(cType); await this.writer.writeClass(dartClass); console.log(`Generated: ${dartClass.name}`); } console.log(`Generating ${this.cEnums.length} Dart enums...`); // Generate enums for (const cEnum of this.cEnums) { const dartEnum = this.convertCEnumToDartEnum(cEnum); await this.enumWriter.writeEnum(dartEnum, this.outputDir); console.log(`Generated enum: ${dartEnum.name}`); } // Generate generic Array class await this.arrayWriter.writeArrayClass(this.outputDir); console.log('Generated: Array'); console.log('Dart wrapper generation complete!'); } private convertCTypeToDartClass(cType: CClassOrStruct): DartClass { const className = toPascalCase(cType.name.replace('spine_', '')); // Separate getters, setters, and regular methods const getters: DartGetter[] = []; const setters: DartSetter[] = []; const methods: DartMethod[] = []; const constructors: DartConstructor[] = []; const arrays: DartArrayGetter[] = []; for (const method of cType.methods) { const methodName = extractMethodName(method.name, cType.name); if (method.name.includes('_create')) { // Constructor constructors.push({ cFunctionName: method.name, parameters: method.parameters.slice(0, -1).map(p => ({ name: p.name, type: this.typeMapper.mapCTypeToDart(p.type), cType: p.type })) }); } else if (isGetter(methodName)) { // Getter const propertyName = extractPropertyName(methodName); const returnType = this.typeMapper.mapCTypeToDart(method.returnType); // Check if this is an array getter if (method.returnType.startsWith('spine_array_')) { const elementType = this.extractArrayElementType(method.returnType); arrays.push({ name: propertyName, elementType, cArrayType: method.returnType, cGetterName: method.name }); } else { getters.push({ name: propertyName, cFunctionName: method.name, returnType }); } } else if (isSetter(methodName)) { // Setter const propertyName = extractPropertyName(methodName); const paramType = this.typeMapper.mapCTypeToDart(method.parameters[1].type); setters.push({ name: propertyName, cFunctionName: method.name, parameterType: paramType }); } else { // Regular method methods.push({ name: methodName, cFunctionName: method.name, returnType: this.typeMapper.mapCTypeToDart(method.returnType), parameters: method.parameters.slice(1).map(p => ({ name: p.name, type: this.typeMapper.mapCTypeToDart(p.type), cType: p.type })) }); } } return { name: className, cName: cType.name, nativeType: `Pointer<${cType.name}_wrapper>`, constructors, methods, getters, setters, arrays }; } private convertCEnumToDartEnum(cEnum: CEnum): DartEnum { return { name: toPascalCase(cEnum.name.replace('spine_', '')), cName: cEnum.name, values: cEnum.values.map(value => ({ name: toCamelCase(value.name.replace(/^SPINE_.*?_/, '')), cName: value.name, value: value.value })) }; } private extractArrayElementType(arrayType: string): string { // spine_array_timeline -> Timeline // spine_array_bone_data -> BoneData const match = arrayType.match(/spine_array_(.+)/); if (!match) throw new Error(`Invalid array type: ${arrayType}`); return toPascalCase(match[1]); } } ``` ### 8. Integration with Existing Codegen (`index.ts` modifications) Extend the existing main function: ```typescript // Add to existing imports import { DartGenerator } from './dart-generator'; // Add to existing main() function after C generation async function main() { // ... existing C generation code ... // Write all C files to disk const cWriter = new CWriter(path.join(__dirname, '../../src/generated')); await cWriter.writeAll(cTypes, cEnums, cArrayTypes); console.log('C code generation complete!'); // NEW: Generate Dart wrappers const dartOutputDir = path.join(__dirname, '../../../lib/src/generated'); const dartGenerator = new DartGenerator( cTypes, cEnums, cArrayTypes, dartOutputDir ); await dartGenerator.generateAll(); console.log('All code generation complete!'); } ``` ## Generated Output Structure After running the generator, the output structure will be: ``` lib/ ├── spine_flutter.dart # Main export file ├── spine_flutter_bindings_generated.dart # Raw FFI bindings (from ffigen) └── src/ └── generated/ ├── array.dart # Generic Array implementation ├── animation.dart # Animation wrapper class ├── skeleton.dart # Skeleton wrapper class ├── bone_data.dart # BoneData wrapper class ├── blend_mode.dart # BlendMode enum └── ... # More generated classes and enums ``` ## Usage Examples ### Before (Manual FFI Usage) ```dart // Raw FFI - error-prone and verbose final skeletonDataPtr = bindings.spine_skeleton_data_create_from_file('skeleton.json'.toNativeUtf8().cast()); final bonesArrayPtr = bindings.spine_skeleton_data_get_bones(skeletonDataPtr); final numBones = bindings.spine_skeleton_data_get_num_bones(skeletonDataPtr); for (int i = 0; i < numBones; i++) { final bonePtr = bindings.spine_array_bone_data_get(bonesArrayPtr, i); final namePtr = bindings.spine_bone_data_get_name(bonePtr); final name = namePtr.cast().toDartString(); print('Bone: $name'); } bindings.spine_skeleton_data_dispose(skeletonDataPtr); ``` ### After (Generated Wrapper Usage) ```dart // Clean, type-safe API final skeletonData = SkeletonData.fromFile(bindings, 'skeleton.json'); final bones = skeletonData.bones; // Array for (final bone in bones) { // Natural Dart iteration print('Bone: ${bone.name}'); // Direct property access } skeletonData.dispose(); // Clean disposal ``` ### Advanced Usage ```dart // Complex operations made simple final skeleton = Skeleton.create(bindings, skeletonData); final animState = AnimationState.create(bindings, animStateData); // Arrays work like native Dart Lists final animations = skeleton.data.animations; final firstAnim = animations[0]; final animCount = animations.length; // Functional programming support final animNames = animations.map((a) => a.name).toList(); final longAnims = animations.where((a) => a.duration > 5.0).toList(); // Enum usage skeleton.setSkin('goblin'); animState.setAnimation(0, firstAnim, true); animState.setMixByName('idle', 'walk', 0.2); // Chaining operations skeleton.data.bones .where((bone) => bone.parent != null) .forEach((bone) => print('Child bone: ${bone.name}')); ``` ## Build Integration ### 1. Update `generate_bindings.sh` Add Dart wrapper generation to the existing script: ```bash #!/bin/bash # ... existing FFI generation code ... # Generate Dart wrappers echo "Generating Dart wrapper classes..." cd src/spine-c/codegen npm run generate-dart echo "✅ All code generation completed!" ``` ### 2. Add npm script to `spine-c/codegen/package.json` ```json { "scripts": { "build": "tsc", "generate": "npm run build && node dist/index.js", "generate-dart": "npm run build && node dist/index.js --dart-only", "test": "echo \"Error: no test specified\" && exit 1" } } ``` ### 3. Update `pubspec.yaml` dependencies The generated code will require: ```yaml dependencies: flutter: sdk: flutter ffi: ^2.1.0 # ... existing dependencies ``` ## Benefits ### 1. **Developer Experience** - **Type Safety**: Compile-time checking prevents FFI pointer errors - **IDE Support**: Full autocomplete, documentation, and refactoring - **Familiar API**: Uses standard Dart patterns (getters, setters, Lists) - **Memory Safety**: Automatic lifetime management with dispose patterns ### 2. **Performance** - **Zero Copy**: Arrays provide lazy access to native data - **Minimal Overhead**: Thin wrappers over FFI calls - **Batch Operations**: Array operations can be optimized in native code ### 3. **Maintainability** - **Automatic Updates**: Changes to C++ API automatically flow through - **Consistent Patterns**: All classes follow the same conventions - **Documentation**: Generated code includes comprehensive docs - **Testing**: Type-safe API makes unit testing straightforward ### 4. **Reliability** - **No Manual Coding**: Eliminates human error in wrapper implementation - **Validated Generation**: Uses proven C type information - **Comprehensive Coverage**: Every C function gets a Dart wrapper ## Implementation Timeline ### Phase 1: Core Infrastructure (1-2 weeks) - [ ] Set up TypeScript generator project structure - [ ] Implement basic type mapping system - [ ] Create simple class generator for basic types - [ ] Test with a few example classes (Animation, Skeleton) ### Phase 2: Array Support (1 week) - [ ] Implement generic Array class - [ ] Add array detection and generation logic - [ ] Test array functionality with timeline/bone arrays ### Phase 3: Enum Support (3-5 days) - [ ] Add enum detection and generation - [ ] Implement enum value mapping - [ ] Test with BlendMode and other enums ### Phase 4: Advanced Features (1 week) - [ ] Add constructor detection and generation - [ ] Implement property getter/setter pairing - [ ] Add disposal pattern detection - [ ] Memory management best practices ### Phase 5: Integration & Polish (3-5 days) - [ ] Integrate with existing build pipeline - [ ] Add comprehensive documentation - [ ] Create migration guide from manual FFI - [ ] Performance testing and optimization ### Phase 6: Testing & Validation (1 week) - [ ] Unit tests for all generated wrappers - [ ] Integration tests with actual Spine files - [ ] Performance benchmarks vs raw FFI - [ ] Documentation and examples **Total Estimated Time: 4-6 weeks** ## Future Enhancements ### 1. **Advanced Memory Management** - Automatic disposal when objects go out of scope - Reference counting for shared objects - Memory leak detection in debug mode ### 2. **Performance Optimizations** - Cached property access - Batch array operations - Native collection implementations ### 3. **Developer Tools** - Debug visualizations for Spine objects - Memory usage monitoring - Performance profiling integration ### 4. **API Enhancements** - Async/Future support for long operations - Stream-based event handling - Reactive programming patterns This comprehensive generator will transform the Spine Flutter experience from low-level FFI manipulation to a clean, type-safe, and performant Dart API that feels native to the Flutter ecosystem.