From 30d4cb98203c71a48a6f24618e2b6feda80b3a91 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Mon, 14 Jul 2025 21:23:08 +0200 Subject: [PATCH] [c] Update codegen README.md --- spine-c/codegen/README.md | 721 +++++++++++++++++++++----------------- 1 file changed, 407 insertions(+), 314 deletions(-) diff --git a/spine-c/codegen/README.md b/spine-c/codegen/README.md index 5c4d41047..93a5e0906 100644 --- a/spine-c/codegen/README.md +++ b/spine-c/codegen/README.md @@ -1,6 +1,6 @@ # 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. +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. ## Table of Contents @@ -8,363 +8,456 @@ This TypeScript-based code generator automatically creates a C wrapper API for t 2. [Architecture](#architecture) 3. [Type System](#type-system) 4. [File Structure](#file-structure) -5. [Generation Process](#generation-process) -6. [Key Design Decisions](#key-design-decisions) -7. [Exclusion System](#exclusion-system) -8. [Array Specializations](#array-specializations) -9. [Type Conversion Rules](#type-conversion-rules) -10. [Running the Generator](#running-the-generator) +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. [Troubleshooting](#troubleshooting) ## Overview -The generator creates a C API that wraps the spine-cpp C++ runtime, allowing C programs to use Spine functionality. Key features: +The code generator performs static analysis on the spine-cpp headers to automatically generate a C API that wraps the C++ classes. It handles: -- **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 +- 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 ## Architecture -### Core Components +The generator follows a multi-stage pipeline: -``` -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 -``` +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` -### Data Flow +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 -1. **Type Extraction**: `extract-spine-cpp-types.js` parses C++ headers → `spine-cpp-types.json` -2. **Loading**: Generator loads JSON and exclusions -3. **Filtering**: Excludes types based on rules (templates, abstracts, manual exclusions) -4. **Generation**: Each generator processes types and creates C code -5. **Writing**: Files are written to `src/generated/` +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` 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`, `spine-c.h`) ## Type System ### Type Categories -The generator classifies all C++ types into systematic categories: +The generator categorizes types for systematic conversion: -1. **Primitives**: `int`, `float`, `double`, `bool`, `char`, `void`, `size_t` - - Direct mapping (e.g., `bool` → `bool`) +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 → spine_array_) +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 -2. **Special Types**: String, function pointers, PropertyId - - `String` → `const utf8 *` - - `void *` → `spine_void` - - `PropertyId` → `int64_t` (typedef'd to long long) +### C++ to C Type Mapping -3. **Arrays**: `Array` specializations - - Generated as `spine_array_` - - Full API for each specialization +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) -4. **Pointers**: Type followed by `*` - - Primitive pointers stay as-is (`float *`) - - Class pointers become opaque (`Bone *` → `spine_bone`) +### Special Types +- `String` → `const char*` +- `PropertyId` → `int64_t` +- `Array` → `spine_array_T` (specialized types) -5. **References**: Type followed by `&` - - Const references: treated as value parameters - - Non-const primitive references: output parameters (`float &` → `float *`) - - Class references: converted to opaque types - -6. **Enums**: Known spine enums - - Prefixed with `spine_` and converted to snake_case - -7. **Classes**: All other types - - Assumed to be spine classes, converted to `spine_` - -### Opaque Type Pattern - -All C++ classes are exposed as opaque pointers: - -```c -// 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(); -} -``` +### Primitive Types +- Primitives pass through unchanged: `int`, `float`, `bool`, etc. +- Non-const primitive references become pointers: `float&` → `float*` ## 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: -1. base.h (basic definitions) -2. types.h (all forward declarations) -3. extensions.h (custom functionality) -4. 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.json` doesn'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 `isTemplate` field -- **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__create()` for default constructor -- Generates `spine__create_with_()` for parameterized constructors -- Always generates `spine__dispose()` for cleanup - -#### Methods -- Getters: `spine__get_()` -- Setters: `spine__set_()` -- Other methods: `spine__()` -- 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: -1. Use `void *` everywhere (loses type safety) -2. 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: -```cpp -T& getValue(); // for non-const objects -const T& getValue() const; // for const objects +``` +codegen/ +├── src/ +│ ├── index.ts # Main entry point and orchestration +│ ├── type-extractor.ts # Clang AST parsing +│ ├── 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 +├── exclusions.txt # Type/method exclusions +├── spine-cpp-types.json # Extracted type information +├── package.json # Node.js configuration +├── tsconfig.json # TypeScript configuration +└── generated/ # Output directory (temporary) ``` -C can only have one function name. We exclude const versions and expose non-const. +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 +- `spine-c.h` - Main include file -### 5. Why Static RTTI Methods? +## Usage -RTTI objects are singletons in spine-cpp. Making getRTTI() static: -- Reflects actual usage (Type::rtti) -- Avoids unnecessary object parameter -- Cleaner API +```bash +# Install dependencies +npm install -## 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 +npx tsx src/index.ts +# The generated files will be in ../src/generated/ ``` -### Exclusion Rules - -1. **Type exclusions**: Entire type and all methods excluded -2. **Method exclusions**: Specific methods on otherwise included types -3. **Const-specific**: Can exclude just const or non-const version - -## Array Specializations - -### Scanning Process - -1. Examines all members of non-excluded types -2. Extracts Array patterns from: - - Return types - - Parameter types - - Field types -3. Cleans element types (removes class/struct prefix) -4. Categorizes as primitive/enum/pointer - -### Generated API - -For each Array, generates: -```c -// 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>) -- **PropertyId**: Treated as int64_t, not enum +The generator automatically: +- Detects when spine-cpp headers have changed +- Regenerates only when necessary +- Reports warnings and errors during generation ## Type Conversion Rules -### toCTypeName Function +### Primitive Types +Primitive types are "pass-through". -Implements systematic type conversion: - -1. **Remove namespace**: Strip any `spine::` prefix -2. **Check primitives**: Direct mapping via table -3. **Check special types**: String, void*, function pointers -4. **Check arrays**: Convert Array to spine_array_* -5. **Check pointers**: Handle based on pointed-to type -6. **Check references**: Handle based on const-ness -7. **Check enums**: Known enum list -8. **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 - -```bash -npm install +``` +C++ Type → C Type +───────────────────────────────── +int → int +float* → float* +const char* → const char* +bool → bool (stdbool.h) +size_t → size_t ``` -### Build and Run - -```bash -npm run build # Compile TypeScript -node dist/index.js # Run generator +### Class Types +``` +C++ Type → C Type +───────────────────────────────── +Bone* → spine_bone +const Bone* → const spine_bone +Bone& → spine_bone +const Bone& → spine_bone ``` -### What Happens +### Special Cases +``` +C++ Type → C Type +───────────────────────────────── +String → const char* +String& → const char* +const String& → const char* +PropertyId → int64_t +Array → spine_array_float +Array → spine_array_bone +``` -1. Checks if type extraction needed (file timestamps) -2. Runs extraction if needed -3. Loads types and exclusions -4. Filters types based on rules -5. Generates code for each type -6. Writes all files to src/generated/ -7. Updates main spine-c.h +### Output Parameters +``` +C++ Type → C Type +───────────────────────────────── +float& → float* (output param) +int& → int* (output param) +``` -### Output +### Function Naming +``` +C++ Method → C Function +───────────────────────────────────────── +Skeleton::updateCache() → spine_skeleton_update_cache() +AnimationState::apply() → spine_animation_state_apply() +Bone::getX() → spine_bone_get_x() +``` -- Generates ~150 .h/.cpp file pairs -- Creates arrays.h with ~30 specializations -- All files include proper license headers -- Organized by type for easy navigation +## Exclusions System -## Maintenance +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` found in the API: + +### Primitive Arrays +```cpp +Array → spine_array_float +Array → spine_array_int +Array → spine_array_unsigned_short +``` + +### Pointer Arrays +```cpp +Array → spine_array_bone +Array → spine_array_slot +Array → spine_array_float_ptr +``` + +### Enum Arrays +```cpp +Array → spine_array_blend_mode +Array → spine_array_property_id +``` + +### Unsupported Arrays +- `Array` - Use `const char**` instead +- `Array>` - 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_new(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_new(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 +typedef enum spine_blend_mode { + SPINE_BLEND_MODE_NORMAL = 0, + SPINE_BLEND_MODE_ADDITIVE = 1, + SPINE_BLEND_MODE_MULTIPLY = 2, + SPINE_BLEND_MODE_SCREEN = 3 +} spine_blend_mode; + +// Implementation: blend_mode.cpp +spine_blend_mode spine_blend_mode_from_cpp(BlendMode value) { + return (spine_blend_mode)value; +} + +BlendMode spine_blend_mode_to_cpp(spine_blend_mode value) { + return (BlendMode)value; +} +``` + +### Array Specialization +```c +// Header: array_float.h +typedef struct spine_array_float* spine_array_float; + +spine_array_float spine_array_float_new(int32_t capacity); +void spine_array_float_dispose(spine_array_float self); +int32_t spine_array_float_get_size(const spine_array_float self); +float spine_array_float_get(const spine_array_float self, int32_t index); +void spine_array_float_set(spine_array_float self, int32_t index, float value); + +// Implementation: array_float.cpp +struct spine_array_float { + Array data; +}; + +spine_array_float spine_array_float_new(int32_t capacity) { + auto* arr = new (__FILE__, __LINE__) spine_array_float(); + arr->data.setCapacity(capacity); + return arr; +} +``` + +## 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: `_new`, `_new2`, `_new3` + +### 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: `_new`, `_new2`, `_new3` +- Other overloads must be excluded (C doesn't support overloading) +- 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 +- Reports abstract classes, missing constructors, etc. +- Warnings don't stop generation but are reported at the end + +## 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 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 + +### 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 ### Adding New Types -1. No action needed - automatically detected from spine-cpp +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 -### Excluding Types/Methods +### Performance Considerations -1. Add to exclusions.txt -2. Regenerate - -### Changing Type Mappings - -1. Update toCTypeName in types.ts -2. 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 \ No newline at end of file +- 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 \ No newline at end of file