2025-07-24 00:53:47 +02:00

911 lines
28 KiB
Markdown

# 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<T> 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<spine_animation_wrapper> self)` |
| `SPINE_C_API void spine_animation_dispose(spine_animation self);` | `void spine_animation_dispose(Pointer<spine_animation_wrapper> 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_array_timeline_wrapper> spine_animation_get_timelines(Pointer<spine_animation_wrapper> self)`**Array<Timeline> 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<spine_animation_wrapper>"
constructors: DartConstructor[];
methods: DartMethod[];
getters: DartGetter[];
setters: DartSetter[];
arrays: DartArrayGetter[]; // Properties that return Array<T> (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<T>)
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<string, DartType> = {
'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<void> {
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<Char>()`;
}
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<void> {
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<T> extends ListBase<T> {
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<T> create<T>(
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<void> {
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<void> {
// 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<T>');
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<T> 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<Utf8>().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<BoneData>
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<T> 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.