Mario Zechner ecbd700af4 Restructure spine-ios modules: SpineC, SpineSwift, SpineiOS
- Created clean module separation:
  - SpineC: C/C++ compilation only (working)
  - SpineSwift: Generated Swift bindings + platform-agnostic API
  - SpineiOS: iOS-specific UI components only
- Updated Package.swift with proper target structure
- Moved generated Swift files to SpineSwift/Generated
- Removed redundant SpineModule
- Created test with skeleton_drawable_test.swift ported from Dart
- Test successfully runs using SpineC module directly
- Note: SpineSwift has Objective-C selector conflicts to be fixed
2025-08-11 19:07:29 +02:00

508 lines
17 KiB
Markdown

# Swift Bindings Generator Analysis
## spine-c Codegen System Analysis
### Purpose and Architecture of the spine-c Codegen System
The spine-c codegen system is a sophisticated TypeScript-based code generator that automatically creates a complete C wrapper API for the Spine C++ runtime. Its primary purpose is to:
1. **Automatic API Wrapping**: Parse spine-cpp headers using Clang's AST and generate a systematic C API with opaque types
2. **Multi-language Binding Support**: Build inheritance maps and interface information to support binding generation for languages like Dart, Swift, Java, and others
3. **Type Safety and Consistency**: Apply systematic type conversion rules and extensive validation to ensure correctness
#### Architecture Overview
The system follows a **multi-stage pipeline architecture**:
1. **Type Extraction** - Uses Clang's `-ast-dump=json` to parse C++ headers
2. **Type Processing** - Filters and validates extracted types
3. **Validation** - Extensive checks for conflicts and unsupported patterns
4. **Array Scanning** - Detects and generates specialized array types
5. **IR Generation** - Converts C++ types to C intermediate representation
6. **Code Writing** - Generates header and implementation files
7. **Inheritance Analysis** - Builds inheritance maps for multi-language bindings
### Structure of Types Available from generate()
The `generate()` method returns a comprehensive data structure containing:
```typescript
{
cTypes, // Generated C wrapper types for classes
cEnums, // Generated C enum types
cArrayTypes, // Specialized array types (Array<T> → spine_array_T)
inheritance, // extends/implements map for single-inheritance languages
supertypes, // Legacy RTTI supertypes map
subtypes, // Legacy RTTI subtypes map
isInterface // Pure interface detection map
}
```
#### Key Type Definitions
From `types.ts`, the system defines several core interfaces:
- **ClassOrStruct**: Represents C++ classes/structs with members, inheritance, template info
- **Method**: Method definitions with parameters, virtuality, const-ness
- **Field**: Public fields with type and location information
- **Constructor/Destructor**: Special member functions
- **Enum**: Enumeration types with values
- **ArraySpecialization**: Specialized array type information
### Information Extracted from C Headers
The system extracts comprehensive information from spine-cpp headers:
#### Class/Struct Information:
- **Members**: All public methods, fields, constructors, destructors
- **Inheritance**: Supertype relationships and template inheritance
- **Properties**: Abstract status, template parameters, virtual methods
- **Location**: File and line number information for each member
#### Method Details:
- **Signatures**: Return types, parameter types and names
- **Modifiers**: Static, virtual, pure virtual, const qualifiers
- **Inheritance**: Which supertype the method comes from
#### Type Analysis:
- **Template Detection**: Identifies and handles template types
- **Interface Classification**: Distinguishes pure interfaces from concrete classes
- **Inheritance Mapping**: Builds complete inheritance hierarchies
#### Validation Information:
- **Conflict Detection**: Const/non-const method conflicts
- **Type Support**: Multi-level pointers, unsupported patterns
- **Name Conflicts**: Method/type name collisions
### How This Information Can Be Used for Generating Bindings
The extracted information enables sophisticated binding generation for multiple target languages:
#### 1. **Type System Mapping**
- **Opaque Types**: C++ classes become opaque pointers (`Skeleton*``spine_skeleton`)
- **Primitive Passthrough**: Direct mapping for int, float, bool, etc.
- **Special Conversions**: `String``const char*`, `PropertyId``int64_t`
- **Array Specializations**: `Array<T>``spine_array_T` with full CRUD operations
#### 2. **Inheritance Support**
The inheritance maps enable proper class hierarchies in target languages:
- **Single Inheritance**: `extends` relationships for concrete parent classes
- **Interface Implementation**: `mixins` for pure interface types
- **Conflict Detection**: Prevents multiple concrete inheritance (unsupported in many languages)
#### 3. **Memory Management**
- **Constructor Wrapping**: Generates `spine_type_create()` functions
- **Destructor Wrapping**: Generates `spine_type_dispose()` functions
- **RTTI Support**: Maintains Spine's custom RTTI system for type checking
#### 4. **Method and Field Access**
- **Method Wrapping**: `Class::method()``spine_class_method()`
- **Field Accessors**: Automatic getter/setter generation for public fields
- **Parameter Marshaling**: Proper conversion between C++ and C calling conventions
#### 5. **Language-Specific Features**
- **Nullability**: Identifies nullable pointer types vs non-null references
- **Array Operations**: Complete CRUD operations for specialized array types
- **Enum Conversion**: Systematic enum name conversion with prefixes
#### 6. **Validation and Safety**
The extensive validation ensures generated bindings are safe and correct:
- **Type Safety**: Prevents unsupported type patterns
- **Name Conflicts**: Ensures no function name collisions
- **Interface Compliance**: Verifies inheritance patterns work in target languages
This comprehensive system allows binding generators for languages like Dart, Swift, Java, etc. to automatically create type-safe, idiomatic APIs that properly expose the full Spine C++ functionality while respecting each language's conventions and constraints.
## Dart Codegen Implementation Analysis
Based on my analysis of the Dart codegen implementation in spine-flutter, here's a comprehensive breakdown of how it works and the patterns that would be applicable to Swift:
### **1. Architecture Overview**
The Dart codegen follows a clean layered architecture:
1. **Input**: Uses the `generate()` function from spine-c's codegen to get the C Intermediate Representation (CIR)
2. **Transform**: Converts CIR to clean Dart model using `DartWriter`
3. **Generate**: Creates idiomatic Dart code from the model
4. **Output**: Writes individual files plus arrays and exports
### **2. How It Uses the generate() Output**
From `/Users/badlogic/workspaces/spine-runtimes/spine-flutter/codegen/src/index.ts`:
```typescript
const { cTypes, cEnums, cArrayTypes, inheritance, supertypes, subtypes, isInterface } = await generate();
```
The codegen consumes:
- **cTypes**: All C wrapper types with nullability information
- **cEnums**: Enum definitions
- **cArrayTypes**: Array specializations
- **inheritance**: Extends/implements relationships
- **isInterface**: Map of which types are pure interfaces
- **supertypes**: Type hierarchy for RTTI-based instantiation
### **3. Type Hierarchy and Inheritance Handling**
The implementation elegantly handles complex inheritance:
#### **Concrete Classes** (like `Skeleton`)
```dart
class Skeleton {
final Pointer<spine_skeleton_wrapper> _ptr;
Skeleton.fromPointer(this._ptr);
Pointer get nativePtr => _ptr;
// ... methods
}
```
#### **Abstract Classes** (like `Attachment`)
```dart
abstract class Attachment {
final Pointer<spine_attachment_wrapper> _ptr;
Attachment.fromPointer(this._ptr);
Pointer get nativePtr => _ptr;
// ... concrete methods with RTTI switching
}
```
#### **Interfaces** (like `Constraint`)
```dart
abstract class Constraint implements Update {
@override
Pointer get nativePtr;
ConstraintData get data; // abstract getters
void sort(Skeleton skeleton); // abstract methods
static Rtti rttiStatic() { /* implementation */ }
}
```
**Key Pattern**: The implementation uses single inheritance (`extends`) for concrete parent classes and multiple interface implementation (`implements`) for mixins.
### **4. Nullability Handling**
The nullability system is comprehensive and automatic:
#### **Nullable Return Values**
```dart
Bone? get rootBone {
final result = SpineBindings.bindings.spine_skeleton_get_root_bone(_ptr);
return result.address == 0 ? null : Bone.fromPointer(result);
}
```
#### **Nullable Parameters**
```dart
void sortBone(Bone? bone) {
SpineBindings.bindings.spine_skeleton_sort_bone(
_ptr,
bone?.nativePtr.cast() ?? Pointer.fromAddress(0)
);
}
```
#### **Non-nullable Types**
```dart
String get name { // No '?' - guaranteed non-null
final result = SpineBindings.bindings.spine_attachment_get_name(_ptr);
return result.cast<Utf8>().toDartString();
}
```
The nullability information flows from:
1. **C++ analysis**: Pointers can be null, references cannot
2. **CIR encoding**: `returnTypeNullable` and `parameter.isNullable` flags
3. **Dart generation**: Automatic `?` types and null checks
### **5. Generated Code Structure**
#### **Class Structure**
Each generated class follows a consistent pattern:
- License header
- Imports (FFI, bindings, related types)
- Class declaration with inheritance
- Pointer field (`_ptr`)
- Pointer constructor (`fromPointer`)
- Native pointer getter
- Factory constructors
- Properties (getters/setters)
- Methods
- Dispose method (for concrete classes)
#### **Method Generation**
The codegen intelligently detects:
- **Getters**: `spine_skeleton_get_data``SkeletonData get data`
- **Setters**: `spine_skeleton_set_x``set x(double value)`
- **Methods**: `spine_skeleton_update``void update(double delta)`
- **Constructors**: `spine_skeleton_create``factory Skeleton()`
#### **Method Overloading**
Handles C's numbered method pattern:
```dart
// spine_skeleton_set_skin_1 → setSkin(String)
void setSkin(String skinName) { ... }
// spine_skeleton_set_skin_2 → setSkin2(Skin?)
void setSkin2(Skin? newSkin) { ... }
```
### **6. RTTI-Based Instantiation**
For abstract types, the codegen generates runtime type checking:
```dart
Attachment? getAttachment(String slotName, String attachmentName) {
final result = SpineBindings.bindings.spine_skeleton_get_attachment_1(...);
if (result.address == 0) return null;
final rtti = SpineBindings.bindings.spine_attachment_get_rtti(result);
final className = SpineBindings.bindings.spine_rtti_get_class_name(rtti)
.cast<Utf8>().toDartString();
switch (className) {
case 'spine_region_attachment':
return RegionAttachment.fromPointer(result.cast());
case 'spine_mesh_attachment':
return MeshAttachment.fromPointer(result.cast());
// ... other concrete types
default:
throw UnsupportedError('Unknown concrete type: $className');
}
}
```
### **7. Array Types**
Arrays get specialized wrapper classes extending `NativeArray<T>`:
```dart
class ArrayFloat extends NativeArray<double> {
final bool _ownsMemory;
ArrayFloat.fromPointer(Pointer<spine_array_float_wrapper> ptr, {bool ownsMemory = false})
: _ownsMemory = ownsMemory, super(ptr);
factory ArrayFloat() { /* create constructor */ }
@override
int get length { /* implementation */ }
@override
double operator [](int index) { /* implementation */ }
void dispose() { /* only if _ownsMemory */ }
}
```
**Key Features**:
- Memory ownership tracking
- Bounds checking
- Null handling for object arrays
- Factory constructors for creation
### **8. Enum Generation**
Enums use Dart's modern enum syntax with values:
```dart
enum BlendMode {
normal(0),
additive(1),
multiply(2),
screen(3);
const BlendMode(this.value);
final int value;
static BlendMode fromValue(int value) {
return values.firstWhere(
(e) => e.value == value,
orElse: () => throw ArgumentError('Invalid BlendMode value: $value'),
);
}
}
```
### **9. Special Patterns for Swift**
Several patterns from the Dart implementation would translate well to Swift:
#### **Memory Management**
```swift
// Swift equivalent of pointer wrapping
class Skeleton {
private let ptr: OpaquePointer
init(fromPointer ptr: OpaquePointer) {
self.ptr = ptr
}
var nativePtr: OpaquePointer { ptr }
deinit {
spine_skeleton_dispose(ptr)
}
}
```
#### **Optional Handling**
```swift
// Swift's optionals map naturally to Dart's nullability
var rootBone: Bone? {
let result = spine_skeleton_get_root_bone(ptr)
return result == nil ? nil : Bone(fromPointer: result!)
}
```
#### **RTTI Switching**
```swift
// Swift's switch with associated values
func getAttachment(_ slotName: String, _ attachmentName: String) -> Attachment? {
guard let result = spine_skeleton_get_attachment_1(ptr, slotName, attachmentName) else {
return nil
}
let rtti = spine_attachment_get_rtti(result)
let className = String(cString: spine_rtti_get_class_name(rtti))
switch className {
case "spine_region_attachment":
return RegionAttachment(fromPointer: result)
case "spine_mesh_attachment":
return MeshAttachment(fromPointer: result)
default:
fatalError("Unknown concrete type: \(className)")
}
}
```
#### **Protocol-Based Interfaces**
```swift
// Swift protocols map well to Dart interfaces
protocol Constraint: Update {
var nativePtr: OpaquePointer { get }
var data: ConstraintData { get }
func sort(_ skeleton: Skeleton)
var isSourceActive: Bool { get }
}
```
### **10. Key Takeaways for Swift Implementation**
1. **Consistent Architecture**: Use the same 3-step process (input CIR → transform → generate)
2. **Nullability Mapping**: Leverage Swift's optionals to match CIR nullability exactly
3. **Memory Management**: Use automatic reference counting with `deinit` for cleanup
4. **Type Safety**: Generate compile-time safe wrappers around C pointers
5. **RTTI Handling**: Use Swift's powerful switch statements for type resolution
6. **Protocol Orientation**: Use Swift protocols for interfaces/mixins
7. **Value Types**: Use Swift enums with raw values for C enums
8. **Collection Types**: Create Array wrapper classes similar to Dart's approach
9. **Method Overloading**: Swift's native overloading can handle numbered C methods more elegantly
10. **Property Synthesis**: Use Swift's computed properties for getters/setters
The Dart implementation provides an excellent blueprint for creating idiomatic, type-safe wrappers around the C API while maintaining full compatibility with the underlying spine-c layer.
## Existing Swift Implementation Analysis
### 1. Current Code Generation Approach (Python-based)
**File**: `/Users/badlogic/workspaces/spine-runtimes/spine-cpp/spine-cpp-lite/spine-cpp-lite-codegen.py`
The current generator uses a Python script that:
- **Parses C++ header file** (`spine-cpp-lite.h`) using regex patterns to extract:
- Opaque types (between `@start: opaque_types` and `@end: opaque_types`)
- Function declarations (between `@start: function_declarations` and `@end: function_declarations`)
- Enums (between `@start: enums` and `@end: enums`)
- **Type mapping** approach:
```python
supported_types_to_swift_types = {
'void *': 'UnsafeMutableRawPointer',
'const utf8 *': 'String?',
'uint64_t': 'UInt64',
'float *': 'Float?',
'float': 'Float',
'int32_t': 'Int32',
'utf8 *': 'String?',
'int32_t *': 'Int32?',
'uint16_t *': 'UInt16',
'spine_bool': 'Bool'
}
```
- **Swift class generation pattern**:
- Each opaque type becomes a Swift class inheriting from `NSObject`
- Uses `@objc` and `@objcMembers` for Objective-C compatibility
- Internal `wrappee` property holds the C++ pointer
- Automatic generation of `isEqual` and `hash` methods
- Smart getter/setter detection for computed properties
### 2. Current Generated Code Structure
**File**: `/Users/badlogic/workspaces/spine-runtimes/spine-ios/Sources/Spine/Spine.Generated.swift`
The generated code follows these patterns:
- **Type aliases for enums**:
```swift
public typealias BlendMode = spine_blend_mode
public typealias MixBlend = spine_mix_blend
// etc.
```
- **Class structure**:
```swift
@objc(SpineTransformConstraintData)
@objcMembers
public final class TransformConstraintData: NSObject {
internal let wrappee: spine_transform_constraint_data
internal init(_ wrappee: spine_transform_constraint_data) {
self.wrappee = wrappee
super.init()
}
public override func isEqual(_ object: Any?) -> Bool {
guard let other = object as? TransformConstraintData else { return false }
return self.wrappee == other.wrappee
}
public override var hash: Int {
var hasher = Hasher()
hasher.combine(self.wrappee)
return hasher.finalize()
}
}
```
- **Smart property generation**:
- Detects getter/setter pairs and creates computed properties
- Handles array types with companion `get_num_*` functions
- Boolean conversion (`spine_bool` ↔ Swift `Bool`)
- Optional handling with `flatMap`
### 3. Extensions and Manual Code
**File**: `/Users/badlogic/workspaces/spine-runtimes/spine-ios/Sources/Spine/Spine.Generated+Extensions.swift`
Contains manually written extensions that provide:
- Async loading methods (`fromBundle`, `fromFile`, `fromHttp`)
- Swift-friendly error handling with `SpineError`
- Integration with iOS types (`UIImage`, `CGRect`)
- Memory management and resource disposal
- SwiftUI integration
### 4. Module Map Setup
**Files**:
- `/Users/badlogic/workspaces/spine-runtimes/spine-ios/Sources/SpineCppLite/include/module.modulemap`
- `/Users/badlogic/workspaces/spine-runtimes/spine-ios/Sources/SpineShadersStructs/module.modulemap`
Simple module maps that expose C++ headers to Swift:
```
module SpineCppLite {
header "spine-cpp-lite.h"
export *
}
```