mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-20 17:26:01 +08:00
585 lines
20 KiB
Markdown
585 lines
20 KiB
Markdown
# Spine C API Code Generator
|
|
|
|
This TypeScript-based code generator automatically creates a C wrapper API for the Spine C++ runtime. It parses the spine-cpp headers using Clang's AST and generates a complete C API with opaque types, following systematic type conversion rules. The generator also builds inheritance maps and interface information for multi-language binding generation.
|
|
|
|
## Table of Contents
|
|
|
|
1. [Overview](#overview)
|
|
2. [Architecture](#architecture)
|
|
3. [Type System](#type-system)
|
|
4. [File Structure](#file-structure)
|
|
5. [Usage](#usage)
|
|
6. [Type Conversion Rules](#type-conversion-rules)
|
|
7. [Exclusions System](#exclusions-system)
|
|
8. [Validation Checks](#validation-checks)
|
|
9. [Array Specializations](#array-specializations)
|
|
10. [Generated Code Examples](#generated-code-examples)
|
|
11. [Implementation Details](#implementation-details)
|
|
12. [Development Tools](#development-tools)
|
|
13. [Troubleshooting](#troubleshooting)
|
|
|
|
## Overview
|
|
|
|
The code generator performs static analysis on the spine-cpp headers to automatically generate a C API that wraps the C++ classes. It handles:
|
|
|
|
- Type conversions between C++ and C
|
|
- Method wrapping with proper parameter marshaling
|
|
- Memory management through constructors and destructors
|
|
- Enum conversions
|
|
- Array specializations for different element types
|
|
- Field accessors (getters/setters) for public fields
|
|
- Automatic validation and conflict detection
|
|
- Inheritance analysis and interface detection for multi-language bindings
|
|
|
|
## Architecture
|
|
|
|
The generator follows a multi-stage pipeline:
|
|
|
|
1. **Type Extraction** (`type-extractor.ts`)
|
|
- Uses Clang's `-ast-dump=json` to parse C++ headers
|
|
- Extracts all public members (methods, fields, constructors, destructors)
|
|
- Handles template types and inheritance relationships
|
|
- Outputs to `spine-cpp-types.json`
|
|
|
|
2. **Type Processing** (`index.ts`)
|
|
- Loads extracted types and exclusions
|
|
- Filters out template types, excluded types, and internal classes
|
|
- Determines which types inherit from SpineObject
|
|
- Validates that all types meet generation requirements
|
|
|
|
3. **Validation** (`checks.ts`)
|
|
- Detects const/non-const method conflicts
|
|
- Identifies multi-level pointers
|
|
- Finds field accessor conflicts
|
|
- Checks for method/type name collisions
|
|
- Validates return types
|
|
|
|
4. **Array Scanning** (`array-scanner.ts`)
|
|
- Scans all types for `Array<T>` usage
|
|
- Generates specialized array types for each element type
|
|
- Handles primitive, pointer, and enum arrays
|
|
|
|
5. **IR Generation** (`ir-generator.ts`)
|
|
- Converts C++ types to C intermediate representation
|
|
- Generates wrapper methods with proper marshaling
|
|
- Creates field accessors for public fields
|
|
- Adds constructors and destructors
|
|
|
|
6. **Code Writing** (`c-writer.ts`)
|
|
- Writes header files with C function declarations
|
|
- Writes implementation files with C++ wrapper code
|
|
- Generates array specialization files
|
|
- Creates main include files (`types.h`)
|
|
|
|
7. **Inheritance Analysis**
|
|
- Builds inheritance maps for single-inheritance languages (Dart, Swift, Java)
|
|
- Identifies pure interfaces vs concrete classes
|
|
- Detects multiple concrete inheritance (not supported)
|
|
- Generates inheritance information for language binding generators
|
|
|
|
## Type System
|
|
|
|
### Type Categories
|
|
|
|
The generator categorizes types for systematic conversion:
|
|
|
|
1. **Primitives**: Direct mapping (int, float, bool, size_t, etc.)
|
|
2. **Special Types**: Custom conversions (String → const char*, PropertyId → int64_t)
|
|
3. **Arrays**: Template specializations (Array<T> → spine_array_<type>)
|
|
4. **Pointers**: Class pointers become opaque types
|
|
5. **References**: Converted based on const-ness and type
|
|
6. **Enums**: Prefixed and snake_cased
|
|
7. **Classes**: Converted to opaque pointers with prefix
|
|
|
|
### C++ to C Type Mapping
|
|
|
|
The generator uses opaque pointers for all C++ classes:
|
|
- `Skeleton*` → `spine_skeleton` (opaque pointer)
|
|
- `const Skeleton*` → `const spine_skeleton`
|
|
- `Skeleton&` → `spine_skeleton` (references become pointers)
|
|
|
|
### Special Types
|
|
- `String` → `const char*`
|
|
- `PropertyId` → `int64_t`
|
|
- `Array<T>` → `spine_array_T` (specialized types)
|
|
|
|
### Primitive Types
|
|
- Primitives pass through unchanged: `int`, `float`, `bool`, etc.
|
|
- Non-const primitive references become pointers: `float&` → `float*`
|
|
|
|
## File Structure
|
|
|
|
```
|
|
codegen/
|
|
├── src/
|
|
│ ├── index.ts # Main entry point and orchestration
|
|
│ ├── type-extractor.ts # Clang AST parsing
|
|
│ ├── cpp-check.ts # C++ nullability analysis tool
|
|
│ ├── types.ts # Type definitions and conversion logic
|
|
│ ├── c-types.ts # C IR type definitions
|
|
│ ├── array-scanner.ts # Array specialization detection
|
|
│ ├── checks.ts # Validation checks
|
|
│ ├── exclusions.ts # Exclusion handling
|
|
│ ├── ir-generator.ts # C++ to C IR conversion
|
|
│ ├── c-writer.ts # File generation
|
|
│ └── warnings.ts # Warning collection
|
|
├── dist/ # TypeScript compilation output
|
|
├── exclusions.txt # Type/method exclusions
|
|
├── spine-cpp-types.json # Extracted type information
|
|
├── nullable.md # C++ nullability analysis results
|
|
├── out.json # Debug output file
|
|
├── package.json # Node.js configuration
|
|
├── tsconfig.json # TypeScript configuration
|
|
├── tsfmt.json # TypeScript formatter configuration
|
|
├── biome.json # Biome linter configuration
|
|
└── node_modules/ # Dependencies
|
|
```
|
|
|
|
Generated files are output to `../src/generated/`:
|
|
- Individual files per type (e.g., `skeleton.h`, `skeleton.cpp`)
|
|
- `types.h` - Forward declarations for all types
|
|
- `arrays.h/cpp` - Array specializations
|
|
|
|
## Usage
|
|
|
|
```bash
|
|
# Install dependencies
|
|
npm install
|
|
|
|
# Run the code generator
|
|
npx tsx src/index.ts
|
|
|
|
# Or export JSON for debugging
|
|
npx tsx src/index.ts --export-json
|
|
|
|
# The generated files will be in ../src/generated/
|
|
```
|
|
|
|
### C++ Nullability Analysis Tool
|
|
|
|
The codegen includes a tool to analyze spine-cpp for nullability patterns:
|
|
|
|
```bash
|
|
# Generate nullable.md with clickable links to methods with nullable inputs/outputs
|
|
npm run cpp-check
|
|
```
|
|
|
|
This tool identifies all methods that either:
|
|
- Return pointer types (nullable return values)
|
|
- Take pointer parameters (nullable inputs)
|
|
|
|
The output `nullable.md` contains clickable markdown links for easy navigation in VS Code. This is useful for cleaning up the spine-cpp API to use references vs pointers appropriately to signal nullability.
|
|
|
|
The generator automatically:
|
|
- Detects when spine-cpp headers have changed
|
|
- Regenerates only when necessary
|
|
- Reports warnings and errors during generation
|
|
- Formats the generated C++ code using the project's formatter
|
|
- Builds inheritance maps for multi-language binding generation
|
|
|
|
## Type Conversion Rules
|
|
|
|
### Primitive Types
|
|
Primitive types are "pass-through".
|
|
|
|
```
|
|
C++ Type → C Type
|
|
─────────────────────────────────
|
|
int → int
|
|
float* → float*
|
|
const char* → const char*
|
|
bool → bool (stdbool.h)
|
|
size_t → size_t
|
|
```
|
|
|
|
### Class Types
|
|
```
|
|
C++ Type → C Type
|
|
─────────────────────────────────
|
|
Bone* → spine_bone
|
|
const Bone* → const spine_bone
|
|
Bone& → spine_bone
|
|
const Bone& → spine_bone
|
|
```
|
|
|
|
### Special Cases
|
|
```
|
|
C++ Type → C Type
|
|
─────────────────────────────────
|
|
String → const char*
|
|
String& → const char*
|
|
const String& → const char*
|
|
PropertyId → int64_t
|
|
Array<float> → spine_array_float
|
|
Array<Bone*> → spine_array_bone
|
|
```
|
|
|
|
### Output Parameters
|
|
```
|
|
C++ Type → C Type
|
|
─────────────────────────────────
|
|
float& → float* (output param)
|
|
int& → int* (output param)
|
|
```
|
|
|
|
### Function Naming
|
|
```
|
|
C++ Method → C Function
|
|
─────────────────────────────────────────
|
|
Skeleton::updateCache() → spine_skeleton_update_cache()
|
|
AnimationState::apply() → spine_animation_state_apply()
|
|
Bone::getX() → spine_bone_get_x()
|
|
```
|
|
|
|
## Exclusions System
|
|
|
|
The `exclusions.txt` file controls what gets generated:
|
|
|
|
### Type Exclusions
|
|
Exclude entire types from generation:
|
|
```
|
|
type: SkeletonClipping
|
|
type: Triangulator
|
|
```
|
|
|
|
### Method Exclusions
|
|
Exclude specific methods:
|
|
```
|
|
method: AnimationState::setListener
|
|
method: AnimationState::addListener
|
|
```
|
|
|
|
### Const-Specific Exclusions
|
|
Exclude only const or non-const versions:
|
|
```
|
|
method: BoneData::getSetupPose const
|
|
```
|
|
|
|
### Constructor Exclusions
|
|
Allow type but prevent instantiation:
|
|
```
|
|
method: AtlasRegion::AtlasRegion
|
|
```
|
|
|
|
### Field Accessor Exclusions
|
|
Control field getter/setter generation:
|
|
```
|
|
field: AtlasRegion::names # Exclude both getter and setter
|
|
field-get: SecretData::password # Exclude only getter
|
|
field-set: Bone::x # Exclude only setter
|
|
```
|
|
|
|
### Type-Wide Field Exclusions
|
|
Exclude all field accessors for a type:
|
|
```
|
|
field: RenderCommand # No field accessors at all
|
|
field-get: DebugData # No getters (write-only fields)
|
|
field-set: RenderCommand # No setters (read-only fields)
|
|
```
|
|
|
|
## Validation Checks
|
|
|
|
The generator performs extensive validation to ensure correctness:
|
|
|
|
### 1. Const/Non-Const Conflicts
|
|
Detects methods with both const and non-const versions:
|
|
```cpp
|
|
T& getValue(); // Non-const version
|
|
const T& getValue() const; // Const version
|
|
```
|
|
|
|
### 2. Multi-Level Pointers
|
|
Rejects types with multiple pointer levels:
|
|
```cpp
|
|
char** strings; // Not supported
|
|
void*** ptr; // Not supported
|
|
```
|
|
|
|
### 3. Field Accessor Conflicts
|
|
Detects when generated accessors would conflict with existing methods:
|
|
```cpp
|
|
class Bone {
|
|
float x; // Would generate get_x/set_x
|
|
float getX(); // Conflicts with generated getter
|
|
};
|
|
```
|
|
|
|
### 4. Method/Type Name Conflicts
|
|
Ensures generated function names don't collide with type names:
|
|
```cpp
|
|
class BonePose { }; // → spine_bone_pose
|
|
class Bone {
|
|
void pose(); // → spine_bone_pose (conflict!)
|
|
};
|
|
```
|
|
|
|
### 5. Value Return Types
|
|
Detects methods returning non-primitive types by value:
|
|
```cpp
|
|
Color getColor(); // Cannot return objects by value in C
|
|
```
|
|
|
|
## Array Specializations
|
|
|
|
The generator automatically creates specialized array types for any `Array<T>` found in the API:
|
|
|
|
### Primitive Arrays
|
|
```cpp
|
|
Array<float> → spine_array_float
|
|
Array<int> → spine_array_int
|
|
Array<unsigned short> → spine_array_unsigned_short
|
|
```
|
|
|
|
### Pointer Arrays
|
|
```cpp
|
|
Array<Bone*> → spine_array_bone
|
|
Array<Slot*> → spine_array_slot
|
|
Array<float*> → spine_array_float_ptr
|
|
```
|
|
|
|
### Enum Arrays
|
|
```cpp
|
|
Array<BlendMode> → spine_array_blend_mode
|
|
Array<PropertyId> → spine_array_property_id
|
|
```
|
|
|
|
### Unsupported Arrays
|
|
- `Array<String>` - Use `const char**` instead
|
|
- `Array<Array<T>>` - Nested arrays not supported
|
|
- Arrays with const elements
|
|
|
|
## Generated Code Examples
|
|
|
|
### Class Wrapper
|
|
```c
|
|
// Header: skeleton.h
|
|
typedef struct spine_skeleton* spine_skeleton;
|
|
|
|
spine_skeleton spine_skeleton_create(spine_skeleton_data data);
|
|
void spine_skeleton_dispose(spine_skeleton self);
|
|
void spine_skeleton_update_cache(spine_skeleton self);
|
|
float spine_skeleton_get_x(const spine_skeleton self);
|
|
void spine_skeleton_set_x(spine_skeleton self, float value);
|
|
|
|
// Implementation: skeleton.cpp
|
|
spine_skeleton spine_skeleton_create(spine_skeleton_data data) {
|
|
return (spine_skeleton) new (__FILE__, __LINE__) Skeleton((SkeletonData*)data);
|
|
}
|
|
|
|
void spine_skeleton_update_cache(spine_skeleton self) {
|
|
((Skeleton*)self)->updateCache();
|
|
}
|
|
```
|
|
|
|
### Enum Wrapper
|
|
```c
|
|
// Header: blend_mode.h
|
|
#ifndef SPINE_SPINE_BLEND_MODE_H
|
|
#define SPINE_SPINE_BLEND_MODE_H
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
typedef enum spine_blend_mode {
|
|
SPINE_BLEND_MODE_NORMAL = 0,
|
|
SPINE_BLEND_MODE_ADDITIVE,
|
|
SPINE_BLEND_MODE_MULTIPLY,
|
|
SPINE_BLEND_MODE_SCREEN
|
|
} spine_blend_mode;
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* SPINE_SPINE_BLEND_MODE_H */
|
|
```
|
|
|
|
### Array Specialization
|
|
Arrays are generated as opaque types with complete CRUD operations. All arrays are consolidated into `arrays.h` and `arrays.cpp`.
|
|
|
|
```c
|
|
// Header: arrays.h
|
|
SPINE_OPAQUE_TYPE(spine_array_float)
|
|
|
|
// Creation functions
|
|
spine_array_float spine_array_float_create(void);
|
|
spine_array_float spine_array_float_create_with_capacity(size_t initialCapacity);
|
|
|
|
// Memory management
|
|
void spine_array_float_dispose(spine_array_float array);
|
|
void spine_array_float_clear(spine_array_float array);
|
|
|
|
// Size and capacity operations
|
|
size_t spine_array_float_get_capacity(spine_array_float array);
|
|
size_t spine_array_float_size(spine_array_float array);
|
|
spine_array_float spine_array_float_set_size(spine_array_float array, size_t newSize, float defaultValue);
|
|
void spine_array_float_ensure_capacity(spine_array_float array, size_t newCapacity);
|
|
|
|
// Element operations
|
|
void spine_array_float_add(spine_array_float array, float inValue);
|
|
void spine_array_float_add_all(spine_array_float array, spine_array_float inValue);
|
|
void spine_array_float_clear_and_add_all(spine_array_float array, spine_array_float inValue);
|
|
void spine_array_float_remove_at(spine_array_float array, size_t inIndex);
|
|
|
|
// Search operations
|
|
bool spine_array_float_contains(spine_array_float array, float inValue);
|
|
int spine_array_float_index_of(spine_array_float array, float inValue);
|
|
|
|
// Direct buffer access
|
|
float *spine_array_float_buffer(spine_array_float array);
|
|
|
|
// Implementation: arrays.cpp
|
|
spine_array_float spine_array_float_create(void) {
|
|
return (spine_array_float) new (__FILE__, __LINE__) Array<float>();
|
|
}
|
|
|
|
void spine_array_float_dispose(spine_array_float array) {
|
|
delete (Array<float> *) array;
|
|
}
|
|
|
|
void spine_array_float_add(spine_array_float array, float inValue) {
|
|
Array<float> *_array = (Array<float> *) array;
|
|
_array->add(inValue);
|
|
}
|
|
|
|
float *spine_array_float_buffer(spine_array_float array) {
|
|
Array<float> *_array = (Array<float> *) array;
|
|
return _array->buffer();
|
|
}
|
|
```
|
|
|
|
Arrays are generated for all basic types (`float`, `int`, `unsigned_short`, `property_id`) and all object types used in collections throughout the API. The implementation directly casts the opaque handle to the underlying `Array<T>*` type.
|
|
|
|
## Implementation Details
|
|
|
|
### Memory Management
|
|
- All C++ objects inheriting from `SpineObject` use location-based `operator new`
|
|
- Constructors use `new (__FILE__, __LINE__)` for memory tracking
|
|
- Destructors call `delete` on the C++ object
|
|
- Array types are wrapped in structs to maintain C++ semantics
|
|
|
|
### Constructor Generation
|
|
- Only generates constructors for non-abstract classes
|
|
- Only generates constructors for classes inheriting from `SpineObject`
|
|
- Requires at least one public constructor or explicit exclusion
|
|
- Constructor overloads are numbered: `_create`, `_create2`, `_create3`
|
|
|
|
### Field Accessor Generation
|
|
- Generates getters for all non-static public fields
|
|
- Generates setters for non-const, non-reference fields
|
|
- Uses direct field access, not C++ getter/setter methods
|
|
- Handles nested field access (e.g., `obj.field.x`)
|
|
|
|
### Method Overloading
|
|
- Constructor overloads are numbered: `_create`, `_create2`, `_create3`, etc.
|
|
- Method overloads are numbered with suffixes: `_1`, `_2`, `_3`, etc.
|
|
- Methods named "create" get `_method` suffix to avoid constructor conflicts
|
|
- Const/non-const conflicts are detected and reported
|
|
|
|
### RTTI Handling
|
|
- Uses Spine's custom RTTI system (`getRTTI().instanceOf()`)
|
|
- No C++ RTTI or exceptions are used
|
|
- RTTI checks are performed in generated code where needed
|
|
|
|
### Warning System
|
|
- Collects non-fatal issues during generation using `WarningsCollector`
|
|
- Reports abstract classes, missing constructors, etc.
|
|
- Groups warnings by pattern to avoid repetition
|
|
- Warnings don't stop generation but are reported at the end
|
|
|
|
### Interface Detection
|
|
- Automatically identifies pure interfaces (classes with only pure virtual methods)
|
|
- Distinguishes between concrete classes and interfaces for inheritance mapping
|
|
- Used to determine extends vs implements relationships for target languages
|
|
|
|
### Multiple Inheritance Handling
|
|
- Detects multiple concrete inheritance scenarios
|
|
- Fails generation with clear error messages when unsupported patterns are found
|
|
- Provides guidance on converting concrete classes to interfaces
|
|
|
|
## Troubleshooting
|
|
|
|
### Common Errors
|
|
|
|
1. **"Unknown type: X"**
|
|
- The type is not a primitive and not in the extracted types
|
|
- Solution: Add to exclusions or check spelling
|
|
|
|
2. **"Multi-level pointers are not supported"**
|
|
- Type contains `**` or more pointers
|
|
- Solution: Refactor C++ code or exclude
|
|
|
|
3. **"Array<String> is not supported"**
|
|
- String arrays need special handling
|
|
- Solution: Use `const char**` in C++ or exclude
|
|
|
|
4. **"No public constructors"**
|
|
- Class has no public constructors for instantiation
|
|
- Solution: Add public constructor or exclude
|
|
|
|
5. **"Method/type name conflict"**
|
|
- Generated function name collides with a type name
|
|
- Solution: Rename method or exclude
|
|
|
|
6. **"Multiple concrete inheritance detected"**
|
|
- A class inherits from multiple concrete (non-interface) classes
|
|
- Solution: Convert one of the parent classes to a pure interface
|
|
- Check the error message for specific guidance on which classes to modify
|
|
|
|
### Debugging Tips
|
|
|
|
1. Check `spine-cpp-types.json` for extracted type information
|
|
2. Look for "Excluding" messages in console output
|
|
3. Verify inheritance with "inherits from SpineObject" messages
|
|
4. Array specializations are listed with element type mapping
|
|
5. Check warnings at the end of generation for issues
|
|
6. Use `--export-json` flag to export inheritance and type information as JSON
|
|
7. Check `out.json` for debug output when troubleshooting
|
|
8. Review console output for inheritance mapping information (extends/mixins)
|
|
|
|
### Adding New Types
|
|
|
|
1. Ensure the type is in spine-cpp headers
|
|
2. Remove from exclusions.txt if previously excluded
|
|
3. Check that all dependent types are included
|
|
4. Run generator and fix any reported issues
|
|
5. Verify generated code compiles
|
|
|
|
### Performance Considerations
|
|
|
|
- Type extraction uses Clang AST parsing (slow but accurate)
|
|
- File generation is parallelized where possible
|
|
- Array scanning happens after type filtering for efficiency
|
|
- Validation checks run before generation to fail fast
|
|
- Incremental generation avoids regenerating unchanged files
|
|
|
|
## Development Tools
|
|
|
|
The codegen project includes several development tools and configurations:
|
|
|
|
### Biome Configuration (`biome.json`)
|
|
- Linting enabled with recommended rules
|
|
- Formatting disabled (uses external formatter)
|
|
- Helps maintain code quality during development
|
|
|
|
### TypeScript Formatter (`tsfmt.json`)
|
|
- Comprehensive formatting rules for TypeScript code
|
|
- Configures indentation, spacing, and code style
|
|
- Used for consistent code formatting across the project
|
|
|
|
### Build Output (`dist/`)
|
|
- Contains compiled TypeScript files
|
|
- Generated JavaScript and declaration files
|
|
- Source maps for debugging
|
|
|
|
### Debug Output (`out.json`)
|
|
- Contains debug information from the generation process
|
|
- Useful for troubleshooting and understanding the generated data structure
|
|
|
|
### Dependencies
|
|
The project uses minimal dependencies for maximum compatibility:
|
|
- `@types/node` - Node.js type definitions
|
|
- `tsx` - TypeScript execution engine
|
|
- `typescript-formatter` - Code formatting
|
|
- `@biomejs/biome` - Fast linter for code quality |