- Ported extract-spine-cpp-types.js to TypeScript in type-extractor.ts - Improved type interfaces with discriminated unions for better type safety - Added proper isConst tracking for const-qualified methods - Fixed exclusions to check method.isConst instead of return type - Removed special type mappings (utf8, spine_void) - primitives pass through unchanged - Made toCTypeName strict with proper error handling - Documented all conversion functions with examples - Excluded SpineObject from extraction (matches JS behavior) - Removed original JS extractor as it's now replaced by TypeScript version The TypeScript extractor produces identical output (107 files, 164 types) while providing better type information including isConst for methods and consistent isStatic fields. 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 and generates a complete C API with opaque types, following systematic type conversion rules.
Table of Contents
- Overview
- Architecture
- Type System
- File Structure
- Generation Process
- Key Design Decisions
- Exclusion System
- Array Specializations
- Type Conversion Rules
- Running the Generator
Overview
The generator creates a C API that wraps the spine-cpp C++ runtime, allowing C programs to use Spine functionality. Key features:
- Opaque Types: All C++ classes are exposed as opaque pointers in C
- Automatic Memory Management: Generates create/dispose functions
- Method Wrapping: Converts C++ methods to C functions with proper type conversion
- Array Specializations: Generates concrete array types for all Array usage
- Systematic Type Handling: Uses categorized type conversion instead of ad-hoc rules
Architecture
Core Components
codegen/
├── src/
│ ├── index.ts # Main entry point
│ ├── types.ts # Type definitions and conversion
│ ├── exclusions.ts # Exclusion system
│ ├── type-extractor.ts # Automatic type extraction
│ ├── array-scanner.ts # Array specialization scanner
│ ├── file-writer.ts # File generation
│ └── generators/
│ ├── opaque-type-generator.ts # Opaque type declarations
│ ├── constructor-generator.ts # Create/dispose functions
│ ├── method-generator.ts # Method wrappers
│ ├── enum-generator.ts # Enum conversions
│ └── array-generator.ts # Array specializations
├── exclusions.txt # Types/methods to exclude
└── spine-cpp-types.json # Extracted type information
Data Flow
- Type Extraction:
extract-spine-cpp-types.jsparses C++ headers →spine-cpp-types.json - Loading: Generator loads JSON and exclusions
- Filtering: Excludes types based on rules (templates, abstracts, manual exclusions)
- Generation: Each generator processes types and creates C code
- Writing: Files are written to
src/generated/
Type System
Type Categories
The generator classifies all C++ types into systematic categories:
-
Primitives:
int,float,double,bool,char,void,size_t- Direct mapping (e.g.,
bool→bool)
- Direct mapping (e.g.,
-
Special Types: String, function pointers, PropertyId
String→const utf8 *void *→spine_voidPropertyId→int64_t(typedef'd to long long)
-
Arrays:
Array<T>specializations- Generated as
spine_array_<element_type> - Full API for each specialization
- Generated as
-
Pointers: Type followed by
*- Primitive pointers stay as-is (
float *) - Class pointers become opaque (
Bone *→spine_bone)
- Primitive pointers stay as-is (
-
References: Type followed by
&- Const references: treated as value parameters
- Non-const primitive references: output parameters (
float &→float *) - Class references: converted to opaque types
-
Enums: Known spine enums
- Prefixed with
spine_and converted to snake_case
- Prefixed with
-
Classes: All other types
- Assumed to be spine classes, converted to
spine_<snake_case>
- Assumed to be spine classes, converted to
Opaque Type Pattern
All C++ classes are exposed as opaque pointers:
// In types.h
SPINE_OPAQUE_TYPE(spine_bone) // Expands to typedef struct spine_bone_wrapper* spine_bone
// In implementation
spine_bone spine_bone_create() {
return (spine_bone) new (__FILE__, __LINE__) Bone();
}
File Structure
Generated Files
-
types.h: Forward declarations for all types
- All opaque type declarations
- Includes for all enum headers
- Includes arrays.h at the bottom
-
arrays.h/arrays.cpp: Array specializations
- Generated for all Array found in spine-cpp
- Complete API for each specialization
-
.h/.cpp: One pair per type
- Header contains function declarations
- Source contains implementations
-
spine-c.h: Main header that includes everything
Include Order
The main spine-c.h includes files in this order:
- base.h (basic definitions)
- types.h (all forward declarations)
- extensions.h (custom functionality)
- All generated type headers
This ensures all types are declared before use.
Generation Process
1. Type Extraction
The generator automatically runs extract-spine-cpp-types.js if:
spine-cpp-types.jsondoesn't exist- Any spine-cpp header is newer than the JSON file
This script:
- Parses all spine-cpp headers using tree-sitter
- Extracts complete type information including inherited members
- Resolves template inheritance
- Marks abstract classes and templates
2. Type Filtering
Types are excluded if they are:
- Templates: Detected by
isTemplatefield - Abstract: Have unimplemented pure virtual methods
- Internal utilities: Array, String, HashMap, etc.
- Manually excluded: Listed in exclusions.txt
3. Code Generation
For each included type:
Constructors
- Generates
spine_<type>_create()for default constructor - Generates
spine_<type>_create_with_<params>()for parameterized constructors - Always generates
spine_<type>_dispose()for cleanup
Methods
- Getters:
spine_<type>_get_<property>() - Setters:
spine_<type>_set_<property>() - Other methods:
spine_<type>_<method_name>() - Special handling for:
- Vector return types (generate collection accessors)
- RTTI methods (made static)
- Const/non-const overloads (reported as errors)
Arrays
- Scans all types for Array usage
- Generates specializations for each unique T
- Filters out template placeholders (T, K)
- Warns about problematic types (String, nested arrays)
Key Design Decisions
1. Why Opaque Types?
C doesn't support classes or inheritance. Opaque pointers:
- Hide implementation details
- Prevent direct struct access
- Allow polymorphism through base type pointers
- Match C convention for handles
2. Why Generate Array Specializations?
C can't have template types. Options were:
- Use
void *everywhere (loses type safety) - Generate specializations (chosen approach)
Benefits:
- Type safety in C
- Better API documentation
- Prevents casting errors
3. Why Systematic Type Classification?
Original code had many special cases. Systematic approach:
- Reduces bugs from missed cases
- Makes behavior predictable
- Easier to maintain
- Clear rules for each category
4. Why Exclude Const Methods?
C doesn't have const-correctness. When C++ has:
T& getValue(); // for non-const objects
const T& getValue() const; // for const objects
C can only have one function name. We exclude const versions and expose non-const.
5. Why Static RTTI Methods?
RTTI objects are singletons in spine-cpp. Making getRTTI() static:
- Reflects actual usage (Type::rtti)
- Avoids unnecessary object parameter
- Cleaner API
Exclusion System
exclusions.txt Format
# Exclude entire types
type: SkeletonClipping
type: Triangulator
# Exclude specific methods
method: AnimationState::setListener
method: AnimationState::addListener
# Exclude const versions specifically
method: BoneData::getSetupPose const
Exclusion Rules
- Type exclusions: Entire type and all methods excluded
- Method exclusions: Specific methods on otherwise included types
- Const-specific: Can exclude just const or non-const version
Array Specializations
Scanning Process
- Examines all members of non-excluded types
- Extracts Array patterns from:
- Return types
- Parameter types
- Field types
- Cleans element types (removes class/struct prefix)
- Categorizes as primitive/enum/pointer
Generated API
For each Array, generates:
// Creation
spine_array_float spine_array_float_create();
spine_array_float spine_array_float_create_with_capacity(int32_t capacity);
void spine_array_float_dispose(spine_array_float array);
// Element access
float spine_array_float_get(spine_array_float array, int32_t index);
void spine_array_float_set(spine_array_float array, int32_t index, float value);
// Array methods (auto-generated from Array type)
size_t spine_array_float_size(spine_array_float array);
void spine_array_float_clear(spine_array_float array);
void spine_array_float_add(spine_array_float array, float value);
// ... etc
Special Cases
- String arrays: Warned but skipped (should use const char**)
- Nested arrays: Warned and skipped (Array<Array>)
- PropertyId: Treated as int64_t, not enum
Type Conversion Rules
toCTypeName Function
Implements systematic type conversion:
- Remove namespace: Strip any
spine::prefix - Check primitives: Direct mapping via table
- Check special types: String, void*, function pointers
- Check arrays: Convert Array to spine_array_*
- Check pointers: Handle based on pointed-to type
- Check references: Handle based on const-ness
- Check enums: Known enum list
- Default to class: Assume spine type
Method Parameter Conversion
- Input parameters: C++ type to C type
- Output parameters: Non-const references become pointers
- String parameters: Create String objects from const char*
- Enum parameters: Cast to C++ enum type
Return Value Conversion
- Strings: Return buffer() as const char*
- References: Take address and cast
- Enums: Cast to C enum type
- Arrays: Return as specialized array type
Running the Generator
Prerequisites
npm install
Build and Run
npm run build # Compile TypeScript
node dist/index.js # Run generator
What Happens
- Checks if type extraction needed (file timestamps)
- Runs extraction if needed
- Loads types and exclusions
- Filters types based on rules
- Generates code for each type
- Writes all files to src/generated/
- Updates main spine-c.h
Output
- Generates ~150 .h/.cpp file pairs
- Creates arrays.h with ~30 specializations
- All files include proper license headers
- Organized by type for easy navigation
Maintenance
Adding New Types
- No action needed - automatically detected from spine-cpp
Excluding Types/Methods
- Add to exclusions.txt
- Regenerate
Changing Type Mappings
- Update toCTypeName in types.ts
- Follow systematic categories
Debugging
- Check spine-cpp-types.json for extracted data
- Look for warnings in console output
- Verify exclusions are applied correctly
- Check generated files for correctness