🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
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
- Overview
- Architecture
- Type System
- File Structure
- Usage
- Type Conversion Rules
- Exclusions System
- Validation Checks
- Array Specializations
- Generated Code Examples
- Implementation Details
- Development Tools
- 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:
-
Type Extraction (
type-extractor.ts)- Uses Clang's
-ast-dump=jsonto parse C++ headers - Extracts all public members (methods, fields, constructors, destructors)
- Handles template types and inheritance relationships
- Outputs to
spine-cpp-types.json
- Uses Clang's
-
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
-
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
-
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
- Scans all types for
-
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
-
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)
-
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:
- Primitives: Direct mapping (int, float, bool, size_t, etc.)
- Special Types: Custom conversions (String → const char*, PropertyId → int64_t)
- Arrays: Template specializations (Array → spine_array_)
- Pointers: Class pointers become opaque types
- References: Converted based on const-ness and type
- Enums: Prefixed and snake_cased
- 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_skeletonSkeleton&→spine_skeleton(references become pointers)
Special Types
String→const char*PropertyId→int64_tArray<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 typesarrays.h/cpp- Array specializations
Usage
# 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:
# 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:
T& getValue(); // Non-const version
const T& getValue() const; // Const version
2. Multi-Level Pointers
Rejects types with multiple pointer levels:
char** strings; // Not supported
void*** ptr; // Not supported
3. Field Accessor Conflicts
Detects when generated accessors would conflict with existing methods:
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:
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:
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
Array<float> → spine_array_float
Array<int> → spine_array_int
Array<unsigned short> → spine_array_unsigned_short
Pointer Arrays
Array<Bone*> → spine_array_bone
Array<Slot*> → spine_array_slot
Array<float*> → spine_array_float_ptr
Enum Arrays
Array<BlendMode> → spine_array_blend_mode
Array<PropertyId> → spine_array_property_id
Unsupported Arrays
Array<String>- Useconst char**insteadArray<Array<T>>- Nested arrays not supported- Arrays with const elements
Generated Code Examples
Class Wrapper
// 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
// 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.
// 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
SpineObjectuse location-basedoperator new - Constructors use
new (__FILE__, __LINE__)for memory tracking - Destructors call
deleteon 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
_methodsuffix 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
-
"Unknown type: X"
- The type is not a primitive and not in the extracted types
- Solution: Add to exclusions or check spelling
-
"Multi-level pointers are not supported"
- Type contains
**or more pointers - Solution: Refactor C++ code or exclude
- Type contains
-
"Array is not supported"
- String arrays need special handling
- Solution: Use
const char**in C++ or exclude
-
"No public constructors"
- Class has no public constructors for instantiation
- Solution: Add public constructor or exclude
-
"Method/type name conflict"
- Generated function name collides with a type name
- Solution: Rename method or exclude
-
"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
- Check
spine-cpp-types.jsonfor extracted type information - Look for "Excluding" messages in console output
- Verify inheritance with "inherits from SpineObject" messages
- Array specializations are listed with element type mapping
- Check warnings at the end of generation for issues
- Use
--export-jsonflag to export inheritance and type information as JSON - Check
out.jsonfor debug output when troubleshooting - Review console output for inheritance mapping information (extends/mixins)
Adding New Types
- Ensure the type is in spine-cpp headers
- Remove from exclusions.txt if previously excluded
- Check that all dependent types are included
- Run generator and fix any reported issues
- 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 definitionstsx- TypeScript execution enginetypescript-formatter- Code formatting@biomejs/biome- Fast linter for code quality