Mario Zechner cc43fd549b feat(spine-ios): Implement SpineSwift high-level API mirroring spine_dart.dart
- Created SkeletonDrawable class wrapping spine_skeleton_drawable C functions
- Implemented AnimationStateEventManager singleton for event listener management
- Added helper types: Bounds and Vector structs
- Added extensions for Skeleton (bounds, getPosition)
- Added extensions for Skin (getEntries to iterate attachments)
- Added extensions for BonePose (coordinate transformations)
- Added extensions for AnimationState and TrackEntry (event listeners)
- Created skeleton_drawable_test_swift.swift using SpineSwift high-level API
- Updated test Package.swift to include SpineSwift dependency
- SpineSwift module now compiles with 0 errors

This completes the port of the high-level API from spine-flutter's spine_dart.dart
to SpineSwift, providing a clean Swift API that mirrors the Dart implementation.
2025-08-11 20:17:17 +02:00

15 KiB

Generate bindings for Swift from spine-c

Status: InProgress Agent PID: 46440

Original Todo

Generate bindings for Swift from spine-c generate() like dart-writer.ts

Description

Create a TypeScript-based Swift bindings generator that uses the spine-c codegen infrastructure to generate idiomatic Swift code. The generator will replace the existing Python-based generator and produce type-safe Swift wrappers around the spine-c API, similar to how the Dart generator works. It will generate classes with proper inheritance hierarchy, nullability annotations, memory management, and maintain compatibility with the existing Swift API while adding modern Swift features.

Read analysis.md in full for analysis results and context.

Implementation Plan

  • Create the Swift codegen package structure (spine-ios/codegen/)

    • Set up TypeScript project with package.json, tsconfig.json
    • Add dependency on spine-c-codegen package
    • Create src/index.ts as entry point
    • Create src/swift-writer.ts for Swift code generation
  • Implement SwiftWriter class (spine-ios/codegen/src/swift-writer.ts)

    • Use DartWriter as reference for structure but implement Swift-specific patterns
    • Map C types to Swift types (OpaquePointer, String, Bool, etc.)
    • Handle nullability with Swift optionals
    • Generate proper class/protocol inheritance
  • Generate Swift classes from CIR

    • Concrete classes with NSObject inheritance and @objc annotations
    • Abstract classes with proper subclass requirements
    • Protocols for interface types (like Constraint, Update)
    • Proper memory management with deinit methods
  • Generate Swift enums

    • Use Swift enums with raw values for C enums
    • Add fromValue static methods for reverse lookup
    • Generate proper case names from C enum values
  • Generate array wrapper types

    • Create Swift array wrapper classes for each spine_array_* type
    • Implement subscript operators and collection protocols
    • Handle memory ownership and disposal
  • Handle RTTI-based instantiation

    • Generate switch statements for abstract type instantiation
    • Map C++ class names to Swift concrete types
    • Handle unknown types gracefully
  • Generate method implementations

    • Convert C function calls to Swift methods
    • Handle method overloading (numbered C methods)
    • Generate computed properties for getter/setter pairs
    • Add proper null checking and optional unwrapping
  • Create output file structure

    • Generate individual .swift files per type
    • Create Arrays.swift for all array types
    • Create Enums.swift for all enum types
    • Generate main exports file
  • Add build integration

    • Create build script to run the generator
    • Update spine-ios Package.swift to include generated files
  • Create minimal test package (spine-ios/test/) inspired by spine-flutter/test

    • Minimal Package.swift for quick iteration
    • Test harness without full spine-ios dependencies
    • Basic examples to verify generated bindings work
  • Test the generated code

    • Verify compilation with Swift compiler
    • Test against the minimal test package
    • Ensure backward compatibility with existing API
  • User test: Generate Swift bindings and verify they compile and work correctly in the test package

SpineSwift Module Implementation - COMPLETED & TODO

Current Module Structure (COMPLETED)

We successfully restructured the spine-ios modules into three clean layers:

  1. SpineC ( WORKING)

    • Location: spine-ios/Sources/SpineC/
    • Contains: Symlinks to spine-c and spine-cpp
    • Purpose: Compiles C/C++ sources, exposes C API to Swift
    • Status: Fully functional, tests pass
  2. SpineSwift (⚠️ PARTIAL - has Objective-C conflicts)

    • Location: spine-ios/Sources/SpineSwift/
    • Contains:
      • Generated/ - All generated Swift bindings from codegen
      • Extensions/ - Platform-agnostic helper functions
    • Purpose: Swift bindings + high-level API (like spine_dart.dart)
    • Status: Generated but has Objective-C selector conflicts
  3. SpineiOS ( RESTRUCTURED)

    • Location: spine-ios/Sources/SpineiOS/
    • Contains: iOS-specific UI components only
    • Files: SpineUIView, SpineController, SkeletonDrawableWrapper, etc.
    • Purpose: iOS-specific rendering and UI integration
    • Status: Cleaned up, old Generated folder removed

What We Accomplished

  • Created proper module separation (SpineC, SpineSwift, SpineiOS)
  • Set up SpineC with symlinks to spine-c and spine-cpp
  • Updated Package.swift with correct target dependencies
  • Updated codegen to output to SpineSwift/Generated
  • Generated Swift bindings with proper imports
  • Created SpineSwift.swift with basic helper functions (loadAtlas, loadSkeletonData)
  • Removed SpineModule (redundant re-export module)
  • Fixed test package to use SpineC directly
  • Created skeleton_drawable_test.swift ported from Dart
  • Cleaned up test folder structure (removed accidental spine-ios subfolder)
  • Test successfully runs using SpineC module

Known Issues

  1. SpineSwift has Objective-C selector conflicts

    • Methods like copy() conflict with NSObject
    • Constructor init(_:) conflicts in inheritance hierarchies
    • Need to either rename methods or remove @objc annotations
  2. Swift Package Manager limitation

    • Cannot mix C/C++ and Swift in same target
    • Must keep SpineC and SpineSwift as separate modules

Major Swift Codegen Rewrite (Session 2 - COMPLETED)

We completely rewrote the Swift code generator to fix fundamental architectural issues. The rewrite reduced compilation errors from 61,503 to ~17,500.

Key Architectural Decisions

  1. Pointer Management

    • Root classes have public let _ptr: UnsafeMutableRawPointer field
    • Subclasses inherit _ptr from parent, no redeclaration
    • All C function calls cast _ptr inline: _ptr.assumingMemoryBound(to: spine_xxx_wrapper.self)
    • No computed properties, no nativePtr, no ptr - just _ptr
  2. Initialization Pattern

    // Root class
    public init(fromPointer ptr: spine_timeline) {
        self._ptr = UnsafeMutableRawPointer(ptr)
    }
    
    // Subclass - cast to parent type
    public init(fromPointer ptr: spine_alpha_timeline) {
        super.init(fromPointer: UnsafeMutableRawPointer(ptr).assumingMemoryBound(to: spine_curve_timeline1_wrapper.self))
    }
    
  3. Single Inheritance + Protocols

    • Swift doesn't support multiple inheritance
    • Use single base class + protocol conformance (like Dart's mixins)
    • Example: class AlphaTimeline: CurveTimeline1, SlotTimeline
    • Protocols don't override properties - each class provides its own implementation
  4. Property Synthesis

    • Merged getter/setter pairs into single properties
    • Fixed duplicate property declarations in protocols
    • Handled Swift reserved words with backticks
  5. Type Conversions

    • Objects passed as parameters: obj._ptr.assumingMemoryBound(to: spine_xxx_wrapper.self)
    • Enums: spine_mix_blend(rawValue: UInt32(swiftEnum.rawValue))
    • size_t parameters: Int(value)
    • Return values: Cast C pointers back to Swift wrapper types
  6. No Objective-C Integration

    • Removed all @objc annotations
    • Removed NSObject inheritance
    • Fixed method name conflicts (copy, etc.)

What Works Now

  • AlphaTimeline compiles without errors
  • Proper inheritance hierarchy established
  • Pointer management is clean and consistent
  • Protocol conformance working correctly
  • Enum conversions functional

Remaining Issues (~17,500 errors)

  • Casting between related pointer types in polymorphic scenarios
  • Some enum return type conversions
  • Array type handling in some edge cases
  • RTTI-based instantiation needs refinement

Progress Summary (Session 4)

Compilation Error Reduction

  • Starting errors: ~17,500
  • Session 3 errors: ~9,720
  • Session 4 errors: ~3,780
  • Final SpineSwift errors: 0
  • Total reduction: 100% for SpineSwift module!

The SpineSwift module now compiles successfully! Remaining 27 errors are in SpineiOS which requires iOS SDK (UIKit).

Fixes Applied

  1. Protocol conformance issues

    • Modified data property in constraint classes to return protocol type for proper conformance
    • Added special handling for covariant return types in Swift protocols
  2. RTTI-based instantiation

    • Fixed pointer casting using UnsafeMutableRawPointer for concrete type instantiation
    • Updated all RTTI switch statements to properly cast abstract pointers to concrete types
  3. Enum value parsing

    • Fixed parsing of bit-shifted enum values (e.g., 1 << 0, 1 << 1)
    • Property enum now has correct values (1, 2, 4, 8, etc.) instead of all being 1
  4. Int32 vs Int conversions

    • Properly mapped size_t to Int instead of Int32
    • Fixed ~1,666 type conversion errors
  5. Array pointer conversions

    • Added assumingMemoryBound casting for all array operations
    • Fixed ~2,400 array-related compilation errors
    • Updated array methods to use proper Int types for indices and counts
  6. Enum return type conversions

    • Fixed conversion from C enum types to Swift enums
    • Added proper .rawValue extraction when needed
  7. Protocol _ptr declaration

    • Added var _ptr: UnsafeMutableRawPointer { get } to protocols
    • Enables polymorphic usage of conforming types in arrays and other contexts
    • Fixed ~31 errors related to protocol usage
  8. Improved constraint data handling

    • Extended fix to handle all constraint types (including Slider)
    • Properly returns protocol type ConstraintData for all constraint implementations
    • Fixed remaining protocol conformance issues
  9. Array wrapper fixes (Session 3)

    • Fixed all array method calls to use assumingMemoryBound(to: spine_array_XXX_wrapper.self)
    • Changed count and length properties to return Int instead of Int32
    • Fixed subscript and removeAt to accept Int index parameter
    • Proper Int32 conversion for C function calls
    • Fixed ~5,140 array-related compilation errors
  10. Array buffer type fixes (Session 4)

  • Removed unnecessary assumingMemoryBound for primitive array buffers (Float, Int32, UInt16, Int64)
  • Fixed object array buffer access - already correct pointer type
  • Fixed ~644 buffer-related errors
  1. size_t parameter fixes (Session 4)
  • Corrected all array methods to use Int (size_t) instead of Int32 for indices and sizes
  • Fixed capacity, removeAt, setSize, and ensureCapacity method parameters
  • Resolved final ~3,000 type conversion errors
  1. Module import fixes (Session 4)
  • Updated SpineiOS to import SpineSwift instead of old Spine module
  • Fixed SpineC imports for Metal renderer

COMPLETED

  • SpineSwift module compiles without errors!
  • Successfully generated Swift bindings from spine-c
  • All type conversions and memory management working correctly

TODO - Next Steps

  • Investigate how SkeletonDrawable is implemented in spine-flutter
    • Check spine_flutter/lib/spine_dart.dart for extensions
    • Understand how SkeletonDrawable is exposed in Dart
    • Check if it's manually implemented or generated

Investigation Results - spine_dart.dart Structure

The Dart implementation has these manually-written high-level components in spine_dart.dart:

  1. SkeletonDrawable class (lines 409-492)

    • Wraps spine_skeleton_drawable C functions
    • Combines skeleton, animation state, and rendering
    • Handles update cycle and event processing
    • Methods: update(delta), render(), dispose()
  2. AnimationStateEventManager (lines 232-305)

    • Singleton for managing animation event listeners
    • Maps native pointer addresses to listeners
    • Handles both state-level and track-entry-level listeners
  3. Helper classes:

    • Bounds (lines 327-339): AABB with x, y, width, height
    • Vector (lines 341-346): 2D vector with x, y
    • SkinEntry (lines 155-162): Slot index and attachment pair
  4. Extensions:

    • SkinExtensions (lines 164-229): getEntries() method
    • AnimationStateListeners (lines 308-318): listener property setter/getter
    • TrackEntryExtensions (lines 319-324): listener property setter/getter
    • SkeletonExtensions (lines 349-371): bounds property, getPosition()
    • BonePoseExtensions (lines 373-406): worldToLocal, localToWorld coordinate transforms
  5. Top-level functions:

    • loadAtlas(String atlasData)
    • loadSkeletonData(Atlas atlas, String jsonData)
    • loadSkeletonDataBinary(Atlas atlas, Uint8List binaryData)

COMPLETED - SpineSwift High-Level API Implementation

  • Port SkeletonDrawable and extensions to SpineSwift

    • Created SkeletonDrawable class wrapping spine_skeleton_drawable functions
    • Implemented AnimationStateEventManager singleton
    • Added Bounds and Vector helper structs
    • Added extensions for Skeleton, Skin, AnimationState, TrackEntry, BonePose
    • Mirrored the update/render cycle from Dart implementation
  • Complete SpineSwift high-level API (port from spine_dart.dart)

    • SkeletonDrawable class (Extensions/SkeletonDrawable.swift)
    • AnimationStateEventManager (Extensions/AnimationStateEventManager.swift)
    • Bounds and Vector types (Extensions/Types.swift)
    • Skin extensions - getEntries (Extensions/SkinExtensions.swift)
    • BonePose coordinate transformations (Extensions/BonePoseExtensions.swift)
    • Animation state listener management (Extensions/AnimationStateEventManager.swift)
    • Skeleton extensions - bounds, getPosition (Extensions/SkeletonExtensions.swift)
  • Create skeleton_drawable_test_swift to use SpineSwift API

    • Created test that uses the high-level SpineSwift API
    • Tests loading atlas and skeleton data with Swift API
    • Tests SkeletonDrawable update/render cycle
    • Tests animation state listeners and events
    • Tests bounds and position tracking
    • Tests skin entries iteration

Files Created (Session 5)

  • spine-ios/Sources/SpineSwift/Extensions/SkeletonDrawable.swift
  • spine-ios/Sources/SpineSwift/Extensions/AnimationStateEventManager.swift
  • spine-ios/Sources/SpineSwift/Extensions/Types.swift
  • spine-ios/Sources/SpineSwift/Extensions/SkeletonExtensions.swift
  • spine-ios/Sources/SpineSwift/Extensions/SkinExtensions.swift
  • spine-ios/Sources/SpineSwift/Extensions/BonePoseExtensions.swift
  • spine-ios/test/src/skeleton_drawable_test_swift.swift
  • spine-ios/test/src/main.swift

Build Status

  • SpineSwift module compiles successfully with 0 errors
  • Test package builds and runs successfully
  • C API test passes all checks
  • SpineSwift API test implementation complete (may have runtime issues to debug)

File Locations Reference

  • Codegen: spine-ios/codegen/src/swift-writer.ts
  • Generated output: spine-ios/Sources/SpineSwift/Generated/
  • Test package: spine-ios/test/
  • Test source: spine-ios/test/src/skeleton_drawable_test.swift

Important Notes

  • The test currently imports SpineC directly and works perfectly
  • VS Code may show "no such module SpineC" but builds work fine (IDE issue)
  • Generated files are in SpineSwift, NOT in SpineiOS
  • SpineiOS should only contain iOS-specific UI components
  • The symlinks in SpineC point to ../../../spine-c and ../../../spine-cpp